vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 50m ago
4b8c9e1d
| 1 | package web |
| 2 | |
| 3 | // Runner presence and queue depth for the Admin page's Runners tab. |
| 4 | // |
| 5 | // A runner is not an entity the forge stores: it is an SSH channel that leases jobs, |
| 6 | // authenticated by a key. So this is built from two derived facts in the queue — the |
| 7 | // presence rows a session writes while it is connected, and the work it is holding — |
| 8 | // plus the queue depth per repository. Nothing here is authoritative; it answers "is |
| 9 | // my build waiting because nobody is listening", which is otherwise a question about |
| 10 | // a process on somebody else's machine. |
| 11 | |
| 12 | import ( |
| 13 | "sort" |
| 14 | "strings" |
| 15 | "time" |
| 16 | ) |
| 17 | |
| 18 | // presenceWindow is how long a silent runner is still shown. A resident runner |
| 19 | // refreshes presence at least once per lease wait (25s) or heartbeat (a third of its |
| 20 | // lease), so two minutes is a runner that died rather than one that is thinking. |
| 21 | const presenceWindow = 2 * time.Minute |
| 22 | |
| 23 | type runnerView struct { |
| 24 | Account string |
| 25 | Repo string |
| 26 | RunID string |
| 27 | Job string |
| 28 | Seen time.Time |
| 29 | Idle bool |
| 30 | // Labels are what the runner says it is, joined for display. They matter here |
| 31 | // because a job waiting for a machine that is not connected is invisible from the |
| 32 | // queue side: the depth says seven waiting, and only this says that nobody |
| 33 | // carrying the label the job asks for has ever connected. |
| 34 | Labels string |
| 35 | } |
| 36 | |
| 37 | type queueView struct { |
| 38 | Name string |
| 39 | Queued int |
| 40 | Running int |
| 41 | // Cap is the per-repository bound on running jobs, 0 when there is none. It is |
| 42 | // shown beside the depth because a queue that is full and not draining is the |
| 43 | // question this page exists to answer, and "running 4 of 4" answers it in one |
| 44 | // line where "4 running, 7 waiting" leaves the reader guessing. |
| 45 | Cap int |
| 46 | } |
| 47 | |
| 48 | // runnerStatus reads the queue for the status page. The status page is an |
| 49 | // instance-admin surface, so every repository is visible, including private ones. |
| 50 | // Problems are returned alongside whatever could still be read. |
| 51 | func (w *Web) runnerStatus() (sessions []runnerView, queue []queueView, problems []string) { |
| 52 | if w.Runs == nil { |
| 53 | return nil, nil, nil |
| 54 | } |
| 55 | now := time.Now().UTC() |
| 56 | |
| 57 | rows, err := w.Runs.Sessions(now.Add(-presenceWindow)) |
| 58 | if err != nil { |
| 59 | problems = append(problems, "sessions: "+err.Error()) |
| 60 | } else { |
| 61 | for _, s := range rows { |
| 62 | v := runnerView{ |
| 63 | Account: s.Name, Repo: s.Repo, RunID: s.RunID, Seen: s.LastSeen, |
| 64 | Idle: s.RunID == "", Labels: strings.Join(s.Labels, ", "), |
| 65 | } |
| 66 | if s.Repo != "" { |
| 67 | if w.Repos == nil { |
| 68 | continue |
| 69 | } |
| 70 | if _, err := w.Repos.Open(s.Repo); err != nil { |
| 71 | continue // a stale row for a deleted repository |
| 72 | } |
| 73 | if run, err := w.Runs.Get(s.RunID); err == nil && run != nil { |
| 74 | v.Job = run.Job |
| 75 | } |
| 76 | } |
| 77 | sessions = append(sessions, v) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | depths, err := w.Runs.QueueByRepo() |
| 82 | if err != nil { |
| 83 | problems = append(problems, "queue: "+err.Error()) |
| 84 | return sessions, nil, problems |
| 85 | } |
| 86 | perRepoCap := 0 |
| 87 | if w.Cfg != nil { |
| 88 | perRepoCap = w.Cfg.MaxJobsPerRepo |
| 89 | } |
| 90 | for name, d := range depths { |
| 91 | if w.Repos == nil { |
| 92 | continue |
| 93 | } |
| 94 | if _, err := w.Repos.Open(name); err != nil { |
| 95 | continue // a queue row whose repository is gone |
| 96 | } |
| 97 | queue = append(queue, queueView{Name: name, Queued: d.Queued, Running: d.Running, Cap: perRepoCap}) |
| 98 | } |
| 99 | sort.Slice(queue, func(i, j int) bool { return queue[i].Name < queue[j].Name }) |
| 100 | return sessions, queue, problems |
| 101 | } |
| 102 |