Commit 255a74d8cf

255a74d8cf7ae143317557d5be6eacea9b331dea

parent: eb453997f3

Verified · cmc ci/build: success

cmc <hello@cleberg.net> · 2026-08-30 04:30 UTC

dashboard: report the running build to admins

The startup warning writes the commit to the journal once and never
again, so the running build is invisible to anyone not reading logs at
the moment of a restart. dashboard now carries it.

Admin-only: the exact build a host runs narrows down which known issues
apply to it. omitempty drops the key for everyone else rather than
reporting an empty string.

Ref #28.

Layout: unified · split

e2e/dashboardbuild_test.go added +48
@@ -0,0 +1,48 @@
1package e2e
2
3import (
4 "strings"
5 "testing"
6)
7
8// The dashboard reports which build is serving, so the running commit is
9// visible without reading the journal. It is admin-only: the exact build a
10// host runs narrows down which known issues apply to it.
11func TestDashboardReportsTheServerBuild(t *testing.T) {
12 inst := startInstance(t)
13
14 adminKey := inst.newKey(t, "root")
15 userKey := inst.newKey(t, "plain")
16 inst.admin(t, "admin", "user", "create", "root", "--key", adminKey+".pub", "--admin")
17 inst.admin(t, "admin", "user", "create", "plain", "--key", userKey+".pub")
18
19 out, errOut, code := inst.ssh(t, adminKey, "", "dashboard", "--json")
20 if code != 0 {
21 t.Fatalf("admin dashboard: %s", errOut)
22 }
23 if !strings.Contains(out, `"server":{"commit":"`) {
24 t.Fatalf("admin dashboard did not report the build:\n%s", out)
25 }
26
27 // A non-admin gets the same dashboard without it. omitempty drops the key
28 // entirely rather than reporting an empty string.
29 out, errOut, code = inst.ssh(t, userKey, "", "dashboard", "--json")
30 if code != 0 {
31 t.Fatalf("user dashboard: %s", errOut)
32 }
33 if strings.Contains(out, `"server"`) {
34 t.Fatalf("a non-admin was told the server build:\n%s", out)
35 }
36 if !strings.Contains(out, `"review_queue"`) {
37 t.Fatalf("user dashboard is missing its usual contents:\n%s", out)
38 }
39
40 // Human output carries it too, for the operator who did not ask for JSON.
41 out, errOut, code = inst.ssh(t, adminKey, "", "dashboard")
42 if code != 0 {
43 t.Fatalf("admin dashboard (human): %s", errOut)
44 }
45 if !strings.Contains(out, "server:") || !strings.Contains(out, "build ") {
46 t.Fatalf("human dashboard did not report the build:\n%s", out)
47 }
48}
internal/control/dashboard.go +14
@@ -6,6 +6,7 @@ import (
6 "io" 6 "io"
7 "strconv" 7 "strconv"
8 8
9 "gitbay.org/gitbay/internal/buildinfo"
9 "gitbay.org/gitbay/internal/gitutil" 10 "gitbay.org/gitbay/internal/gitutil"
10 "gitbay.org/gitbay/internal/policy" 11 "gitbay.org/gitbay/internal/policy"
11 "gitbay.org/gitbay/internal/protocol" 12 "gitbay.org/gitbay/internal/protocol"
@@ -52,6 +53,12 @@ func runDashboard(c *Ctx, args []string) int {
52 CreatedAt string `json:"created_at"` 53 CreatedAt string `json:"created_at"`
53 FinishedAt string `json:"finished_at,omitempty"` 54 FinishedAt string `json:"finished_at,omitempty"`
54 } 55 }
56 // serverOut is admin-only. The exact build a host is running narrows down
57 // which known issues apply to it, so it is not everyone's to read; the
58 // person who needs it is the operator.
59 type serverOut struct {
60 Commit string `json:"commit"`
61 }
55 type out struct { 62 type out struct {
56 Reviews []dashboardItem `json:"review_queue"` 63 Reviews []dashboardItem `json:"review_queue"`
57 Assigned []dashboardItem `json:"assigned_issues"` 64 Assigned []dashboardItem `json:"assigned_issues"`
@@ -60,6 +67,7 @@ func runDashboard(c *Ctx, args []string) int {
60 Pinned []pinnedOut `json:"pinned"` 67 Pinned []pinnedOut `json:"pinned"`
61 Activity []feedOut `json:"recent_activity"` 68 Activity []feedOut `json:"recent_activity"`
62 Builds []buildOut `json:"builds"` 69 Builds []buildOut `json:"builds"`
70 Server *serverOut `json:"server,omitempty"`
63 } 71 }
64 d := out{ 72 d := out{
65 Reviews: []dashboardItem{}, Assigned: []dashboardItem{}, MRs: []dashboardItem{}, 73 Reviews: []dashboardItem{}, Assigned: []dashboardItem{}, MRs: []dashboardItem{},
@@ -127,6 +135,9 @@ func runDashboard(c *Ctx, args []string) int {
127 for _, b := range builds { 135 for _, b := range builds {
128 d.Builds = append(d.Builds, buildOut{b.RepoPath, b.Number, b.Job, b.Status, b.SHA, b.Ref, b.CreatedAt, b.FinishedAt}) 136 d.Builds = append(d.Builds, buildOut{b.RepoPath, b.Number, b.Job, b.Status, b.SHA, b.Ref, b.CreatedAt, b.FinishedAt})
129 } 137 }
138 if c.User.IsAdmin {
139 d.Server = &serverOut{Commit: buildinfo.String()}
140 }
130 141
131 return c.emit(d, func(w io.Writer) { 142 return c.emit(d, func(w io.Writer) {
132 fmt.Fprintln(w, "waiting on your review:") 143 fmt.Fprintln(w, "waiting on your review:")
@@ -153,6 +164,9 @@ func runDashboard(c *Ctx, args []string) int {
153 for _, b := range d.Builds { 164 for _, b := range d.Builds {
154 fmt.Fprintf(w, " %s\t%d\t%s\t%s\t%.10s\t%s\n", b.Repo, b.Number, b.Job, b.Status, b.SHA, b.Ref) 165 fmt.Fprintf(w, " %s\t%d\t%s\t%s\t%.10s\t%s\n", b.Repo, b.Number, b.Job, b.Status, b.SHA, b.Ref)
155 } 166 }
167 if d.Server != nil {
168 fmt.Fprintf(w, "server:\n build %s\n", d.Server.Commit)
169 }
156 }) 170 })
157} 171}
158 172