| @@ -159,7 +159,7 @@ func runAdminUserList(c *Ctx, args []string) int { |
| 159 | 159 | for _, u := range users { |
| 160 | 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 | 163 | tb := c.table(w, "USERNAME", "STATE", "ADMIN", "CREATED", "LAST SEEN") |
| 164 | 164 | for _, d := range ds { |
| 165 | 165 | mark := "" |
| @@ -169,6 +169,25 @@ func runAdminUserList(c *Ctx, args []string) int { |
| 169 | 169 | tb.row(cRef(d.Username), cState(d.State), cText(mark), cAge(d.CreatedAt), cAge(d.LastSeen)) |
| 170 | 170 | } |
| 171 | 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 | 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 | 303 | admin := "" |
| 285 | 304 | if d.Admin { |
| 286 | 305 | admin = "yes" |
| @@ -346,6 +365,66 @@ func runAdminUserShow(c *Ctx, args []string) int { |
| 346 | 365 | } |
| 347 | 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 | 518 | size := gitutil.DirSize(RepoDir(c.Cfg.Server.Root, r.OwnerName, r.Name)) |
| 440 | 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 | 522 | tb := c.table(w, "PATH", "VISIBILITY", "BYTES", "CREATED", "LAST PUSH") |
| 444 | 523 | for _, d := range ds { |
| 445 | 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 | 528 | tb.row(cells...) |
| 450 | 529 | } |
| 451 | 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 | 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 | 673 | v := c.view(w) |
| 579 | 674 | v.fields( |
| 580 | 675 | "pending", fmt.Sprintf("%d", queue.Pending), |
| @@ -599,6 +694,32 @@ func runAdminRunners(c *Ctx, args []string) int { |
| 599 | 694 | tb.row(cText(r.Username), cText(r.Fingerprint), cAge(r.LastSeen), cFlex(scope), cText(held)) |
| 600 | 695 | } |
| 601 | 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 | 808 | if err := gitutil.PruneNow(dir); err != nil { |
| 688 | 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 | 812 | tb := c.table(w, "!", "HEAD") |
| 692 | 813 | for _, r := range rows { |
| 693 | 814 | if r.Head == "" { |
| @@ -697,5 +818,21 @@ func runAdminMRPrune(c *Ctx, args []string) int { |
| 697 | 818 | tb.row(cRef(fmt.Sprintf("!%d", r.Number)), cRef(r.Head)) |
| 698 | 819 | } |
| 699 | 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 | } |