OXYGENHUB
# 737de0fac722 oxygenhub / templates / admin_runners.html
8.5 KiB · HTML · last changed Administration: a sidebar of grouped pages, one page per section (closes #288) 15 days ago source blame history raw
b6b7e4cf Andy McCall ↶ 1
{% extends "base.html" %}
737de0fa Andy McCall ↶ 2
{% import "admin_nav.html" as an %}
b6b7e4cf Andy McCall ↶ 3
{% block title %}Runners · Administration{% endblock %}
4
{% block mainclass %} class="wide"{% endblock %}
5
{% block content %}
6
<div class="head"><h1>Runners <small>{{ runners.len()|plural("runner", "runners") }} · <a href="/admin">Administration</a></small></h1></div>
737de0fa Andy McCall ↶ 7
<div class="settings-layout">
8
{% call an::nav(crate::web::admin::AdminSection::Runners) %}
9
<div class="sections settings-body">
6ca0b971 Andy McCall ↶ 10
<div class="bar"><span class="modes"><a href="/admin/runners" class="on">runners</a><a href="/admin/runners/images">images</a><a href="/admin/runners/settings">settings</a></span></div>
b6b7e4cf Andy McCall ↶ 11
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
12
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
13
{% if let Some(t) = new_token %}
14
<section class="panel strip ok">
15
  <h2>Registration token</h2>
16
  <p>Use it once, within a day, on the machine that will run jobs. It is shown only now.</p>
17
  <pre class="snippet">flow runner register {{ hub }} --token {{ t }} --name NAME [--ca hub-ca.pem] [--label LABEL]
18
flow runner</pre>
19
  <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>
20
</section>
21
{% endif %}
22
<section class="panel">
23
  {% 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 %}
24
  <div class="table"><table>
e9b9619c Andy McCall ↶ 25
  <tr><th>Runner</th><th>Connectivity</th><th>Status</th><th>Serves</th><th>Signing key</th><th>Machine</th><th>Labels</th><th>Capacity</th><th>Last seen</th><th></th></tr>
b6b7e4cf Andy McCall ↶ 26
  {% for row in runners %}{% let r = row.r %}
27
  <tr>
28
    <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>
e9b9619c Andy McCall ↶ 29
    <td><span class="runner-status st-{{ r.connectivity() }}">{{ r.connectivity() }}</span></td>
30
    <td class="small"><a href="/admin/runners?runner={{ r.name|urlencode }}#queue" title="{% if row.jobs.is_empty() %}Its jobs in the queue{% else %}{{ row.jobs_line() }}{% endif %}">{{ row.doing() }}</a></td>
74be9796 Andy McCall ↶ 31
    <td class="small">{{ row.scope }}
32
      <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>
b6b7e4cf Andy McCall ↶ 33
    <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>
a46d15e7 Andy McCall ↶ 34
    <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 %}{% if !row.swarm.is_empty() %}<br><span class="mute" title="Where other runners fetch image chunks from it">swarm {{ row.swarm }}</span>{% endif %}</td>
b6b7e4cf Andy McCall ↶ 35
    <td class="small">{% for l in r.label_list() %}<span class="lbl">{{ l }}</span> {% endfor %}</td>
36
    <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>
37
    <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>
38
    <td><form class="tiny" method="post" action="/admin/runners/{{ r.id }}/state"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
39
      {% 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>
a7aa76ac Andy McCall 40
      <form class="tiny" method="post" action="/admin/runners/{{ r.id }}/remove" data-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>
b6b7e4cf Andy McCall ↶ 41
  </tr>
42
  {% endfor %}
43
  </table></div>
44
  {% endif %}
45
</section>
e9b9619c Andy McCall ↶ 46
<section class="panel" id="queue">
47
  <h2>Queue <small>{% if filter.any() %}{{ queue.len() }} of {{ queue_total }} · <a href="/admin/runners#queue">all</a>{% else %}{{ queue_total|plural("job", "jobs") }}{% endif %}</small></h2>
48
  <form class="filters" method="get" action="/admin/runners#queue">
49
    <select name="repo" aria-label="Repository"><option value="">every repository</option>{% for r in queue_repos %}<option{% if *r == filter.repo %} selected{% endif %}>{{ r }}</option>{% endfor %}</select>
50
    <select name="status" aria-label="Status"><option value="">running and queued</option><option value="running"{% if filter.status == "running" %} selected{% endif %}>running</option><option value="queued"{% if filter.status == "queued" %} selected{% endif %}>queued</option></select>
51
    <select name="runner" aria-label="Runner"><option value="">every runner</option>{% for r in queue_runners %}<option{% if *r == filter.runner %} selected{% endif %}>{{ r }}</option>{% endfor %}{% if !filter.runner.is_empty() && !queue_runners.contains(filter.runner) %}<option selected>{{ filter.runner }}</option>{% endif %}</select>
52
    <button>filter</button>
e97500b7 Andy McCall ↶ 53
    <a class="go end" href="/admin/insights?range=24h{% if !filter.repo.is_empty() %}&repo={{ filter.repo|urlencode }}{% endif %}{% if !filter.runner.is_empty() %}&runner={{ filter.runner|urlencode }}{% endif %}" target="_blank" rel="noopener" title="Pickup time, jobs, durations and runners, charted (opens a new window)">Charts ↗</a>
e9b9619c Andy McCall ↶ 54
  </form>
55
  {% if queue.is_empty() %}<p class="mute small">{% if filter.any() %}Nothing matches.{% else %}Nothing running or waiting.{% endif %}</p>{% else %}
b66e70b7 Andy McCall ↶ 56
  <div class="table"><table>
107eaba5 Andy McCall ↶ 57
  <tr><th>Repository</th><th>Run</th><th>Job</th><th>Status</th><th>Priority</th><th>Started</th><th>Runner</th></tr>
58
  {% 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 class="small"{% if !q.priority_note.is_empty() %} title="{{ q.priority_note }}"{% endif %}>{% if q.priority() == "normal" %}<span class="mute">normal</span>{% else %}<span class="tag">{{ q.priority() }}</span>{% endif %}</td><td class="small">{% if let Some(t) = q.started_ms %}<time title="{{ t|when_i }}">{{ t|ago_i }}</time>{% endif %}</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 %}<a href="/admin/runners?runner={{ q.runner|urlencode }}#queue">{{ q.runner }}</a>{% endif %}</td></tr>{% endfor %}
b66e70b7 Andy McCall ↶ 59
  </table></div>
55673c65 Andy McCall ↶ 60
  <p class="small mute">Queued jobs are in the order runners claim them: repositories take turns, a repository with jobs running waiting behind those without; a job waits for its dependencies and for a runner whose scope, tier and room fit it.</p>
b66e70b7 Andy McCall ↶ 61
  {% endif %}
62
</section>
63
<section class="panel">
b6b7e4cf Andy McCall ↶ 64
  <h2>Add a runner</h2>
65
  <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>
74be9796 Andy McCall ↶ 66
  <p class="small">The runner serves the whole site, one owner's repositories, or one repository; it never takes another scope's jobs.</p>
67
  <form method="post" action="/admin/runners/token"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
68
    <label for="scope">Serves (empty for the whole site, an owner, or owner/repository)</label>
69
    <input type="text" id="scope" name="scope" placeholder="andymccall or andymccall/oxygen">
70
    <button type="submit" class="primary-btn">Create a registration token</button></form>
b6b7e4cf Andy McCall ↶ 71
  {% 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 %}
72
</section>
737de0fa Andy McCall ↶ 73
</div>
74
</div>
b6b7e4cf Andy McCall ↶ 75
{% endblock %}