diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2023-02-21 14:08:30 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <wp32pw@gmail.com> | 2023-02-21 14:56:46 +0100 |
| commit | 47536162e02e8ba9aadd6e06184cb044f2eb069b (patch) | |
| tree | 48542cdb8ad4a674aca61d73a1521ab2f89c99f0 | |
| parent | f949448d9137ce181301419253c1bb224fcfea28 (diff) | |
dashboard: introduce a subsystem page
Display the list of open bugs, subsystem info and stats there.
Make the URL look nice, e.g. /upstream/s/fs
| -rw-r--r-- | dashboard/app/main.go | 85 | ||||
| -rw-r--r-- | dashboard/app/main_test.go | 26 | ||||
| -rw-r--r-- | dashboard/app/subsystem_page.html | 28 |
3 files changed, 118 insertions, 21 deletions
diff --git a/dashboard/app/main.go b/dashboard/app/main.go index fa31a7ab2..cbe3ca7df 100644 --- a/dashboard/app/main.go +++ b/dashboard/app/main.go @@ -22,6 +22,7 @@ import ( "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" "github.com/google/syzkaller/pkg/html" + "github.com/google/syzkaller/pkg/subsystem" "github.com/google/syzkaller/pkg/vcs" "golang.org/x/net/context" "golang.org/x/sync/errgroup" @@ -61,6 +62,7 @@ func initHTTPHandlers() { http.Handle("/"+ns+"/repos", handlerWrapper(handleRepos)) http.Handle("/"+ns+"/bug-stats", handlerWrapper(handleBugStats)) http.Handle("/"+ns+"/subsystems", handlerWrapper(handleSubsystemsList)) + http.Handle("/"+ns+"/s/", handlerWrapper(handleSubsystemPage)) } http.HandleFunc("/cron/cache_update", cacheUpdate) http.HandleFunc("/cron/deprecate_assets", handleDeprecateAssets) @@ -145,6 +147,12 @@ type uiRepo struct { Alias string } +type uiSubsystemPage struct { + Header *uiHeader + Info *uiSubsystem + Groups []*uiBugGroup +} + type uiSubsystemsPage struct { Header *uiHeader List []*uiSubsystem @@ -437,6 +445,40 @@ func handleInvalid(c context.Context, w http.ResponseWriter, r *http.Request) er }) } +func handleSubsystemPage(c context.Context, w http.ResponseWriter, r *http.Request) error { + hdr, err := commonHeader(c, r, w, "") + if err != nil { + return err + } + service := getSubsystemService(c, hdr.Namespace) + if service == nil { + return fmt.Errorf("the namespace does not have subsystems") + } + var subsystem *subsystem.Subsystem + if pos := strings.Index(r.URL.Path, "/s/"); pos != -1 { + subsystem = service.ByName(r.URL.Path[pos+3:]) + } + if subsystem == nil { + return fmt.Errorf("the subsystem is not found") + } + groups, err := fetchNamespaceBugs(c, accessLevel(c, r), + hdr.Namespace, &userBugFilter{ + Subsystem: subsystem.Name, + }) + if err != nil { + return err + } + cached, err := CacheGet(c, r, hdr.Namespace) + if err != nil { + return err + } + return serveTemplate(w, "subsystem_page.html", &uiSubsystemPage{ + Header: hdr, + Info: createUISubsystem(hdr.Namespace, subsystem, cached), + Groups: groups, + }) +} + func handleRepos(c context.Context, w http.ResponseWriter, r *http.Request) error { hdr, err := commonHeader(c, r, w, "") if err != nil { @@ -805,27 +847,7 @@ func handleSubsystemsList(c context.Context, w http.ResponseWriter, r *http.Requ } list := []*uiSubsystem{} for _, item := range service.List() { - stats := cached.Subsystems[item.Name] - list = append(list, &uiSubsystem{ - Name: item.Name, - Lists: strings.Join(item.Lists, ", "), - Maintainers: strings.Join(item.Maintainers, ", "), - Open: uiSubsystemStats{ - Count: stats.Open, - Link: html.AmendURL("/"+hdr.Namespace, - "subsystem", item.Name), - }, - Fixed: uiSubsystemStats{ - Count: stats.Fixed, - Link: html.AmendURL("/"+hdr.Namespace+"/fixed", - "subsystem", item.Name), - }, - Invalid: uiSubsystemStats{ - Count: stats.Invalid, - Link: html.AmendURL("/"+hdr.Namespace+"/invalid", - "subsystem", item.Name), - }, - }) + list = append(list, createUISubsystem(hdr.Namespace, item, cached)) } sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name }) return serveTemplate(w, "subsystems.html", &uiSubsystemsPage{ @@ -834,6 +856,27 @@ func handleSubsystemsList(c context.Context, w http.ResponseWriter, r *http.Requ }) } +func createUISubsystem(ns string, item *subsystem.Subsystem, cached *Cached) *uiSubsystem { + stats := cached.Subsystems[item.Name] + return &uiSubsystem{ + Name: item.Name, + Lists: strings.Join(item.Lists, ", "), + Maintainers: strings.Join(item.Maintainers, ", "), + Open: uiSubsystemStats{ + Count: stats.Open, + Link: html.AmendURL("/"+ns, "subsystem", item.Name), + }, + Fixed: uiSubsystemStats{ + Count: stats.Fixed, + Link: html.AmendURL("/"+ns+"/fixed", "subsystem", item.Name), + }, + Invalid: uiSubsystemStats{ + Count: stats.Invalid, + Link: html.AmendURL("/"+ns+"/invalid", "subsystem", item.Name), + }, + } +} + // handleText serves plain text blobs (crash logs, reports, reproducers, etc). func handleTextImpl(c context.Context, w http.ResponseWriter, r *http.Request, tag string) error { var id int64 diff --git a/dashboard/app/main_test.go b/dashboard/app/main_test.go index e69dd0275..8f732ac3d 100644 --- a/dashboard/app/main_test.go +++ b/dashboard/app/main_test.go @@ -210,3 +210,29 @@ func TestSubsystemsList(t *testing.T) { assert.Contains(t, string(reply), "subsystemA") assert.Contains(t, string(reply), "subsystemB") } + +func TestSubsystemPage(t *testing.T) { + c := NewCtx(t) + defer c.Close() + + client := c.client + build := testBuild(1) + client.UploadBuild(build) + + crash1 := testCrash(build, 1) + crash1.Title = "test crash title" + crash1.GuiltyFiles = []string{"a.c"} + client.ReportCrash(crash1) + client.pollBug() + + crash2 := testCrash(build, 2) + crash2.GuiltyFiles = []string{"b.c"} + client.ReportCrash(crash2) + crash2.Title = "crash that must not be present" + client.updateBug(client.pollBug().ID, dashapi.BugStatusInvalid, "") + + reply, err := c.AuthGET(AccessAdmin, "/test1/s/subsystemA") + c.expectOK(err) + assert.Contains(t, string(reply), crash1.Title) + assert.NotContains(t, string(reply), crash2.Title) +} diff --git a/dashboard/app/subsystem_page.html b/dashboard/app/subsystem_page.html new file mode 100644 index 000000000..ea108b596 --- /dev/null +++ b/dashboard/app/subsystem_page.html @@ -0,0 +1,28 @@ +{{/* +Copyright 2023 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. + +The list of polled trees. +*/}} + +<!doctype html> +<html> +<head> + {{template "head" .Header}} + <title>syzbot: {{.Info.Name}} subsystem</title> +</head> +<body> + {{template "header" .Header}} + <h2>{{.Info.Name}} subsystem</h2><br> + {{if .Info.Lists}} + <b>List(s):</b> {{.Info.Lists}}<br> + {{end}} + {{if .Info.Maintainers}} + <b>Maintainer(s):</b> {{.Info.Maintainers}}<br> + {{end}} + <b>Fixed bugs:</b> {{link .Info.Fixed.Link (printf "%d" .Info.Fixed.Count)}}<br> + {{range $group := $.Groups}} + {{template "bug_list" $group}} + {{end}} +</body> +</html> |
