diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2019-04-05 17:59:52 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2019-04-08 14:32:32 +0200 |
| commit | 3ef496b7ba14f5099f3d09f9da0e931411c2afc0 (patch) | |
| tree | a8be358be459834235232c96bbb6bdd9d0a2e1ba | |
| parent | c34fde03ec2b778c7cb3f4463dac2e6b9c7934c9 (diff) | |
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.
| -rw-r--r-- | dashboard/app/access_test.go | 30 | ||||
| -rw-r--r-- | dashboard/app/admin.html | 4 | ||||
| -rw-r--r-- | dashboard/app/app.yaml | 9 | ||||
| -rw-r--r-- | dashboard/app/app_test.go | 27 | ||||
| -rw-r--r-- | dashboard/app/bug.html | 6 | ||||
| -rw-r--r-- | dashboard/app/config.go | 5 | ||||
| -rw-r--r-- | dashboard/app/entities.go | 10 | ||||
| -rw-r--r-- | dashboard/app/fixed.html | 19 | ||||
| -rw-r--r-- | dashboard/app/handler.go | 142 | ||||
| -rw-r--r-- | dashboard/app/main.go | 227 | ||||
| -rw-r--r-- | dashboard/app/main.html | 17 | ||||
| -rw-r--r-- | dashboard/app/static/common.js | 4 | ||||
| -rw-r--r-- | dashboard/app/static/style.css | 8 | ||||
| -rw-r--r-- | dashboard/app/templates.html | 35 | ||||
| -rw-r--r-- | dashboard/app/util_test.go | 12 | ||||
| -rw-r--r-- | docs/akaros/found_bugs.md | 2 | ||||
| -rw-r--r-- | docs/linux/found_bugs.md | 2 | ||||
| -rw-r--r-- | docs/linux/reporting_kernel_bugs.md | 2 | ||||
| -rw-r--r-- | docs/netbsd/README.md | 2 | ||||
| -rw-r--r-- | docs/netbsd/found_bugs.md | 2 | ||||
| -rw-r--r-- | docs/openbsd/found_bugs.md | 2 | ||||
| -rw-r--r-- | docs/syzbot.md | 2 | ||||
| -rw-r--r-- | pkg/html/generated.go | 6 |
23 files changed, 425 insertions, 150 deletions
diff --git a/dashboard/app/access_test.go b/dashboard/app/access_test.go index c331fa242..7765f0c06 100644 --- a/dashboard/app/access_test.go +++ b/dashboard/app/access_test.go @@ -53,10 +53,34 @@ func TestAccess(t *testing.T) { url string // url at which this entity can be requested. } entities := []entity{ + // Main pages. { - // Main page. - level: config.AccessLevel, - url: "/", + level: AccessAdmin, + url: "/admin", + }, + { + level: AccessPublic, + url: "/access-public", + }, + { + level: AccessPublic, + url: "/access-public/fixed", + }, + { + level: AccessUser, + url: "/access-user", + }, + { + level: AccessUser, + url: "/access-user/fixed", + }, + { + level: AccessAdmin, + url: "/access-admin", + }, + { + level: AccessAdmin, + url: "/access-admin/fixed", }, { // Any references to namespace, reporting, links, etc. diff --git a/dashboard/app/admin.html b/dashboard/app/admin.html index 09fd67513..edbdc9f7e 100644 --- a/dashboard/app/admin.html +++ b/dashboard/app/admin.html @@ -26,6 +26,7 @@ Main page. <table class="list_table"> <caption id="jobs"><a class="plain" href="#jobs">Recent jobs:</a></caption> + <thead> <tr> <th>Bug</th> <th>Created</th> @@ -36,6 +37,8 @@ Main page. <th>Manager</th> <th>Result</th> </tr> + </thead> + <tbody> {{range $job := $.Jobs}} <tr> <td class="title"><a href="{{$job.BugLink}}">{{$job.BugTitle}}</a></td> @@ -74,6 +77,7 @@ Main page. </td> </tr> {{end}} + </tbody> </table> <br><br> </body> diff --git a/dashboard/app/app.yaml b/dashboard/app/app.yaml index 7c8d90ab9..0e5ca351f 100644 --- a/dashboard/app/app.yaml +++ b/dashboard/app/app.yaml @@ -19,12 +19,6 @@ handlers: - url: /static static_dir: static secure: always -- url: /(|bug|text|x/.+) - script: _go_app - secure: always -- url: /(api) - script: _go_app - secure: always - url: /(admin|email_poll) script: _go_app login: admin @@ -32,3 +26,6 @@ handlers: - url: /_ah/(mail/.+|bounce) script: _go_app login: admin +- url: /(|api|bug|text|x/.+|.*) + script: _go_app + secure: always diff --git a/dashboard/app/app_test.go b/dashboard/app/app_test.go index 6d0e887a7..8cc3bce80 100644 --- a/dashboard/app/app_test.go +++ b/dashboard/app/app_test.go @@ -7,6 +7,7 @@ package dash import ( "fmt" + "net/http" "strconv" "strings" "testing" @@ -30,6 +31,7 @@ var testConfig = &GlobalConfig{ EmailBlacklist: []string{ "\"Bar\" <BlackListed@Domain.com>", }, + DefaultNamespace: "test1", Namespaces: map[string]*Config{ "test1": { AccessLevel: AccessAdmin, @@ -288,7 +290,7 @@ func TestApp(t *testing.T) { c := NewCtx(t) defer c.Close() - c.expectOK(c.GET("/")) + c.expectOK(c.GET("/test1")) apiClient1 := c.makeClient(client1, key1, false) apiClient2 := c.makeClient(client2, key2, false) @@ -337,6 +339,29 @@ func TestApp(t *testing.T) { }) } +func TestRedirects(t *testing.T) { + c := NewCtx(t) + defer c.Close() + + checkRedirect(c, AccessUser, "/", "/test1") // redirect to default namespace + checkRedirect(c, AccessAdmin, "/", "/admin") + + _, err := c.httpRequest("GET", "/access-user", "", AccessPublic) // not accessible namespace + c.expectForbidden(err) + + _, err = c.httpRequest("GET", "/access-user", "", AccessUser) + c.expectOK(err) +} + +func checkRedirect(c *Ctx, accessLevel AccessLevel, from, to string) { + _, err := c.httpRequest("GET", from, "", accessLevel) + c.expectNE(err, nil) + httpErr, ok := err.(HttpError) + c.expectTrue(ok) + c.expectEQ(httpErr.Code, http.StatusMovedPermanently) + c.expectEQ(httpErr.Headers["Location"], []string{to}) +} + // Test purging of old crashes for bugs with lots of crashes. func TestPurgeOldCrashes(t *testing.T) { if testing.Short() { diff --git a/dashboard/app/bug.html b/dashboard/app/bug.html index ab32d8e54..6bbf30fc7 100644 --- a/dashboard/app/bug.html +++ b/dashboard/app/bug.html @@ -14,7 +14,7 @@ Page with details about a single bug. <body> {{template "header" .Header}} - <b>[{{.Bug.Namespace}}] {{.Bug.Title}}</b><br> + <b>{{.Bug.Title}}</b><br> Status: {{if .Bug.ExternalLink}}<a href="{{.Bug.ExternalLink}}">{{.Bug.Status}}</a>{{else}}{{.Bug.Status}}{{end}}<br> Reported-by: {{.Bug.CreditEmail}}<br> {{if .Bug.Commits}} @@ -71,6 +71,7 @@ Page with details about a single bug. <table class="list_table"> <caption>All crashes ({{.Bug.NumCrashes}}):</caption> + <thead> <tr> <th><a onclick="return sortTable(this, 'Manager', textSort)" href="#">Manager</a></th> <th><a onclick="return sortTable(this, 'Time', textSort, true)" href="#">Time</a></th> @@ -86,6 +87,8 @@ Page with details about a single bug. <th><a onclick="return sortTable(this, 'Maintainers', textSort)" href="#">Maintainers</a></th> {{end}} </tr> + </thead> + <tbody> {{range $c := $.Crashes}} <tr> <td class="manager">{{$c.Manager}}</td> @@ -104,6 +107,7 @@ Page with details about a single bug. {{end}} </tr> {{end}} + </tbody> </table> </body> </html> diff --git a/dashboard/app/config.go b/dashboard/app/config.go index 4b8a70a09..1676bf6ce 100644 --- a/dashboard/app/config.go +++ b/dashboard/app/config.go @@ -33,6 +33,8 @@ type GlobalConfig struct { Clients map[string]string // List of emails blacklisted from issuing test requests. EmailBlacklist []string + // Namespace that is shown by default (no namespace selected yet). + DefaultNamespace string // Per-namespace config. // Namespaces are a mechanism to separate groups of different kernels. // E.g. Debian 4.4 kernels and Ubuntu 4.9 kernels. @@ -191,6 +193,9 @@ func checkConfig(cfg *GlobalConfig) { clientNames := make(map[string]bool) checkClients(clientNames, cfg.Clients) checkConfigAccessLevel(&cfg.AccessLevel, AccessPublic, "global") + if cfg.Namespaces[cfg.DefaultNamespace] == nil { + panic(fmt.Sprintf("default namespace %q is not found", cfg.DefaultNamespace)) + } for ns, cfg := range cfg.Namespaces { checkNamespace(ns, cfg, namespaces, clientNames) } diff --git a/dashboard/app/entities.go b/dashboard/app/entities.go index b1fd89d32..c6d26c476 100644 --- a/dashboard/app/entities.go +++ b/dashboard/app/entities.go @@ -302,16 +302,18 @@ func updateManager(c context.Context, ns, name string, fn func(mgr *Manager, sta return db.RunInTransaction(c, tx, &db.TransactionOptions{Attempts: 10}) } -func loadAllManagers(c context.Context) ([]*Manager, []*db.Key, error) { +func loadAllManagers(c context.Context, ns string) ([]*Manager, []*db.Key, error) { var managers []*Manager - keys, err := db.NewQuery("Manager"). - GetAll(c, &managers) + query := db.NewQuery("Manager") + if ns != "" { + query = query.Filter("Namespace=", ns) + } + keys, err := query.GetAll(c, &managers) if err != nil { return nil, nil, fmt.Errorf("failed to query managers: %v", err) } var result []*Manager var resultKeys []*db.Key - for i, mgr := range managers { if config.Namespaces[mgr.Namespace].Managers[mgr.Name].Decommissioned { continue diff --git a/dashboard/app/fixed.html b/dashboard/app/fixed.html new file mode 100644 index 000000000..4a514583f --- /dev/null +++ b/dashboard/app/fixed.html @@ -0,0 +1,19 @@ +{{/* +Copyright 2019 syzkaller project authors. All rights reserved. +Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +Fixed bugs page. +*/}} + +<!doctype html> +<html> +<head> + {{template "head" .Header}} + <title>syzbot</title> +</head> +<body> + {{template "header" .Header}} + + {{template "bug_list" .Bugs}} +</body> +</html> 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") diff --git a/dashboard/app/main.go b/dashboard/app/main.go index fdf3904fc..88bcfce1e 100644 --- a/dashboard/app/main.go +++ b/dashboard/app/main.go @@ -36,12 +36,25 @@ func initHTTPHandlers() { http.Handle("/x/patch.diff", handlerWrapper(handleTextX(textPatch))) http.Handle("/x/bisect.txt", handlerWrapper(handleTextX(textLog))) http.Handle("/x/error.txt", handlerWrapper(handleTextX(textError))) + for ns := range config.Namespaces { + http.Handle("/"+ns, handlerWrapper(handleMain)) + http.Handle("/"+ns+"/fixed", handlerWrapper(handleFixed)) + } } -type uiMain struct { - Header *uiHeader - Now time.Time - BugNamespaces []*uiBugNamespace +type uiMainPage struct { + Header *uiHeader + Now time.Time + FixedLink string + FixedCount int + Managers []*uiManager + Groups []*uiBugGroup +} + +type uiFixedPage struct { + Header *uiHeader + Now time.Time + Bugs *uiBugGroup } type uiAdminPage struct { @@ -105,15 +118,6 @@ type uiBugPage struct { Crashes []*uiCrash } -type uiBugNamespace struct { - Name string - Caption string - FixedLink string - FixedCount int - Managers []*uiManager - Groups []*uiBugGroup -} - type uiBugGroup struct { Now time.Time Caption string @@ -191,40 +195,63 @@ type uiJob struct { // handleMain serves main page. func handleMain(c context.Context, w http.ResponseWriter, r *http.Request) error { - accessLevel := accessLevel(c, r) - var managers []*uiManager - if r.FormValue("fixed") == "" { - var err error - managers, err = loadManagers(c, accessLevel) - if err != nil { - return err - } + if ns := r.FormValue("fixed"); ns != "" { + http.Redirect(w, r, fmt.Sprintf("/%v/fixed", ns), http.StatusMovedPermanently) + return nil } - bugNamespaces, err := fetchBugs(c, r) + hdr, err := commonHeader(c, r, w, "") if err != nil { return err } - for _, ns := range bugNamespaces { - for _, mgr := range managers { - if ns.Name == mgr.Namespace { - ns.Managers = append(ns.Managers, mgr) - } - } + accessLevel := accessLevel(c, r) + managers, err := loadManagers(c, accessLevel, hdr.Namespace) + if err != nil { + return err } - data := &uiMain{ - Header: commonHeader(c, r), - Now: timeNow(c), - BugNamespaces: bugNamespaces, + groups, fixedCount, err := fetchNamespaceBugs(c, accessLevel, hdr.Namespace) + if err != nil { + return err + } + data := &uiMainPage{ + Header: hdr, + Now: timeNow(c), + FixedCount: fixedCount, + FixedLink: fmt.Sprintf("/%v/fixed", hdr.Namespace), + Groups: groups, + Managers: managers, } return serveTemplate(w, "main.html", data) } +func handleFixed(c context.Context, w http.ResponseWriter, r *http.Request) error { + accessLevel := accessLevel(c, r) + hdr, err := commonHeader(c, r, w, "") + if err != nil { + return err + } + hdr.Subpage = "/fixed" + bugs, err := fetchFixedBugs(c, accessLevel, hdr.Namespace) + if err != nil { + return err + } + data := &uiFixedPage{ + Header: hdr, + Now: timeNow(c), + Bugs: bugs, + } + return serveTemplate(w, "fixed.html", data) +} + func handleAdmin(c context.Context, w http.ResponseWriter, r *http.Request) error { accessLevel := accessLevel(c, r) if accessLevel != AccessAdmin { return ErrAccess } - managers, err := loadManagers(c, accessLevel) + hdr, err := commonHeader(c, r, w, "") + if err != nil { + return err + } + managers, err := loadManagers(c, accessLevel, "") if err != nil { return err } @@ -237,7 +264,7 @@ func handleAdmin(c context.Context, w http.ResponseWriter, r *http.Request) erro return err } data := &uiAdminPage{ - Header: commonHeader(c, r), + Header: hdr, Log: errorLog, Managers: managers, Jobs: jobs, @@ -266,6 +293,10 @@ func handleBug(c context.Context, w http.ResponseWriter, r *http.Request) error if err := checkAccessLevel(c, r, bug.sanitizeAccess(accessLevel)); err != nil { return err } + hdr, err := commonHeader(c, r, w, bug.Namespace) + if err != nil { + return err + } state, err := loadReportingState(c) if err != nil { return err @@ -326,7 +357,7 @@ func handleBug(c context.Context, w http.ResponseWriter, r *http.Request) error } } data := &uiBugPage{ - Header: commonHeader(c, r), + Header: hdr, Now: timeNow(c), Bug: uiBug, BisectCause: bisectCause, @@ -416,47 +447,21 @@ func textFilename(tag string) string { } } -func fetchBugs(c context.Context, r *http.Request) ([]*uiBugNamespace, error) { - state, err := loadReportingState(c) +func fetchNamespaceBugs(c context.Context, accessLevel AccessLevel, ns string) ([]*uiBugGroup, int, error) { + var bugs []*Bug + _, err := db.NewQuery("Bug"). + Filter("Namespace=", ns). + GetAll(c, &bugs) if err != nil { - return nil, err - } - accessLevel := accessLevel(c, r) - onlyFixed := r.FormValue("fixed") - var res []*uiBugNamespace - for ns, cfg := range config.Namespaces { - if accessLevel < cfg.AccessLevel { - continue - } - if onlyFixed != "" && onlyFixed != ns { - continue - } - uiNamespace, err := fetchNamespaceBugs(c, accessLevel, ns, state, onlyFixed != "") - if err != nil { - return nil, err - } - res = append(res, uiNamespace) + return nil, 0, err } - sort.Slice(res, func(i, j int) bool { - return res[i].Caption < res[j].Caption - }) - return res, nil -} - -func fetchNamespaceBugs(c context.Context, accessLevel AccessLevel, ns string, - state *ReportingState, onlyFixed bool) (*uiBugNamespace, error) { - query := db.NewQuery("Bug").Filter("Namespace=", ns) - if onlyFixed { - query = query.Filter("Status=", BugStatusFixed) - } - var bugs []*Bug - _, err := query.GetAll(c, &bugs) + state, err := loadReportingState(c) if err != nil { - return nil, err + return nil, 0, err } managers, err := managerList(c, ns) if err != nil { - return nil, err + return nil, 0, err } fixedCount := 0 groups := make(map[int][]*uiBug) @@ -465,8 +470,9 @@ func fetchNamespaceBugs(c context.Context, accessLevel AccessLevel, ns string, for _, bug := range bugs { if bug.Status == BugStatusFixed { fixedCount++ + continue } - if bug.Status == BugStatusInvalid || bug.Status == BugStatusFixed != onlyFixed { + if bug.Status == BugStatusInvalid { continue } if accessLevel < bug.sanitizeAccess(accessLevel) { @@ -479,10 +485,8 @@ func fetchNamespaceBugs(c context.Context, accessLevel AccessLevel, ns string, uiBug := createUIBug(c, bug, state, managers) bugMap[bug.keyHash()] = uiBug id := uiBug.ReportingIndex - if bug.Status == BugStatusFixed { + if len(uiBug.Commits) != 0 { id = -1 - } else if len(uiBug.Commits) != 0 { - id = -2 } groups[id] = append(groups[id], uiBug) } @@ -493,6 +497,7 @@ func fetchNamespaceBugs(c context.Context, accessLevel AccessLevel, ns string, } mergeUIBug(c, bug, dup) } + cfg := config.Namespaces[ns] var uiGroups []*uiBugGroup for index, bugs := range groups { sort.Slice(bugs, func(i, j int) bool { @@ -504,27 +509,24 @@ func fetchNamespaceBugs(c context.Context, accessLevel AccessLevel, ns string, } return bugs[i].ReportedTime.After(bugs[j].ReportedTime) }) - caption, fragment, showPatch, showPatched := "", "", false, false + caption, fragment, showPatched := "", "", false switch index { case -1: - caption, showPatch, showPatched = "fixed", true, false - case -2: - caption, showPatch, showPatched = "fix pending", false, true - fragment = ns + "-pending" - case len(config.Namespaces[ns].Reporting) - 1: - caption, showPatch, showPatched = "open", false, false - fragment = ns + "-open" + caption, showPatched = "fix pending", true + fragment = "pending" + case len(cfg.Reporting) - 1: + caption, showPatched = "open", false + fragment = "open" default: - reporting := &config.Namespaces[ns].Reporting[index] - caption, showPatch, showPatched = reporting.DisplayTitle, false, false - fragment = ns + "-" + reporting.Name + reporting := &cfg.Reporting[index] + caption, showPatched = reporting.DisplayTitle, false + fragment = reporting.Name } uiGroups = append(uiGroups, &uiBugGroup{ Now: timeNow(c), - Caption: fmt.Sprintf("%v (%v)", caption, len(bugs)), + Caption: caption, Fragment: fragment, Namespace: ns, - ShowPatch: showPatch, ShowPatched: showPatched, ShowIndex: index, Bugs: bugs, @@ -533,19 +535,42 @@ func fetchNamespaceBugs(c context.Context, accessLevel AccessLevel, ns string, sort.Slice(uiGroups, func(i, j int) bool { return uiGroups[i].ShowIndex > uiGroups[j].ShowIndex }) - fixedLink := "" - if !onlyFixed { - fixedLink = fmt.Sprintf("?fixed=%v", ns) + return uiGroups, fixedCount, nil +} + +func fetchFixedBugs(c context.Context, accessLevel AccessLevel, ns string) (*uiBugGroup, error) { + var bugs []*Bug + _, err := db.NewQuery("Bug"). + Filter("Namespace=", ns). + Filter("Status=", BugStatusFixed). + GetAll(c, &bugs) + if err != nil { + return nil, err } - cfg := config.Namespaces[ns] - uiNamespace := &uiBugNamespace{ - Name: ns, - Caption: cfg.DisplayTitle, - FixedCount: fixedCount, - FixedLink: fixedLink, - Groups: uiGroups, + state, err := loadReportingState(c) + if err != nil { + return nil, err } - return uiNamespace, nil + managers, err := managerList(c, ns) + if err != nil { + return nil, err + } + res := &uiBugGroup{ + Now: timeNow(c), + Caption: "fixed", + ShowPatch: true, + Namespace: ns, + } + for _, bug := range bugs { + if accessLevel < bug.sanitizeAccess(accessLevel) { + continue + } + res.Bugs = append(res.Bugs, createUIBug(c, bug, state, managers)) + } + sort.Slice(res.Bugs, func(i, j int) bool { + return res.Bugs[i].ClosedTime.After(res.Bugs[j].ClosedTime) + }) + return res, nil } func loadDupsForBug(c context.Context, r *http.Request, bug *Bug, state *ReportingState, managers []string) ( @@ -788,10 +813,10 @@ func makeUIBuild(build *Build) *uiBuild { } } -func loadManagers(c context.Context, accessLevel AccessLevel) ([]*uiManager, error) { +func loadManagers(c context.Context, accessLevel AccessLevel, ns string) ([]*uiManager, error) { now := timeNow(c) date := timeDate(now) - managers, managerKeys, err := loadAllManagers(c) + managers, managerKeys, err := loadAllManagers(c, ns) if err != nil { return nil, err } @@ -877,7 +902,7 @@ func loadRecentJobs(c context.Context) ([]*uiJob, error) { var jobs []*Job keys, err := db.NewQuery("Job"). Order("-Created"). - Limit(40). + Limit(80). GetAll(c, &jobs) if err != nil { return nil, err diff --git a/dashboard/app/main.html b/dashboard/app/main.html index e1fc5a5ec..4c1191a09 100644 --- a/dashboard/app/main.html +++ b/dashboard/app/main.html @@ -14,17 +14,12 @@ Main page. <body> {{template "header" .Header}} - {{range $ns := $.BugNamespaces}} - <br> - <a class="plain" href="#{{$ns.Name}}"><h2 id="{{$ns.Name}}">{{$ns.Caption}}</h2></a> - {{if $ns.FixedLink}} - <a href="{{$ns.FixedLink}}">fixed bugs ({{$ns.FixedCount}})</a> - {{end}} - {{template "manager_list" $ns.Managers}} - {{range $group := $ns.Groups}} - {{template "bug_list" $group}} - {{end}} - <br> + {{if $.FixedLink}} + <a href="{{$.FixedLink}}">fixed bugs ({{$.FixedCount}})</a> + {{end}} + {{template "manager_list" $.Managers}} + {{range $group := $.Groups}} + {{template "bug_list" $group}} {{end}} </body> </html> diff --git a/dashboard/app/static/common.js b/dashboard/app/static/common.js index d3bd12fe6..f501d61da 100644 --- a/dashboard/app/static/common.js +++ b/dashboard/app/static/common.js @@ -2,7 +2,7 @@ // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. function sortTable(item, colName, conv, desc = false) { - table = item.parentNode.parentNode.parentNode; + table = item.parentNode.parentNode.parentNode.parentNode; rows = table.rows; col = findColumnByName(rows[0].getElementsByTagName("th"), colName); values = []; @@ -18,7 +18,7 @@ function sortTable(item, colName, conv, desc = false) { return 1; }); for (i = 0; i < values.length; i++) - table.appendChild(values[i][1]); + table.tBodies[0].appendChild(values[i][1]); return false; } diff --git a/dashboard/app/static/style.css b/dashboard/app/static/style.css index 9a1b15315..ee5ef5fe1 100644 --- a/dashboard/app/static/style.css +++ b/dashboard/app/static/style.css @@ -35,6 +35,12 @@ table td, table th { overflow: hidden; } +.namespace { + font-weight: bold; + font-size: large; + color: #375EAB; +} + .position_table { border: 0px; margin: 0px; @@ -59,7 +65,7 @@ table td, table th { background: #F4F4F4; } -.list_table tr:nth-child(2n+1) { +.list_table tr:nth-child(2n) { background: #F4F4F4; } diff --git a/dashboard/app/templates.html b/dashboard/app/templates.html index 5747aa722..8c95831c9 100644 --- a/dashboard/app/templates.html +++ b/dashboard/app/templates.html @@ -6,6 +6,15 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the {{/* Common page head part, invoked with *uiHeader */}} {{define "head"}} <link rel="stylesheet" href="/static/style.css"/> + {{if .Redirects}} + <script> + {{range $redir := .Redirects}} + if (window.location.hash == "{{$redir.From}}") { + window.location.href = "{{$redir.To}}"; + } + {{end}} + </script> + {{end}} <script src="/static/common.js"></script> {{if .AnalyticsTrackingID}} <script async src="https://www.googletagmanager.com/gtag/js?id={{.AnalyticsTrackingID}}"></script> @@ -24,7 +33,14 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the <table class="position_table"> <tr> <td> - <h1><a href="/">syzbot</a></h1> + <h1><a href="/{{$.Namespace}}">syzbot</a></h1> + <select class="namespace" onchange="window.location.href = '/' + this.value + '{{.Subpage}}';"> + {{range $ns := .Namespaces}} + <option value="{{$ns.Name}}" {{if eq $.Namespace $ns.Name}}selected="1"{{end}}> + {{$ns.Caption}} + </option> + {{end}} + </select> </td> <td class="search"> {{if .Admin}} @@ -49,10 +65,14 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the {{if .Bugs}} <table class="list_table"> {{if $.Fragment}} - <caption id="{{$.Fragment}}"><a class="plain" href="#{{$.Fragment}}">{{$.Caption}}:</a></caption> + <caption id="{{$.Fragment}}"><a class="plain" href="#{{$.Fragment}}"> {{else}} - <caption>{{$.Caption}}:</caption> + <caption> {{end}} + {{$.Caption}} ({{len $.Bugs}}): + {{if $.Fragment}}</a>{{end}} + </caption> + <thead> <tr> {{if $.ShowNamespace}} <th><a onclick="return sortTable(this, 'Kernel', textSort)" href="#">Kernel</a></th> @@ -74,6 +94,8 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the <th><a onclick="return sortTable(this, 'Status', textSort)" href="#">Status</a></th> {{end}} </tr> + </thead> + <tbody> {{range $b := .Bugs}} <tr> {{if $.ShowNamespace}}<td>{{$b.Namespace}}</td>{{end}} @@ -107,6 +129,7 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the {{end}} </tr> {{end}} + </tbody> </table> {{end}} {{end}} @@ -116,7 +139,8 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the {{define "manager_list"}} {{if .}} <table class="list_table"> - <caption id="{{(index . 0).Namespace}}-managers"><a class="plain" href="#{{(index . 0).Namespace}}-managers">Instances:</a></caption> + <caption id="managers"><a class="plain" href="managers">Instances:</a></caption> + <thead> <tr> <th>Name</th> <th>Active</th> @@ -143,6 +167,8 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the <th>Freshness</th> <th>Status</th> </tr> + </thead> + <tbody> {{range $mgr := .}} <tr> <td>{{link $mgr.Link $mgr.Name}}</td> @@ -181,6 +207,7 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the {{end}} </tr> {{end}} + </tbody> </table> {{end}} {{end}} diff --git a/dashboard/app/util_test.go b/dashboard/app/util_test.go index 73d1986ad..94b1f4578 100644 --- a/dashboard/app/util_test.go +++ b/dashboard/app/util_test.go @@ -139,7 +139,10 @@ func caller(skip int) string { func (c *Ctx) Close() { if !c.t.Failed() { // Ensure that we can render main page and all bugs in the final test state. - c.expectOK(c.GET("/")) + c.expectOK(c.GET("/test1")) + c.expectOK(c.GET("/test2")) + c.expectOK(c.GET("/test1/fixed")) + c.expectOK(c.GET("/test2/fixed")) c.expectOK(c.GET("/admin")) var bugs []*Bug keys, err := db.NewQuery("Bug").GetAll(c.ctx, &bugs) @@ -201,14 +204,15 @@ func (c *Ctx) httpRequest(method, url, body string, access AccessLevel) ([]byte, http.DefaultServeMux.ServeHTTP(w, r) c.t.Logf("REPLY: %v", w.Code) if w.Code != http.StatusOK { - return nil, HttpError{w.Code, w.Body.String()} + return nil, HttpError{w.Code, w.Body.String(), w.HeaderMap} } return w.Body.Bytes(), nil } type HttpError struct { - Code int - Body string + Code int + Body string + Headers http.Header } func (err HttpError) Error() string { diff --git a/docs/akaros/found_bugs.md b/docs/akaros/found_bugs.md index 9ad09d04e..6c29b4487 100644 --- a/docs/akaros/found_bugs.md +++ b/docs/akaros/found_bugs.md @@ -2,7 +2,7 @@ Most latest bugs are reported by [syzbot](/docs/syzbot.md) to [akaros](https://groups.google.com/forum/#!searchin/akaros/syzbot) -mailing list and are listed on the [dashboard](https://syzkaller.appspot.com/#akaros). +mailing list and are listed on the [dashboard](https://syzkaller.appspot.com/akaros). _newer first_ diff --git a/docs/linux/found_bugs.md b/docs/linux/found_bugs.md index c8de55f86..1c2e4174f 100644 --- a/docs/linux/found_bugs.md +++ b/docs/linux/found_bugs.md @@ -1,6 +1,6 @@ # Found bugs -Most latest bugs are reported by [syzbot](/docs/syzbot.md) and are listed [here](https://groups.google.com/forum/#!forum/syzkaller-bugs) and on the [dashboard](https://syzkaller.appspot.com/#upstream). +Most latest bugs are reported by [syzbot](/docs/syzbot.md) and are listed [here](https://groups.google.com/forum/#!forum/syzkaller-bugs) and on the [dashboard](https://syzkaller.appspot.com/upstream). Additional USB bugs are [here](/docs/linux/found_bugs_usb.md). _newer first_ diff --git a/docs/linux/reporting_kernel_bugs.md b/docs/linux/reporting_kernel_bugs.md index 030ba3d17..d8e5d9256 100644 --- a/docs/linux/reporting_kernel_bugs.md +++ b/docs/linux/reporting_kernel_bugs.md @@ -1,6 +1,6 @@ # Reporting Linux kernel bugs -Before reporting a bug make sure nobody else already reported it. The easiest way to do this is to search through the [syzkaller mailing list](https://groups.google.com/forum/#!forum/syzkaller), [syzkaller-bugs mailing list](https://groups.google.com/forum/#!forum/syzkaller-bugs) and [syzbot dashboard](https://syzkaller.appspot.com/#upstream) for key frames present in the kernel stack traces. +Before reporting a bug make sure nobody else already reported it. The easiest way to do this is to search through the [syzkaller mailing list](https://groups.google.com/forum/#!forum/syzkaller), [syzkaller-bugs mailing list](https://groups.google.com/forum/#!forum/syzkaller-bugs) and [syzbot dashboard](https://syzkaller.appspot.com/upstream) for key frames present in the kernel stack traces. Please report found bugs to the Linux kernel maintainers. To find out the list of maintainers responsible for a particular kernel subsystem, use the [get_maintainer.pl](https://github.com/torvalds/linux/blob/master/scripts/get_maintainer.pl) script: `./scripts/get_maintainer.pl -f guilty_file.c`. Please add `syzkaller@googlegroups.com` to the CC list. diff --git a/docs/netbsd/README.md b/docs/netbsd/README.md index e10955713..fc4119505 100644 --- a/docs/netbsd/README.md +++ b/docs/netbsd/README.md @@ -168,7 +168,7 @@ You can compile a kernel with KASAN to increase the chances of finding bugs. [syzbot](/docs/syzbot.md) tests NetBSD and reports bugs to [syzkaller-netbsd-bugs](https://groups.google.com/forum/#!forum/syzkaller-netbsd-bugs) mailing list -(also can be seen on [dashboard](https://syzkaller.appspot.com#netbsd)). +(also can be seen on [dashboard](https://syzkaller.appspot.com/netbsd)). The image `syzbot` uses can be downloaded from [here](https://storage.googleapis.com/syzkaller/netbsd-image.raw) (2GB) and root diff --git a/docs/netbsd/found_bugs.md b/docs/netbsd/found_bugs.md index 94e9328da..068159e0a 100644 --- a/docs/netbsd/found_bugs.md +++ b/docs/netbsd/found_bugs.md @@ -2,7 +2,7 @@ Most latest bugs are reported by [syzbot](/docs/syzbot.md) to [syzkaller-netbsd-bugs](https://groups.google.com/forum/#!forum/syzkaller-netbsd-bugs) -mailing list and are listed on the [dashboard](https://syzkaller.appspot.com/#netbsd) +mailing list and are listed on the [dashboard](https://syzkaller.appspot.com/netbsd) Newer bugs come first diff --git a/docs/openbsd/found_bugs.md b/docs/openbsd/found_bugs.md index 394d76104..345232536 100644 --- a/docs/openbsd/found_bugs.md +++ b/docs/openbsd/found_bugs.md @@ -2,7 +2,7 @@ Most latest bugs are reported by [syzbot](/docs/syzbot.md) to [syzkaller-openbsd-bugs](https://groups.google.com/forum/#!forum/syzkaller-openbsd-bugs) -mailing list and are listed on the [dashboard](https://syzkaller.appspot.com/#openbsd). +mailing list and are listed on the [dashboard](https://syzkaller.appspot.com/openbsd). Newer bugs comes first. diff --git a/docs/syzbot.md b/docs/syzbot.md index 17a0d6c27..2b8cc454e 100644 --- a/docs/syzbot.md +++ b/docs/syzbot.md @@ -220,7 +220,7 @@ existing IPC object) and there is long tail of other reasons. Bugs with reproducers are automatically reported to kernel mailing lists. Bugs without reproducers are first staged in moderation queue to filter out invalid, unactionable or duplicate reports. Staged bugs are shown on dashboard -in [moderation](https://syzkaller.appspot.com/#upstream-moderation2) section +in [moderation](https://syzkaller.appspot.com/upstream#moderation2) section and mailed to [syzkaller-upstream-moderation](https://groups.google.com/forum/#!forum/syzkaller-upstream-moderation) mailing list. Staged bugs accept all commands supported for reported bugs diff --git a/pkg/html/generated.go b/pkg/html/generated.go index cd6ee413c..089f3f193 100644 --- a/pkg/html/generated.go +++ b/pkg/html/generated.go @@ -38,6 +38,12 @@ table td, table th { overflow: hidden; } +.namespace { + font-weight: bold; + font-size: large; + color: #375EAB; +} + .position_table { border: 0px; margin: 0px; |
