v0.8.0 eaf4295

actions.templ RawHistoryFolder

1package web
2
3// The CI pages: the runs of a repository, a run with its jobs, a job with its
4// steps and its log (actions.go). A state is always a word as well as a
5// symbol and a colour.
6
7// CheckIcon is the symbol of a state: a mark in the colour of its kind, with
8// the state as its text for a screen reader.
9templ CheckIcon(kind, state string) {
10	<span class={ "ck", "ck-" + kind } role="img" aria-label={ state } title={ state }></span>
11}
12
13templ Actions(l Layout, p ActionsPage) {
14	@Shell(l, nil) {
15		if len(p.Runs) == 0 {
16			<p class="dim">no runs yet: a push that matches a workflow of .github/workflows starts one.</p>
17		} else {
18			<table class="list runs" aria-label="Runs">
19				<thead>
20					<tr><th class="c-st"></th><th class="c-name">workflow</th><th class="c-sub">commit</th><th class="c-ref">ref</th><th class="c-who">actor</th><th class="c-dur">time</th><th class="c-when">created</th></tr>
21				</thead>
22				<tbody id="run-rows" data-rows>
23					for _, r := range p.Runs {
24						@RunListRow(r)
25					}
26				</tbody>
27			</table>
28		}
29		if p.More != "" {
30			<p class="more"><a href={ templ.SafeURL(p.More) } rel="next">older runs</a></p>
31		}
32	}
33}
34
35// RunListRow is a row of the list of runs; its id is the target of the live
36// updates.
37templ RunListRow(r RunRow) {
38	<tr id={ "run-" + r.ID }>
39		<td class="c-st">@CheckIcon(r.Kind, r.State)</td>
40		<td class="c-name">
41			<a href={ templ.SafeURL(r.Href) }>{ r.Workflow }</a>
42			<span class="mute">{ r.Number }</span>
43			if r.Attempt > 1 {
44				<span class="pill">attempt { itoa(int(r.Attempt)) }</span>
45			}
46		</td>
47		<td class="c-sub"><a href={ templ.SafeURL(r.CHref) }>{ r.Short }</a> { r.Title }</td>
48		<td class="c-ref"><a href={ templ.SafeURL(r.RefHref) }>{ r.Ref }</a></td>
49		<td class="c-who dim">{ r.Actor }</td>
50		<td class="c-dur dim">{ r.State }{ " " }{ r.Duration }</td>
51		<td class="c-when dim">
52			if r.WhenAt != "" {
53				<time datetime={ r.WhenAt } title={ r.WhenAt }>{ r.When }</time>
54			}
55		</td>
56	</tr>
57}
58
59templ RunDetail(l Layout, p RunPage) {
60	@Shell(l, runRail(p)) {
61		@LiveSlot("run-notice")
62		<div id="run-body">
63			@RunBody(p)
64		</div>
65	}
66}
67
68// runBodyFragment is the body of the run page as a live fragment.
69templ runBodyFragment(p RunPage) {
70	<div id="run-body">
71		@RunBody(p)
72	</div>
73}
74
75// RunBody is the jobs of a run, replaced by the live updates.
76templ RunBody(p RunPage) {
77	<h2 class="run-h">@CheckIcon(p.Kind, p.State)<span>{ p.Workflow }</span> <span class="mute">{ p.Number }</span></h2>
78	if p.Error != "" {
79		<pre class="run-err">{ p.Error }</pre>
80	}
81	<ul class="jobs">
82		for _, j := range p.Jobs {
83			<li id={ "job-" + j.ID }>
84				<a class="job-h" href={ templ.SafeURL(j.Href) }>
85					@CheckIcon(j.Kind, j.State)
86					<span class="j-name">{ j.Name }</span>
87					<span class="dim">{ j.State }{ " " }{ j.Duration }</span>
88				</a>
89				if j.Runner != "" {
90					<span class="mute">on { runnerText(j) }</span>
91				}
92				if len(j.Needs) > 0 {
93					<span class="mute">needs { joinWords(j.Needs) }</span>
94				}
95				if j.Error != "" {
96					<p class="job-err">{ j.Error }</p>
97				}
98				if len(j.Steps) > 0 {
99					<ol class="steps">
100						for _, s := range j.Steps {
101							<li>@CheckIcon(s.Kind, s.State) <span>{ s.Name }</span> <span class="dim">{ s.Duration }</span></li>
102						}
103					</ol>
104				}
105			</li>
106		}
107	</ul>
108}
109
110templ runRail(p RunPage) {
111	<section aria-labelledby="r-run">
112		<h2 id="r-run">Run</h2>
113		<dl class="kv">
114			<dt>result</dt>
115			<dd>{ p.State }</dd>
116			<dt>commit</dt>
117			<dd><a href={ templ.SafeURL(p.CHref) }>{ p.Short }</a></dd>
118			<dt>ref</dt>
119			<dd><a href={ templ.SafeURL(p.RefHref) }>{ p.Ref }</a></dd>
120			<dt>actor</dt>
121			<dd>{ p.Actor }</dd>
122			<dt>attempt</dt>
123			<dd>{ itoa(int(p.Attempt)) } of { itoa(int(p.Latest)) }</dd>
124		</dl>
125		if len(p.Attempts) > 0 {
126			<ul class="attempts">
127				for _, a := range p.Attempts {
128					<li>
129						if a.Current {
130							<span aria-current="page">{ a.Label }</span>
131						} else {
132							<a href={ templ.SafeURL(a.Href) }>{ a.Label }</a>
133						}
134					</li>
135				}
136			</ul>
137		}
138	</section>
139	if p.CancelAction != "" || p.RerunAction != "" {
140		<section aria-labelledby="r-act" class="decide">
141			<h2 id="r-act">Actions</h2>
142			<div class="d-btns">
143				if p.CancelAction != "" {
144					<form method="post" action={ templ.SafeURL(p.CancelAction) }><button type="submit" class="btn">Cancel run</button></form>
145				}
146				if p.RerunAction != "" {
147					<form method="post" action={ templ.SafeURL(p.RerunAction) }><button type="submit" class="btn primary">Re-run all jobs</button></form>
148				}
149				if p.CanRerunFailed {
150					<form method="post" action={ templ.SafeURL(p.RerunFailedAction) }><button type="submit" class="btn">Re-run failed jobs</button></form>
151				}
152			</div>
153		</section>
154	}
155}
156
157// JobHead is the title of the job page with its state and its error, replaced
158// by the live updates. total is the number of lines the log holds.
159templ JobHead(j JobRow, total int64) {
160	<div id="job-head" data-total={ itoa64(total) }>
161		<h2 class="run-h">@CheckIcon(j.Kind, j.State)<span>{ j.Name }</span> <span class="dim">{ j.State }{ " " }{ j.Duration }</span></h2>
162		if j.Error != "" {
163			<p class="job-err">{ j.Error }</p>
164		}
165	</div>
166}
167
168// JobSteps is the steps of the job in the rail, replaced by the live updates.
169templ JobSteps(steps []StepView) {
170	<ol class="steps" id="job-steps">
171		for _, s := range steps {
172			<li>
173				@CheckIcon(s.Kind, s.State)
174				if s.LogEnd > s.LogStart {
175					<a href={ templ.SafeURL("#L" + itoa64(s.LogStart)) }>{ s.Name }</a>
176				} else {
177					<span>{ s.Name }</span>
178				}
179				<span class="dim">{ s.Duration }</span>
180			</li>
181		}
182	</ol>
183}
184
185templ Job(l Layout, p JobPage) {
186	@Shell(l, jobRail(p)) {
187		@LiveSlot("job-notice")
188		@JobHead(p.JobRow, p.Total)
189		<div id="job-log" class="joblog" data-total={ itoa64(p.Total) } data-follow?={ p.Follow } data-stream={ p.StreamHref } data-next={ itoa64(p.From + int64(len(p.Lines))) }>
190			if p.Note != "" {
191				<p class="dim">{ p.Note }</p>
192			}
193			if p.Prev != "" {
194				<p class="more"><a href={ templ.SafeURL(p.Prev) } rel="prev">earlier lines</a></p>
195			}
196			<ol class="log" start={ itoa64(p.From + 1) }>
197				for _, ln := range p.Lines {
198					@LogLine(ln)
199				}
200			</ol>
201			if p.Next != "" {
202				<p class="more"><a href={ templ.SafeURL(p.Next) } rel="next">more lines</a></p>
203			}
204		</div>
205	}
206}
207
208// LogLine is a line of the log. Group and error markers of the runner show as
209// headings and errors, never as raw text.
210templ LogLine(ln LogLineView) {
211	<li id={ "L" + itoa64(ln.N) } class={ logClass(ln) } data-step={ itoa(int(ln.Step)) } data-cont?={ ln.Continued }><time>{ ln.Time }</time><span class="t">{ ln.Text }</span></li>
212}
213
214templ jobRail(p JobPage) {
215	<section aria-labelledby="r-job">
216		<h2 id="r-job">Steps</h2>
217		@JobSteps(p.Steps)
218	</section>
219	<section aria-labelledby="r-jobf">
220		<h2 id="r-jobf">Job</h2>
221		<dl class="kv">
222			<dt>run</dt>
223			<dd><a href={ templ.SafeURL(p.RunHref) }>{ p.RunName }</a></dd>
224			if p.Runner != "" {
225				<dt>runner</dt>
226				<dd>{ p.Runner }</dd>
227			}
228			if p.Isolation != "" {
229				<dt>isolation</dt>
230				<dd>{ p.Isolation }</dd>
231			}
232			<dt>lines</dt>
233			<dd>{ itoa64(p.Total) }</dd>
234		</dl>
235		<p><a href={ templ.SafeURL(p.RawHref) }>raw log</a></p>
236	</section>
237}
238
239// CommitChecks is the combined state of the checks of a commit, as a mark
240// with a count, linking to the page that explains it.
241templ CommitChecks(c ChecksSummary) {
242	if c.Total > 0 {
243		<a class="ck-sum" href={ templ.SafeURL(c.Href) } title={ c.Title }>
244			@CheckIcon(c.Kind, c.State)
245			<span class="mute">{ c.Label }</span>
246		</a>
247	}
248}
249
250// MRChecks is the checks section of the rail of a merge request.
251templ MRChecks(c ChecksSection) {
252	<section aria-labelledby="r-checks" id="mr-checks">
253		<h2 id="r-checks">Checks <span class="count">{ itoa(len(c.Rows)) }</span></h2>
254		if len(c.Rows) == 0 {
255			<p class="dim">no checks</p>
256		} else {
257			<ul class="checks">
258				for _, r := range c.Rows {
259					<li>
260						@CheckIcon(r.Kind, r.State)
261						if r.Href != "" {
262							<a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
263						} else {
264							<span>{ r.Name }</span>
265						}
266						<span class="dim">{ r.State }</span>
267						if r.Waiting {
268							<span class="mute">waiting for a runner</span>
269						}
270					</li>
271				}
272			</ul>
273		}
274		if c.Note != "" {
275			<p class="mute">{ c.Note }</p>
276		}
277	</section>
278}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close