| 1 |
{% extends "base.html" %} |
| 2 |
{% block title %}Runners · Administration{% endblock %} |
| 3 |
{% block mainclass %} class="wide"{% endblock %} |
| 4 |
{% block content %} |
| 5 |
<div class="head"><h1>Runners <small>{{ runners.len()|plural("runner", "runners") }} · <a href="/admin">Administration</a></small></h1></div> |
| 6 |
<div class="bar"><span class="modes"><a href="/admin/runners" class="on">runners</a><a href="/admin/runners/settings">settings</a></span></div> |
| 7 |
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %} |
| 8 |
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %} |
| 9 |
{% if let Some(t) = new_token %} |
| 10 |
<section class="panel strip ok"> |
| 11 |
<h2>Registration token</h2> |
| 12 |
<p>Use it once, within a day, on the machine that will run jobs. It is shown only now.</p> |
| 13 |
<pre class="snippet">flow runner register {{ hub }} --token {{ t }} --name NAME [--ca hub-ca.pem] [--label LABEL] |
| 14 |
flow runner</pre> |
| 15 |
<p class="small mute">The runner sends its signing identity when it registers; this hub accepts receipts from it signed by that key only.</p> |
| 16 |
</section> |
| 17 |
{% endif %} |
| 18 |
<section class="panel"> |
| 19 |
{% if runners.is_empty() %}<p class="mute">No runners yet. Create a registration token below and run <code>flow runner register</code> on the runner.</p>{% else %} |
| 20 |
<div class="table"><table> |
| 21 |
<tr><th>Runner</th><th>Status</th><th>Serves</th><th>Signing key</th><th>Machine</th><th>Labels</th><th>Capacity</th><th>Running</th><th>Last seen</th><th></th></tr> |
| 22 |
{% for row in runners %}{% let r = row.r %} |
| 23 |
<tr> |
| 24 |
<td><a href="/admin/runners/{{ r.id }}"><strong>{{ r.name }}</strong></a>{% if r.source == "config" %} <span class="tag" title="Configured with a hub.toml token">hub.toml</span>{% endif %}</td> |
| 25 |
<td><span class="runner-status st-{{ r.status() }}">{{ r.status() }}</span></td> |
| 26 |
<td class="small">{{ row.scope }} |
| 27 |
<form class="tiny" method="post" action="/admin/runners/{{ r.id }}/scope" title="Empty for the whole site, an owner, or owner/repository"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="text" name="scope" size="14" placeholder="owner or owner/repo"> <button>set</button></form></td> |
| 28 |
<td>{% if r.key.is_empty() %}<span class="mute" title="Bound on its first signed receipt">not yet</span>{% else %}<code title="{{ r.key }} · {{ r.key_name }}">{{ r.key|short }}</code>{% endif %}</td> |
| 29 |
<td class="small">{% if r.arch.is_empty() %}<span class="mute">unknown</span>{% else %}{{ r.arch }} · {{ r.os }} · flow {{ r.version }}{% endif %}{% let f = r.facts_line() %}{% if !f.is_empty() %}<br><span class="mute" title="Measured by the runner at its last claim{% if !r.kernel.is_empty() %}; kernel {{ r.kernel }}{% endif %}">{{ f }}</span>{% endif %}</td> |
| 30 |
<td class="small">{% for l in r.label_list() %}<span class="lbl">{{ l }}</span> {% endfor %}</td> |
| 31 |
<td class="small">{{ r.max_jobs|plural("job", "jobs") }}{% if let Some(m) = r.memory_mib %} · {{ m }} MiB{% endif %}{% if let Some(c) = r.cpus %} · {{ c }} vCPU{% endif %}</td> |
| 32 |
<td>{{ row.running }}</td> |
| 33 |
<td class="small">{% if let Some(t) = r.last_seen_ms %}<time title="{{ t|when_i }}">{{ t|ago_i }}</time>{% else %}<span class="mute">never</span>{% endif %}</td> |
| 34 |
<td><form class="tiny" method="post" action="/admin/runners/{{ r.id }}/state"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"> |
| 35 |
{% if r.state == "active" %}<button name="state" value="draining" title="Finish current jobs, take no new ones">drain</button> <button name="state" value="disabled" title="Refuse its requests at once">disable</button>{% else %}<button name="state" value="active">resume</button>{% endif %}</form> |
| 36 |
<form class="tiny" method="post" action="/admin/runners/{{ r.id }}/remove" onsubmit="return confirm('Remove {{ r.name }}? Its credential stops working; its jobs keep its name.')"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button class="danger-btn">remove</button></form></td> |
| 37 |
</tr> |
| 38 |
{% endfor %} |
| 39 |
</table></div> |
| 40 |
{% endif %} |
| 41 |
</section> |
| 42 |
<section class="panel"> |
| 43 |
<h2>Queue</h2> |
| 44 |
{% if queue.is_empty() %}<p class="mute small">Nothing running or waiting.</p>{% else %} |
| 45 |
<div class="table"><table> |
| 46 |
<tr><th>Repository</th><th>Run</th><th>Job</th><th>Status</th><th>Runner</th></tr> |
| 47 |
{% for q in queue %}<tr><td><a href="/{{ q.owner }}/{{ q.name }}">{{ q.owner }}/{{ q.name }}</a></td><td><a href="/{{ q.owner }}/{{ q.name }}/flow/{{ q.run_number }}">#{{ q.run_number }}</a></td><td>{{ q.job }}</td><td>{{ q.status }}</td><td>{% if q.runner.is_empty() %}<span class="mute">waiting{% let needs = q.needs() %}{% if !needs.is_empty() %} for a runner with {% for l in needs %}<span class="lbl">{{ l }}</span> {% endfor %}{% endif %}{% if let Some(a) = q.arch() %} on <span class="lbl">{{ a }}</span>{% endif %}</span>{% else %}{{ q.runner }}{% endif %}</td></tr>{% endfor %} |
| 48 |
</table></div> |
| 49 |
<p class="small mute">Queued jobs are in the order runners claim them; a job waits for its dependencies and for a runner whose scope, tier and room fit it.</p> |
| 50 |
{% endif %} |
| 51 |
</section> |
| 52 |
<section class="panel"> |
| 53 |
<h2>Add a runner</h2> |
| 54 |
<p class="small">A runner registers with a one-time token: it sends its signing identity, architecture and labels, and gets its own credential, kept in <code>runner.toml</code> beside its <code>flow.toml</code>.</p> |
| 55 |
<p class="small">The runner serves the whole site, one owner's repositories, or one repository; it never takes another scope's jobs.</p> |
| 56 |
<form method="post" action="/admin/runners/token"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"> |
| 57 |
<label for="scope">Serves (empty for the whole site, an owner, or owner/repository)</label> |
| 58 |
<input type="text" id="scope" name="scope" placeholder="andymccall or andymccall/oxygen"> |
| 59 |
<button type="submit" class="primary-btn">Create a registration token</button></form> |
| 60 |
{% if !pending.is_empty() %}<p class="small mute">Unused tokens: {% for (by, made, expires) in pending %}by {{ by }} {{ made|ago_i }}, expires {{ expires|when_i }}{% if !loop.last %}; {% endif %}{% endfor %}</p>{% endif %} |
| 61 |
</section> |
| 62 |
{% endblock %} |