| @@ -159,7 +159,7 @@ func runAdminUserList(c *Ctx, args []string) int { |
| 159 | for _, u := range users { |
159 | for _, u := range users { |
| 160 | ds = append(ds, adminUserRow(u)) |
160 | ds = append(ds, adminUserRow(u)) |
| 161 | } |
161 | } |
| 162 | return c.emitPage(p, ds, next, func(w io.Writer) { |
162 | return c.emitPageView(p, ds, next, func(w io.Writer) { |
| 163 | tb := c.table(w, "USERNAME", "STATE", "ADMIN", "CREATED", "LAST SEEN") |
163 | tb := c.table(w, "USERNAME", "STATE", "ADMIN", "CREATED", "LAST SEEN") |
| 164 | for _, d := range ds { |
164 | for _, d := range ds { |
| 165 | mark := "" |
165 | mark := "" |
| @@ -169,6 +169,25 @@ func runAdminUserList(c *Ctx, args []string) int { |
| 169 | tb.row(cRef(d.Username), cState(d.State), cText(mark), cAge(d.CreatedAt), cAge(d.LastSeen)) |
169 | tb.row(cRef(d.Username), cState(d.State), cText(mark), cAge(d.CreatedAt), cAge(d.LastSeen)) |
| 170 | } |
170 | } |
| 171 | tb.flush() |
171 | tb.flush() |
| |
172 | }, func() screen { |
| |
173 | rows := make([]row, len(ds)) |
| |
174 | for i, d := range ds { |
| |
175 | lead := cGlyph("") |
| |
176 | if d.State == "pending" { |
| |
177 | lead = cYou() |
| |
178 | } |
| |
179 | admin, seen := "", "" |
| |
180 | if d.Admin { |
| |
181 | admin = "admin" |
| |
182 | } |
| |
183 | if d.LastSeen != "" { |
| |
184 | seen = "seen " + relAge(d.LastSeen, termNow()) |
| |
185 | } |
| |
186 | rows[i] = rowOf(cRef(d.Username), lead, cState(d.State), cMeta(admin, seen)) |
| |
187 | } |
| |
188 | return listScreen("Accounts", rows, |
| |
189 | action{"Filter", []string{"admin", "user", "list", "--state", "pending"}}, |
| |
190 | ) |
| 172 | }) |
191 | }) |
| 173 | } |
192 | } |
| 174 | |
193 | |
| @@ -280,7 +299,7 @@ func runAdminUserShow(c *Ctx, args []string) int { |
| 280 | return c.fail(protocol.ExitFailure, "%v", err) |
299 | return c.fail(protocol.ExitFailure, "%v", err) |
| 281 | } |
300 | } |
| 282 | |
301 | |
| 283 | return c.emit(d, func(w io.Writer) { |
302 | return c.emitView(d, func(w io.Writer) { |
| 284 | admin := "" |
303 | admin := "" |
| 285 | if d.Admin { |
304 | if d.Admin { |
| 286 | admin = "yes" |
305 | admin = "yes" |
| @@ -346,6 +365,66 @@ func runAdminUserShow(c *Ctx, args []string) int { |
| 346 | } |
365 | } |
| 347 | tt.flush() |
366 | tt.flush() |
| 348 | } |
367 | } |
| |
368 | }, func() screen { |
| |
369 | s := screen{} |
| |
370 | user := []cell{cRef(d.Username), cState(d.State)} |
| |
371 | if d.Admin { |
| |
372 | user = append(user, cMeta("admin")) |
| |
373 | } |
| |
374 | seen := "never seen" |
| |
375 | if d.LastSeen != "" { |
| |
376 | seen = "seen " + relAge(d.LastSeen, termNow()) |
| |
377 | } |
| |
378 | s.fields = []field{ |
| |
379 | {"User", user}, |
| |
380 | {"Seen", []cell{cText(seen), cMeta("created " + relAge(d.CreatedAt, termNow()))}}, |
| |
381 | {"Repos", []cell{cText(strconv.FormatInt(d.Repos, 10))}}, |
| |
382 | {"Sessions", []cell{cText(strconv.FormatInt(d.WebSessions, 10))}}, |
| |
383 | } |
| |
384 | keys := section{title: "Keys", n: len(d.Keys)} |
| |
385 | for _, k := range d.Keys { |
| |
386 | keys.rows = append(keys.rows, rowOf(cRef(k.Fingerprint), cState(k.Scope), cMeta(k.Algo, "used "+relAge(k.LastUsedAt, termNow())))) |
| |
387 | } |
| |
388 | emails := section{title: "Emails", n: len(d.Emails)} |
| |
389 | for _, e := range d.Emails { |
| |
390 | state, primary := "unverified", "" |
| |
391 | if e.Verified { |
| |
392 | state = "verified" |
| |
393 | } |
| |
394 | if e.Primary { |
| |
395 | primary = "primary" |
| |
396 | } |
| |
397 | emails.rows = append(emails.rows, rowOf(cRef(e.Address), cState(state), cMeta(primary, e.VerifiedBy))) |
| |
398 | } |
| |
399 | pgp := section{title: "PGP keys", n: len(d.PGPKeys)} |
| |
400 | for _, k := range d.PGPKeys { |
| |
401 | pgp.rows = append(pgp.rows, rowOf(cRef(k.Fingerprint))) |
| |
402 | } |
| |
403 | orgs := section{title: "Orgs", n: len(d.Orgs)} |
| |
404 | for _, o := range d.Orgs { |
| |
405 | orgs.rows = append(orgs.rows, rowOf(cLink(o.Org, c.siteURL(o.Org)), cState(o.Role))) |
| |
406 | } |
| |
407 | tokens := section{title: "API tokens", n: len(d.APITokens)} |
| |
408 | for _, tk := range d.APITokens { |
| |
409 | used := "" |
| |
410 | if tk.LastUsedAt != nil { |
| |
411 | used = "used " + relAge(tk.LastUsedAt.UTC().Format(time.RFC3339Nano), termNow()) |
| |
412 | } |
| |
413 | tokens.rows = append(tokens.rows, rowOf(cRef(tk.Name), cState(tk.Scope), cMeta(used))) |
| |
414 | } |
| |
415 | s.sections = []section{keys, emails, pgp, orgs, tokens} |
| |
416 | role, state := "promote", "disable" |
| |
417 | if d.Admin { |
| |
418 | role = "demote" |
| |
419 | } |
| |
420 | if d.State == "disabled" { |
| |
421 | state = "enable" |
| |
422 | } |
| |
423 | s.actions = []action{ |
| |
424 | {"Manage", []string{"admin", "user", role, d.Username}}, |
| |
425 | {"Manage", []string{"admin", "user", state, d.Username}}, |
| |
426 | } |
| |
427 | return s |
| 349 | }) |
428 | }) |
| 350 | } |
429 | } |
| 351 | |
430 | |
| @@ -439,7 +518,7 @@ func runAdminRepoList(c *Ctx, args []string) int { |
| 439 | size := gitutil.DirSize(RepoDir(c.Cfg.Server.Root, r.OwnerName, r.Name)) |
518 | size := gitutil.DirSize(RepoDir(c.Cfg.Server.Root, r.OwnerName, r.Name)) |
| 440 | ds = append(ds, out{r.Path, r.Visibility, r.Archived, r.CreatedAt, r.LastPush, size}) |
519 | ds = append(ds, out{r.Path, r.Visibility, r.Archived, r.CreatedAt, r.LastPush, size}) |
| 441 | } |
520 | } |
| 442 | return c.emitPage(p, ds, next, func(w io.Writer) { |
521 | return c.emitPageView(p, ds, next, func(w io.Writer) { |
| 443 | tb := c.table(w, "PATH", "VISIBILITY", "BYTES", "CREATED", "LAST PUSH") |
522 | tb := c.table(w, "PATH", "VISIBILITY", "BYTES", "CREATED", "LAST PUSH") |
| 444 | for _, d := range ds { |
523 | for _, d := range ds { |
| 445 | cells := []cell{cLink(d.Path, c.siteURL(d.Path)), cState(d.Visibility), cSize(d.Bytes), cAge(d.CreatedAt), cAge(d.LastPush)} |
524 | cells := []cell{cLink(d.Path, c.siteURL(d.Path)), cState(d.Visibility), cSize(d.Bytes), cAge(d.CreatedAt), cAge(d.LastPush)} |
| @@ -449,6 +528,22 @@ func runAdminRepoList(c *Ctx, args []string) int { |
| 449 | tb.row(cells...) |
528 | tb.row(cells...) |
| 450 | } |
529 | } |
| 451 | tb.flush() |
530 | tb.flush() |
| |
531 | }, func() screen { |
| |
532 | rows := make([]row, len(ds)) |
| |
533 | for i, d := range ds { |
| |
534 | state := d.Visibility |
| |
535 | if d.Archived { |
| |
536 | state += ", archived" |
| |
537 | } |
| |
538 | pushed := "" |
| |
539 | if d.LastPush != "" { |
| |
540 | pushed = "pushed " + relAge(d.LastPush, termNow()) |
| |
541 | } |
| |
542 | rows[i] = rowOf(cLink(d.Path, c.siteURL(d.Path)), cState(state), cSize(d.Bytes), cMeta(pushed)) |
| |
543 | } |
| |
544 | return listScreen("Repositories", rows, |
| |
545 | action{"Filter", []string{"admin", "repo", "list", "--visibility", "private"}}, |
| |
546 | ) |
| 452 | }) |
547 | }) |
| 453 | } |
548 | } |
| 454 | |
549 | |
| @@ -574,7 +669,7 @@ func runAdminRunners(c *Ctx, args []string) int { |
| 574 | } |
669 | } |
| 575 | } |
670 | } |
| 576 | d := map[string]any{"queue": queue, "runners": runners} |
671 | d := map[string]any{"queue": queue, "runners": runners} |
| 577 | return c.emit(d, func(w io.Writer) { |
672 | return c.emitView(d, func(w io.Writer) { |
| 578 | v := c.view(w) |
673 | v := c.view(w) |
| 579 | v.fields( |
674 | v.fields( |
| 580 | "pending", fmt.Sprintf("%d", queue.Pending), |
675 | "pending", fmt.Sprintf("%d", queue.Pending), |
| @@ -599,6 +694,32 @@ func runAdminRunners(c *Ctx, args []string) int { |
| 599 | tb.row(cText(r.Username), cText(r.Fingerprint), cAge(r.LastSeen), cFlex(scope), cText(held)) |
694 | tb.row(cText(r.Username), cText(r.Fingerprint), cAge(r.LastSeen), cFlex(scope), cText(held)) |
| 600 | } |
695 | } |
| 601 | tb.flush() |
696 | tb.flush() |
| |
697 | }, func() screen { |
| |
698 | s := screen{fields: []field{{"Queue", []cell{ |
| |
699 | cText(fmt.Sprintf("%d pending", queue.Pending)), |
| |
700 | cMeta(fmt.Sprintf("%d claimed in 24h", queue.Claimed24h), "wait avg "+c.Term.dur(queue.ClaimWaitAvgS), |
| |
701 | "max "+c.Term.dur(queue.ClaimWaitMaxS), fmt.Sprintf("%d reaped", queue.Reaped24h)), |
| |
702 | }}}} |
| |
703 | runnersSec := section{title: "Runners", n: len(runners)} |
| |
704 | idle := "" |
| |
705 | for _, r := range runners { |
| |
706 | scope := r.Scope |
| |
707 | if scope == "" { |
| |
708 | scope = "any" |
| |
709 | } |
| |
710 | lead, held := cGlyph(""), "idle" |
| |
711 | if r.BuildNumber != 0 { |
| |
712 | lead, held = cGlyph("running"), fmt.Sprintf("building %s #%d %s", r.BuildRepo, r.BuildNumber, r.BuildJob) |
| |
713 | } else if idle == "" { |
| |
714 | idle = r.Fingerprint |
| |
715 | } |
| |
716 | runnersSec.rows = append(runnersSec.rows, rowOf(cRef(r.Username), lead, cFlex(scope), cMeta("seen "+relAge(r.LastSeen, termNow()), held))) |
| |
717 | } |
| |
718 | s.sections = []section{runnersSec} |
| |
719 | if idle != "" { |
| |
720 | s.actions = []action{{"Prune", []string{"admin", "runners", "remove", idle}}} |
| |
721 | } |
| |
722 | return s |
| 602 | }) |
723 | }) |
| 603 | } |
724 | } |
| 604 | |
725 | |
| @@ -687,7 +808,7 @@ func runAdminMRPrune(c *Ctx, args []string) int { |
| 687 | if err := gitutil.PruneNow(dir); err != nil { |
808 | if err := gitutil.PruneNow(dir); err != nil { |
| 688 | return c.fail(protocol.ExitFailure, "%v; the head refs are deleted but the objects are not yet pruned; re-run the same command", err) |
809 | return c.fail(protocol.ExitFailure, "%v; the head refs are deleted but the objects are not yet pruned; re-run the same command", err) |
| 689 | } |
810 | } |
| 690 | return c.emit(rows, func(w io.Writer) { |
811 | return c.emitView(rows, func(w io.Writer) { |
| 691 | tb := c.table(w, "!", "HEAD") |
812 | tb := c.table(w, "!", "HEAD") |
| 692 | for _, r := range rows { |
813 | for _, r := range rows { |
| 693 | if r.Head == "" { |
814 | if r.Head == "" { |
| @@ -697,5 +818,21 @@ func runAdminMRPrune(c *Ctx, args []string) int { |
| 697 | tb.row(cRef(fmt.Sprintf("!%d", r.Number)), cRef(r.Head)) |
818 | tb.row(cRef(fmt.Sprintf("!%d", r.Number)), cRef(r.Head)) |
| 698 | } |
819 | } |
| 699 | tb.flush() |
820 | tb.flush() |
| |
821 | }, func() screen { |
| |
822 | rs := make([]row, len(rows)) |
| |
823 | for i, r := range rows { |
| |
824 | n := strconv.FormatInt(r.Number, 10) |
| |
825 | ref := cLink("!"+n, c.siteURL(repo.Path(), "mrs", n)) |
| |
826 | if r.Head == "" { |
| |
827 | rs[i] = rowOf(ref, cGlyph("skipped"), cMeta("already gone")) |
| |
828 | continue |
| |
829 | } |
| |
830 | rs[i] = rowOf(ref, cGlyph("ok"), cRef(fmt.Sprintf("%.10s", r.Head))) |
| |
831 | } |
| |
832 | s := listScreen("Pruned", rs) |
| |
833 | if len(rows) > 0 { |
| |
834 | s.actions = []action{{"Read", []string{"mr", "show", repo.Path(), strconv.FormatInt(rows[0].Number, 10)}}} |
| |
835 | } |
| |
836 | return s |
| 700 | }) |
837 | }) |
| 701 | } |
838 | } |