Commit b8cd3fd9d0

b8cd3fd9d0a562abf2b50f21649edac1dffe8905

parent: b96e074d36

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-24 01:45 UTC

control: account, org, admin, notification, snippet, feed and dashboard lists as tables

Ref #254
internal/control/admin.go +33 −15
@@ -128,13 +128,15 @@ func runAdminUserList(c *Ctx, args []string) int {
128128 ds = append(ds, adminUserRow(u))
129129 }
130130 return c.emitPage(p, ds, next, func(w io.Writer) {
131 tb := c.table(w, "USERNAME", "STATE", "ADMIN", "CREATED", "LAST SEEN")
131132 for _, d := range ds {
132133 mark := ""
133134 if d.Admin {
134135 mark = "admin"
135136 }
136 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", d.Username, d.State, mark, d.CreatedAt, d.LastSeen)
137 tb.row(cRef(d.Username), cState(d.State), cText(mark), cAge(d.CreatedAt), cAge(d.LastSeen))
137138 }
139 tb.flush()
138140 })
139141}
140142
@@ -257,37 +259,47 @@ func runAdminUserShow(c *Ctx, args []string) int {
257259 }
258260 fmt.Fprintf(w, "repos\t%d\nweb sessions\t%d\n", d.Repos, d.WebSessions)
259261 fmt.Fprintln(w, "keys:")
262 tk := c.table(w, "FINGERPRINT", "ALGO", "SCOPE", "LAST USED")
260263 for _, k := range d.Keys {
261 fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", k.Fingerprint, k.Algo, k.Scope, k.LastUsedAt)
264 tk.row(cRef(" "+k.Fingerprint), cText(k.Algo), cState(k.Scope), cAge(k.LastUsedAt))
262265 }
266 tk.flush()
263267 fmt.Fprintln(w, "emails:")
268 te := c.table(w, "ADDRESS", "STATE")
264269 for _, e := range d.Emails {
265270 state := "unverified"
266271 if e.Verified {
267272 state = "verified by " + e.VerifiedBy
268273 }
269 mark := ""
274 cells := []cell{cRef(" " + e.Address), cState(state)}
270275 if e.Primary {
271 mark = "\tprimary"
276 cells = append(cells, cText("primary"))
272277 }
273 fmt.Fprintf(w, " %s\t%s%s\n", e.Address, state, mark)
278 te.row(cells...)
274279 }
280 te.flush()
275281 fmt.Fprintln(w, "pgp keys:")
282 tp := c.table(w, "FINGERPRINT")
276283 for _, k := range d.PGPKeys {
277 fmt.Fprintf(w, " %s\n", k.Fingerprint)
284 tp.row(cRef(" " + k.Fingerprint))
278285 }
286 tp.flush()
279287 fmt.Fprintln(w, "orgs:")
288 to := c.table(w, "ORG", "ROLE")
280289 for _, o := range d.Orgs {
281 fmt.Fprintf(w, " %s\t%s\n", o.Org, o.Role)
290 to.row(cRef(" "+o.Org), cState(o.Role))
282291 }
292 to.flush()
283293 fmt.Fprintln(w, "api tokens:")
294 tt := c.table(w, "NAME", "SCOPE", "LAST USED")
284295 for _, t := range d.APITokens {
285296 used := ""
286297 if t.LastUsedAt != nil {
287 used = t.LastUsedAt.UTC().Format(time.RFC3339)
298 used = t.LastUsedAt.UTC().Format(time.RFC3339Nano)
288299 }
289 fmt.Fprintf(w, " %s\t%s\t%s\n", t.Name, t.Scope, strings.TrimSpace(used))
300 tt.row(cRef(" "+t.Name), cState(t.Scope), cAge(used))
290301 }
302 tt.flush()
291303 })
292304}
293305
@@ -382,13 +394,15 @@ func runAdminRepoList(c *Ctx, args []string) int {
382394 ds = append(ds, out{r.Path, r.Visibility, r.Archived, r.CreatedAt, r.LastPush, size})
383395 }
384396 return c.emitPage(p, ds, next, func(w io.Writer) {
397 tb := c.table(w, "PATH", "VISIBILITY", "BYTES", "CREATED", "LAST PUSH")
385398 for _, d := range ds {
386 mark := ""
399 cells := []cell{cRef(d.Path), cState(d.Visibility), cNum(d.Bytes), cAge(d.CreatedAt), cAge(d.LastPush)}
387400 if d.Archived {
388 mark = "\t[archived]"
401 cells = append(cells, cText("[archived]"))
389402 }
390 fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s%s\n", d.Path, d.Visibility, d.Bytes, d.CreatedAt, d.LastPush, mark)
403 tb.row(cells...)
391404 }
405 tb.flush()
392406 })
393407}
394408
@@ -517,6 +531,7 @@ func runAdminRunners(c *Ctx, args []string) int {
517531 return c.emit(d, func(w io.Writer) {
518532 fmt.Fprintf(w, "queue: %d pending; last 24h: %d claimed, wait avg %ds max %ds, %d reaped\n",
519533 queue.Pending, queue.Claimed24h, queue.ClaimWaitAvgS, queue.ClaimWaitMaxS, queue.Reaped24h)
534 tb := c.table(w, "USER", "FINGERPRINT", "LAST SEEN", "SCOPE", "HELD")
520535 for _, r := range runners {
521536 scope := r.Scope
522537 if scope == "" {
@@ -526,8 +541,9 @@ func runAdminRunners(c *Ctx, args []string) int {
526541 if r.BuildNumber != 0 {
527542 held = fmt.Sprintf("%s #%d %s since %s", r.BuildRepo, r.BuildNumber, r.BuildJob, r.StartedAt)
528543 }
529 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", r.Username, r.Fingerprint, r.LastSeen, scope, held)
544 tb.row(cText(r.Username), cRef(r.Fingerprint), cAge(r.LastSeen), cText(scope), cText(held))
530545 }
546 tb.flush()
531547 })
532548}
533549
@@ -609,12 +625,14 @@ func runAdminMRPrune(c *Ctx, args []string) int {
609625 return c.fail(protocol.ExitFailure, "%v; the head refs are deleted but the objects are not yet pruned; re-run the same command", err)
610626 }
611627 return c.emit(rows, func(w io.Writer) {
628 tb := c.table(w, "!", "HEAD")
612629 for _, r := range rows {
613630 if r.Head == "" {
614 fmt.Fprintf(w, "!%d\talready gone\n", r.Number)
631 tb.row(cRef(fmt.Sprintf("!%d", r.Number)), cText("already gone"))
615632 continue
616633 }
617 fmt.Fprintf(w, "!%d\t%s\n", r.Number, r.Head)
634 tb.row(cRef(fmt.Sprintf("!%d", r.Number)), cRef(r.Head))
618635 }
636 tb.flush()
619637 })
620638}
internal/control/audit.go +3 −2
@@ -1,7 +1,6 @@
11package control
22
33import (
4 "fmt"
54 "io"
65 "strconv"
76 "strings"
@@ -47,13 +46,15 @@ func runAudit(c *Ctx, args []string) int {
4746 return c.fail(protocol.ExitFailure, "%v", err)
4847 }
4948 return c.emit(entries, func(w io.Writer) {
49 tb := c.table(w, "WHEN", "ACTOR", "ACTION", "DATA")
5050 for _, e := range entries {
5151 actor := e.Actor
5252 if actor == "" {
5353 actor = "-"
5454 }
55 fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", e.CreatedAt, actor, e.Action, e.Data)
55 tb.row(cAge(e.CreatedAt), cText(actor), cText(e.Action), cFlex(e.Data))
5656 }
57 tb.flush()
5758 })
5859}
5960
internal/control/dashboard.go +93 −45
@@ -5,6 +5,7 @@ import (
55 "fmt"
66 "io"
77 "strconv"
8 "strings"
89
910 "gitbay.org/gitbay/internal/buildinfo"
1011 "gitbay.org/gitbay/internal/gitutil"
@@ -163,90 +164,135 @@ func runDashboard(c *Ctx, args []string) int {
163164 }
164165
165166 return c.emit(d, func(w io.Writer) {
167 section := func(title string, header []string, rows [][]cell) {
168 if c.Term.Cols > 0 {
169 fmt.Fprintln(w, c.Term.paint(sgrBold, title))
170 } else {
171 fmt.Fprintln(w, title)
172 }
173 if len(rows) == 0 {
174 fmt.Fprintln(w, " none")
175 return
176 }
177 if c.Term.Cols == 0 {
178 for _, r := range rows {
179 parts := make([]string, len(r))
180 for i, cl := range r {
181 parts[i] = cl.s
182 if cl.kind == kindAge {
183 parts[i] = stamp(cl.s)
184 }
185 }
186 fmt.Fprintf(w, " %s\n", strings.Join(parts, "\t"))
187 }
188 return
189 }
190 tb := c.table(w, header...)
191 for _, r := range rows {
192 tb.row(r...)
193 }
194 tb.flush()
195 }
196 itemRows := func(items []DashboardItem, marker string) [][]cell {
197 rows := make([][]cell, len(items))
198 for i, item := range items {
199 rows[i] = []cell{cRef(fmt.Sprintf("%s%s%d", item.Repo, marker, item.Number)), cFlex(item.Title), cText(item.Author)}
200 }
201 return rows
202 }
203
166204 if d.Unread > 0 {
167205 fmt.Fprintf(w, "unread notifications: %d\n", d.Unread)
168206 }
169 fmt.Fprintln(w, "waiting on your review:")
170 printDashboardItems(w, d.Reviews, "!")
171 fmt.Fprintln(w, "assigned to you:")
172 printDashboardItems(w, d.Assigned, "#")
173 fmt.Fprintln(w, "open merge requests:")
174 printDashboardItems(w, d.MRs, "!")
175 fmt.Fprintln(w, "open issues:")
176 printDashboardItems(w, d.Issues, "#")
177 fmt.Fprintln(w, "pinned:")
178 if len(d.Pinned) == 0 {
179 fmt.Fprintln(w, " none")
180 }
181 for _, p := range d.Pinned {
182 mark := ""
207 itemHeader := []string{"REF", "TITLE", "AUTHOR"}
208 section("waiting on your review:", itemHeader, itemRows(d.Reviews, "!"))
209 section("assigned to you:", itemHeader, itemRows(d.Assigned, "#"))
210 section("open merge requests:", itemHeader, itemRows(d.MRs, "!"))
211 section("open issues:", itemHeader, itemRows(d.Issues, "#"))
212
213 pinnedRows := make([][]cell, len(d.Pinned))
214 for i, p := range d.Pinned {
215 cells := []cell{cRef(p.Path), cState(p.Visibility), cFlex(p.Description)}
183216 if p.Archived {
184 mark = "\t[archived]"
217 cells = append(cells, cText("[archived]"))
185218 }
186 fmt.Fprintf(w, " %s\t%s\t%s%s\n", p.Path, p.Visibility, p.Description, mark)
219 pinnedRows[i] = cells
187220 }
188 fmt.Fprintln(w, "recent activity:")
189 if len(d.Activity) == 0 {
190 fmt.Fprintln(w, " none")
191 }
192 for _, e := range d.Activity {
193 fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", e.CreatedAt, e.Actor, e.Kind, e.Repo, string(e.Data))
194 }
195 fmt.Fprintln(w, "builds:")
196 if len(d.Builds) == 0 {
197 fmt.Fprintln(w, " none")
221 section("pinned:", []string{"PATH", "VISIBILITY", "DESCRIPTION"}, pinnedRows)
222
223 activityRows := make([][]cell, len(d.Activity))
224 for i, e := range d.Activity {
225 activityRows[i] = []cell{cAge(e.CreatedAt), cText(e.Actor), cText(e.Kind), cRef(e.Repo), cFlex(string(e.Data))}
198226 }
199 for _, b := range d.Builds {
200 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)
227 section("recent activity:", []string{"WHEN", "ACTOR", "KIND", "REPO", "DATA"}, activityRows)
228
229 buildRows := make([][]cell, len(d.Builds))
230 for i, b := range d.Builds {
231 buildRows[i] = []cell{cRef(b.Repo), cNum(b.Number), cText(b.Job), cState(b.Status), cRef(fmt.Sprintf("%.10s", b.SHA)), cText(b.Ref)}
201232 }
233 section("builds:", []string{"REPO", "#", "JOB", "STATUS", "SHA", "REF"}, buildRows)
234
202235 if d.Server != nil {
203236 fmt.Fprintf(w, "server:\n build %s\n", d.Server.Commit)
204237 }
205238 if q := d.Queues; q != nil {
206239 fmt.Fprintln(w, "queues:")
240
207241 fmt.Fprintf(w, " webhooks\tpending %d\tretrying %d\tfailed %d\n", q.Webhooks.Pending, q.Webhooks.Retrying, q.Webhooks.Failed)
242 twh := c.table(w, "REPO", "URL", "ATTEMPTS", "ERROR")
208243 for _, it := range q.Webhooks.Items {
209 fmt.Fprintf(w, " %s\t%s\tattempts %d\t%s\n", it.Repo, it.URL, it.Attempts, it.LastError)
244 twh.row(cRef(" "+it.Repo), cText(it.URL), cText(fmt.Sprintf("attempts %d", it.Attempts)), cText(it.LastError))
210245 }
246 twh.flush()
247
211248 fmt.Fprintf(w, " mail\tpending %d\tretrying %d\tfailed %d\n", q.Mail.Pending, q.Mail.Retrying, q.Mail.Failed)
249 tma := c.table(w, "RECIPIENT", "SUBJECT", "ATTEMPTS", "ERROR")
212250 for _, it := range q.Mail.Items {
213 fmt.Fprintf(w, " %s\t%s\tattempts %d\t%s\n", it.Recipient, it.Subject, it.Attempts, it.LastError)
251 tma.row(cRef(" "+it.Recipient), cText(it.Subject), cText(fmt.Sprintf("attempts %d", it.Attempts)), cText(it.LastError))
214252 }
253 tma.flush()
254
215255 // The device id, not the token: a token is never echoed.
216256 fmt.Fprintf(w, " push\tpending %d\tretrying %d\tfailed %d\n", q.Push.Pending, q.Push.Retrying, q.Push.Failed)
257 tpu := c.table(w, "DEVICE", "TITLE", "ATTEMPTS", "ERROR")
217258 for _, it := range q.Push.Items {
218 fmt.Fprintf(w, " device %d\t%s\tattempts %d\t%s\n", it.DeviceID, it.Title, it.Attempts, it.LastError)
259 tpu.row(cRef(fmt.Sprintf(" device %d", it.DeviceID)), cText(it.Title), cText(fmt.Sprintf("attempts %d", it.Attempts)), cText(it.LastError))
219260 }
261 tpu.flush()
262
220263 fmt.Fprintf(w, " mirrors\tdirty %d\terrors %d\n", q.Mirrors.Dirty, q.Mirrors.Errors)
264 tmi := c.table(w, "REPO", "DIRECTION", "URL", "ERROR")
221265 for _, it := range q.Mirrors.Items {
222 fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", it.Repo, it.Direction, it.URL, it.LastError)
266 tmi.row(cRef(" "+it.Repo), cText(it.Direction), cText(it.URL), cText(it.LastError))
223267 }
268 tmi.flush()
269
224270 fmt.Fprintf(w, " builds\tpending %d\trunning %d\n", q.Builds.Pending, q.Builds.Running)
271 tbq := c.table(w, "REPO", "#", "JOB", "STATUS")
225272 for _, it := range q.Builds.Items {
226273 since := it.StartedAt
227274 if it.Status == "pending" {
228275 since = it.CreatedAt
229276 }
230 fmt.Fprintf(w, " %s\t%d\t%s\t%s since %s\n", it.Repo, it.Number, it.Job, it.Status, since)
277 if c.Term.Cols == 0 {
278 since = stamp(since)
279 } else {
280 since = relAge(since, termNow())
281 }
282 tbq.row(cRef(" "+it.Repo), cNum(it.Number), cText(it.Job), cText(fmt.Sprintf("%s since %s", it.Status, since)))
231283 }
284 tbq.flush()
285
232286 fmt.Fprintf(w, " deps\terrors %d\n", q.Deps.Errors)
287 tde := c.table(w, "REPO", "ERROR")
233288 for _, it := range q.Deps.Items {
234 fmt.Fprintf(w, " %s\t%s\n", it.Repo, it.LastError)
289 tde.row(cRef(" "+it.Repo), cText(it.LastError))
235290 }
291 tde.flush()
236292 }
237293 })
238294}
239295
240func printDashboardItems(w io.Writer, items []DashboardItem, marker string) {
241 if len(items) == 0 {
242 fmt.Fprintln(w, " none")
243 return
244 }
245 for _, item := range items {
246 fmt.Fprintf(w, " %s%s%d\t%s\t%s\n", item.Repo, marker, item.Number, item.Title, item.Author)
247 }
248}
249
250296// feedDefaultLimit caps a bare `feed` call; pagination reaches further
251297// back.
252298const feedDefaultLimit = 50
@@ -292,8 +338,10 @@ func runFeed(c *Ctx, args []string) int {
292338 })
293339 ds := feedOutputs(events)
294340 return c.emitPage(p, ds, next, func(w io.Writer) {
341 tb := c.table(w, "WHEN", "ACTOR", "KIND", "REPO", "DATA")
295342 for _, d := range ds {
296 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", d.CreatedAt, d.Actor, d.Kind, d.Repo, string(d.Data))
343 tb.row(cAge(d.CreatedAt), cText(d.Actor), cText(d.Kind), cRef(d.Repo), cFlex(string(d.Data)))
297344 }
345 tb.flush()
298346 })
299347}
internal/control/deploykey.go +3 −1
@@ -98,9 +98,11 @@ func runDeployKeyList(c *Ctx, args []string) int {
9898 ds = append(ds, out{k.Fingerprint, k.Algo, mode, k.Label})
9999 }
100100 return c.emit(ds, func(w io.Writer) {
101 tb := c.table(w, "FINGERPRINT", "ALGO", "MODE", "LABEL")
101102 for _, d := range ds {
102 fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", d.Fingerprint, d.Algo, d.Mode, d.Label)
103 tb.row(cRef(d.Fingerprint), cText(d.Algo), cState(d.Mode), cText(d.Label))
103104 }
105 tb.flush()
104106 })
105107}
106108
internal/control/identity.go +3 −1
@@ -83,9 +83,11 @@ func runKeysList(c *Ctx, args []string) int {
8383 ds = append(ds, out{k.Fingerprint, k.Algo, k.Scope, k.Label})
8484 }
8585 return c.emit(ds, func(w io.Writer) {
86 tb := c.table(w, "FINGERPRINT", "ALGO", "SCOPE", "LABEL")
8687 for _, d := range ds {
87 fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", d.Fingerprint, d.Algo, d.Scope, d.Label)
88 tb.row(cRef(d.Fingerprint), cText(d.Algo), cState(d.Scope), cText(d.Label))
8889 }
90 tb.flush()
8991 })
9092}
9193
internal/control/notifications.go +7 −3
@@ -303,9 +303,11 @@ func runNotificationsDeviceList(c *Ctx, args []string) int {
303303 Token: ShortToken(d.Token), Added: d.CreatedAt})
304304 }
305305 return c.emit(rows, func(w io.Writer) {
306 tb := c.table(w, "ID", "LABEL", "TOKEN", "ADDED")
306307 for _, r := range rows {
307 fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", r.ID, r.Label, r.Token, r.Added)
308 tb.row(cRef(fmt.Sprintf("%d", r.ID)), cText(r.Label), cText(r.Token), cAge(r.Added))
308309 }
310 tb.flush()
309311 })
310312}
311313
@@ -381,14 +383,16 @@ func runNotificationsList(c *Ctx, args []string) int {
381383 ds = append(ds, out{n.ID, n.RepoPath, n.Kind, n.Actor, n.Summary, n.Path, n.CreatedAt, n.ReadAt})
382384 }
383385 return c.emitPage(p, ds, next, func(w io.Writer) {
386 tb := c.table(w, "ID", "WHEN", "REPO", "EVENT", "PATH")
384387 for _, d := range ds {
385388 mark := "*"
386389 if d.ReadAt != "" {
387390 mark = " "
388391 }
389 fmt.Fprintf(w, "%s %d\t%s\t%s\t%s %s\t%s\n",
390 mark, d.ID, d.CreatedAt, d.Repo, d.Actor, d.Summary, d.Path)
392 tb.row(cRef(fmt.Sprintf("%s %d", mark, d.ID)), cAge(d.CreatedAt), cRef(d.Repo),
393 cFlex(fmt.Sprintf("%s %s", d.Actor, d.Summary)), cText(d.Path))
391394 }
395 tb.flush()
392396 })
393397}
394398
internal/control/org.go +6 −2
@@ -87,9 +87,11 @@ func runOrgList(c *Ctx, args []string) int {
8787 ds = append(ds, out{o.Username, o.Role})
8888 }
8989 return c.emit(ds, func(w io.Writer) {
90 tb := c.table(w, "ORG", "ROLE")
9091 for _, d := range ds {
91 fmt.Fprintf(w, "%s\t%s\n", d.Org, d.Role)
92 tb.row(cRef(d.Org), cState(d.Role))
9293 }
94 tb.flush()
9395 })
9496}
9597
@@ -122,9 +124,11 @@ func runOrgShow(c *Ctx, args []string) int {
122124 }{org.Name, ms}
123125 return c.emit(d, func(w io.Writer) {
124126 fmt.Fprintf(w, "%s\n", d.Org)
127 tb := c.table(w, "USER", "ROLE")
125128 for _, m := range ms {
126 fmt.Fprintf(w, " %s\t%s\n", m.User, m.Role)
129 tb.row(cRef(" "+m.User), cState(m.Role))
127130 }
131 tb.flush()
128132 })
129133}
130134
internal/control/register.go +5 −2
@@ -62,16 +62,19 @@ func runEmailList(c *Ctx, args []string) int {
6262 ds = append(ds, out{e.Address, e.Verified, e.VerifiedBy, e.Primary})
6363 }
6464 return c.emit(ds, func(w io.Writer) {
65 tb := c.table(w, "ADDRESS", "STATE")
6566 for _, d := range ds {
6667 state := "unverified"
6768 if d.Verified {
6869 state = "verified"
6970 }
71 cells := []cell{cRef(d.Address), cState(state)}
7072 if d.Primary {
71 state += "\tprimary"
73 cells = append(cells, cText("primary"))
7274 }
73 fmt.Fprintf(w, "%s\t%s\n", d.Address, state)
75 tb.row(cells...)
7476 }
77 tb.flush()
7578 })
7679}
7780
internal/control/snippet.go +6 −2
@@ -202,9 +202,11 @@ func runSnippetShow(c *Ctx, args []string) int {
202202 fmt.Fprintf(w, "%s\n", sn.Description)
203203 }
204204 fmt.Fprintf(w, "%s\nupdated %s\n", snippetURL(c, sn), sn.UpdatedAt)
205 tb := c.table(w, "NAME", "SIZE")
205206 for _, f := range files {
206 fmt.Fprintf(w, " %s\t%d bytes\n", f.Name, f.Size)
207 tb.row(cRef(" "+f.Name), cText(fmt.Sprintf("%d bytes", f.Size)))
207208 }
209 tb.flush()
208210 })
209211}
210212
@@ -238,6 +240,7 @@ func runSnippetList(c *Ctx, args []string) int {
238240 items = append(items, snippetOut(c, sn))
239241 }
240242 return c.emitPage(p, items, next, func(w io.Writer) {
243 tb := c.table(w, "ID", "VISIBILITY", "FILES", "DESCRIPTION")
241244 for _, sn := range rows {
242245 names := ""
243246 for i, f := range sn.Files {
@@ -246,8 +249,9 @@ func runSnippetList(c *Ctx, args []string) int {
246249 }
247250 names += f.Name
248251 }
249 fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", sn.PublicID, sn.Visibility, names, sn.Description)
252 tb.row(cRef(sn.PublicID), cState(sn.Visibility), cText(names), cFlex(sn.Description))
250253 }
254 tb.flush()
251255 })
252256}
253257
internal/control/teams.go +6 −2
@@ -143,9 +143,11 @@ func runTeamList(c *Ctx, args []string) int {
143143 names = append(names, t.Name)
144144 }
145145 return c.emit(names, func(w io.Writer) {
146 tb := c.table(w, "TEAM")
146147 for _, n := range names {
147 fmt.Fprintln(w, n)
148 tb.row(cRef(n))
148149 }
150 tb.flush()
149151 })
150152}
151153
@@ -176,9 +178,11 @@ func runTeamShow(c *Ctx, args []string) int {
176178 }{team.Name, members, grants}
177179 return c.emit(d, func(w io.Writer) {
178180 fmt.Fprintf(w, "%s/%s\nmembers: %s\n", org.Name, team.Name, strings.Join(members, ", "))
181 tb := c.table(w, "REPO", "ROLE")
179182 for _, g := range grants {
180 fmt.Fprintf(w, "%s\t%s\n", g.RepoPath, g.Role)
183 tb.row(cRef(g.RepoPath), cState(g.Role))
181184 }
185 tb.flush()
182186 })
183187}
184188
internal/control/token.go +9 −2
@@ -96,13 +96,20 @@ func runTokenList(c *Ctx, args []string) int {
9696 ds = append(ds, out{t.Name, t.Scope, t.CreatedAt, t.ExpiresAt, t.LastUsedAt})
9797 }
9898 return c.emit(ds, func(w io.Writer) {
99 tb := c.table(w, "NAME", "SCOPE", "EXPIRES")
99100 for _, d := range ds {
100101 exp := "never expires"
101102 if d.ExpiresAt != nil {
102 exp = "expires " + d.ExpiresAt.UTC().Format(time.RFC3339)
103 ts := d.ExpiresAt.UTC().Format(time.RFC3339Nano)
104 if c.Term.Cols == 0 {
105 exp = "expires " + stamp(ts)
106 } else {
107 exp = "expires " + relAge(ts, termNow())
108 }
103109 }
104 fmt.Fprintf(w, "%s\t%s\t%s\n", d.Name, d.Scope, exp)
110 tb.row(cRef(d.Name), cState(d.Scope), cText(exp))
105111 }
112 tb.flush()
106113 })
107114}
108115
internal/control/web.go +9 −1
@@ -33,9 +33,17 @@ func runWebSessionsList(c *Ctx, args []string) int {
3333 return c.fail(protocol.ExitFailure, "%v", err)
3434 }
3535 return c.emit(sessions, func(w io.Writer) {
36 tb := c.table(w, "ID", "SINCE", "UNTIL")
3637 for _, s := range sessions {
37 fmt.Fprintf(w, "%s\tsince %s\tuntil %s\n", s.ID, s.CreatedAt, s.ExpiresAt)
38 since, until := s.CreatedAt, s.ExpiresAt
39 if c.Term.Cols == 0 {
40 since, until = stamp(since), stamp(until)
41 } else {
42 since, until = relAge(since, termNow()), relAge(until, termNow())
43 }
44 tb.row(cRef(s.ID), cText("since "+since), cText("until "+until))
3845 }
46 tb.flush()
3947 })
4048}
4149