From 3ef496b7ba14f5099f3d09f9da0e931411c2afc0 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Fri, 5 Apr 2019 17:59:52 +0200 Subject: dashboard/app: split dashboard per-namespace We now have too many namespaces and bugs. Main page takes infinity to load. Also almost nobody is interested in more than 1 namespace. So split main page per-namespaces. --- dashboard/app/handler.go | 142 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 5 deletions(-) (limited to 'dashboard/app/handler.go') diff --git a/dashboard/app/handler.go b/dashboard/app/handler.go index 7fc133bee..8e2d66ad7 100644 --- a/dashboard/app/handler.go +++ b/dashboard/app/handler.go @@ -5,7 +5,12 @@ package dash import ( "bytes" + "encoding/base64" + "encoding/json" + "fmt" "net/http" + "sort" + "strings" "github.com/google/syzkaller/pkg/html" "golang.org/x/net/context" @@ -26,11 +31,12 @@ func handleContext(fn contextHandler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c := appengine.NewContext(r) if err := fn(c, w, r); err != nil { + hdr, _ := commonHeader(c, r, w, "") data := &struct { Header *uiHeader Error string }{ - Header: commonHeader(c, r), + Header: hdr, Error: err.Error(), } if err == ErrAccess { @@ -41,6 +47,10 @@ func handleContext(fn contextHandler) http.Handler { } return } + if redir, ok := err.(ErrRedirect); ok { + http.Redirect(w, r, redir.Error(), http.StatusMovedPermanently) + return + } if _, dontlog := err.(ErrDontLog); !dontlog { log.Errorf(c, "%v", err) } @@ -52,7 +62,10 @@ func handleContext(fn contextHandler) http.Handler { }) } -type ErrDontLog error +type ( + ErrDontLog error + ErrRedirect error +) func handleAuth(fn contextHandler) contextHandler { return func(c context.Context, w http.ResponseWriter, r *http.Request) error { @@ -76,17 +89,136 @@ type uiHeader struct { Admin bool LoginLink string AnalyticsTrackingID string + Subpage string + Namespace string + Namespaces []uiNamespace + Redirects []uiRedirect +} + +type uiNamespace struct { + Name string + Caption string +} + +type uiRedirect struct { + From string + To string +} + +type cookieData struct { + Namespace string `json:"namespace"` } -func commonHeader(c context.Context, r *http.Request) *uiHeader { +func commonHeader(c context.Context, r *http.Request, w http.ResponseWriter, ns string) (*uiHeader, error) { + accessLevel := accessLevel(c, r) + if ns == "" { + ns = strings.ToLower(r.URL.Path) + if ns != "" && ns[0] == '/' { + ns = ns[1:] + } + if pos := strings.IndexByte(ns, '/'); pos != -1 { + ns = ns[:pos] + } + } h := &uiHeader{ - Admin: accessLevel(c, r) == AccessAdmin, + Admin: accessLevel == AccessAdmin, AnalyticsTrackingID: config.AnalyticsTrackingID, } + const adminPage = "admin" + isAdminPage := r.URL.Path == "/"+adminPage + isBugPage := r.URL.Path == "/bug" + found := false + for ns1, cfg := range config.Namespaces { + if accessLevel < cfg.AccessLevel { + if ns1 == ns { + return nil, ErrAccess + } + continue + } + if ns1 == ns { + found = true + } + h.Namespaces = append(h.Namespaces, uiNamespace{ + Name: ns1, + Caption: cfg.DisplayTitle, + }) + // This handles redirects from old URL scheme to new scheme. + // This this should be removed at some point (Apr 5, 2019). + // Also see handling of "fixed" parameter in handleMain. + if isBugPage { + continue + } + h.Redirects = append(h.Redirects, uiRedirect{ + From: "#" + ns1, + To: "/" + ns1, + }) + fragments := []string{"managers", "open", "pending"} + for _, reporting := range cfg.Reporting { + if !reporting.moderation || accessLevel < reporting.AccessLevel { + continue + } + fragments = append(fragments, reporting.Name) + } + for _, frag := range fragments { + h.Redirects = append(h.Redirects, uiRedirect{ + From: "#" + ns1 + "-" + frag, + To: "/" + ns1 + "#" + frag, + }) + } + } + sort.Slice(h.Namespaces, func(i, j int) bool { + return h.Namespaces[i].Caption < h.Namespaces[j].Caption + }) + cookie := decodeCookie(r) + if !found { + ns = config.DefaultNamespace + if cfg := config.Namespaces[cookie.Namespace]; cfg != nil && cfg.AccessLevel <= accessLevel { + ns = cookie.Namespace + } + if accessLevel == AccessAdmin { + ns = adminPage + } + if ns != adminPage || !isAdminPage { + return nil, ErrRedirect(fmt.Errorf("/%v", ns)) + } + } + if ns != adminPage { + h.Namespace = ns + cookie.Namespace = ns + encodeCookie(w, cookie) + } if user.Current(c) == nil { h.LoginLink, _ = user.LoginURL(c, r.URL.String()) } - return h + return h, nil +} + +const cookieName = "syzkaller" + +func decodeCookie(r *http.Request) *cookieData { + cd := new(cookieData) + cookie, err := r.Cookie(cookieName) + if err != nil { + return cd + } + decoded, err := base64.StdEncoding.DecodeString(cookie.Value) + if err != nil { + return cd + } + json.Unmarshal(decoded, cd) + return cd +} + +func encodeCookie(w http.ResponseWriter, cd *cookieData) { + data, err := json.Marshal(cd) + if err != nil { + return + } + cookie := &http.Cookie{ + Name: cookieName, + Value: base64.StdEncoding.EncodeToString(data), + } + http.SetCookie(w, cookie) } var templates = html.CreateGlob("*.html") -- cgit mrf-deployment