OXYGENHUB
# 5caa65fd51e7 oxygenhub / templates / admin_runner_updates.html
9.6 KiB · HTML · last changed Runner updates: a build that rolls back on one runner is held 10 days ago source blame history raw
8501fd58 Andy McCall ↶ 1
{% extends "base.html" %}
2
{% import "admin_nav.html" as an %}
dc5c6429 Andy McCall ↶ 3
{% import "runner_update_fields.html" as ruf %}
4
8501fd58 Andy McCall ↶ 5
{% block title %}Runner updates · Administration{% endblock %}
6
{% block content %}
7
<div class="head"><h1>Runners <small><a href="/admin">Administration</a></small></h1></div>
8
<div class="settings-layout">
9
{% call an::nav(crate::web::admin::AdminSection::RunnerUpdates) %}
10
<div class="sections settings-body">
11
<div class="bar"><span class="modes"><a href="/admin/runners">runners</a><a href="/admin/runners/images">images</a><a href="/admin/runners/settings">settings</a><a href="/admin/runners/updates" class="on">updates</a></span></div>
12
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
13
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
14
<datalist id="update-channels"><option value="stable"><option value="canary"></datalist>
15
<section class="panel">
16
  <h2>Runners update themselves</h2>
17
  <p>A runner asks the hub which build it should run, fetches it, checks it as a release, finishes what it holds and swaps itself in; a build that does not reach the hub goes back to the one before. <strong>Off unless chosen.</strong> The site, an owner, a test group and a runner each may set any of the settings below, and the nearest setting wins: a runner's own, then its test groups', then its owner's, then the site's. A runner's <code>flow.toml</code> can still refuse every update (<code>[runner.update] allow = false</code>).</p>
18
  <p class="small mute">A rollout: turn updates on for a test group (two chosen runners, or 10% of them), watch it, then widen it to 50%, then turn updates on for everyone. A percentage is taken by a stable hash of each runner's id, so it is the same machines every time, and 10% sits inside 50%.</p>
19
</section>
20
<form class="card wide" method="post" action="/admin/runners/updates/source">
21
  <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
22
  <h2>Where builds come from</h2>
23
  <label for="update-repo">The repository whose releases carry runner builds</label>
24
  <input type="text" id="update-repo" name="repo" value="{{ repo }}" placeholder="owner/name, e.g. andymccall/oxygenflow">
b6dfcb61 Andy McCall ↶ 25
  <p class="small mute">Each release offers one file per machine, <code>flow-linux-x86_64</code>, <code>flow-linux-aarch64</code>, <code>flow-linux-riscv64</code>, <code>flow-windows-x86_64.exe</code>. <em>stable</em> is its newest release, <em>canary</em> its newest including pre-releases; a version (<code>0.3.4</code>) pins it, going back if need be.</p>
8501fd58 Andy McCall ↶ 26
  <button type="submit">Save</button>
27
</form>
faf5246c Andy McCall ↶ 28
{% if !holds.is_empty() %}
29
<section class="panel strip warn" id="holds">
30
  <h2>Held builds</h2>
31
  <p class="small">A build that rolled back on a runner is offered to no other until you lift the hold.</p>
32
  {% for h in holds %}
33
  <form class="pol-form" method="post" action="/admin/runners/updates/holds/lift"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="version" value="{{ h.version }}">
34
    <strong>flow {{ h.version }}</strong><span class="small">rolled back on {{ h.runner }}: {{ h.why }} · <time title="{{ h.at_ms|when_i }}">{{ h.at_ms|ago_i }}</time></span><button type="submit" class="small">LIFT</button>
35
  </form>
36
  {% endfor %}
37
</section>
38
{% endif %}
8501fd58 Andy McCall ↶ 39
<form class="card wide" method="post" action="/admin/runners/updates/policy">
40
  <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="site">
41
  <h2>The site</h2>
42
  <p class="small mute">What every runner does unless a nearer level says otherwise: {{ site.summary() }}.</p>
dc5c6429 Andy McCall ↶ 43
  <div class="pol-row">{% call ruf::policy_fields(site, true) %}</div>
8501fd58 Andy McCall ↶ 44
  <button type="submit">Save</button>
45
</form>
46
<section class="panel" id="owners">
47
  <h2>Owners</h2>
48
  <p class="small mute">For the runners an account or organisation registered for its own repositories.</p>
49
  {% if owners.is_empty() %}<p class="mute small">No owner sets anything; their runners follow the site.</p>{% endif %}
50
  {% for o in owners %}
51
  <form class="pol-form" method="post" action="/admin/runners/updates/policy">
52
    <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="owner"><input type="hidden" name="key" value="{{ o.key }}">
dc5c6429 Andy McCall ↶ 53
    <strong>{{ o.key }}</strong>{% call ruf::policy_fields(o, false) %}<button type="submit">Save</button>
8501fd58 Andy McCall ↶ 54
  </form>
55
  {% endfor %}
56
  <form class="pol-form" method="post" action="/admin/runners/updates/policy">
57
    <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="owner">
dc5c6429 Andy McCall ↶ 58
    <label class="pol">Owner <input type="text" name="key" size="12" placeholder="account or org" required></label>{% call ruf::policy_fields(site, false) %}<button type="submit">Add</button>
8501fd58 Andy McCall ↶ 59
  </form>
60
</section>
61
<section class="panel" id="groups">
62
  <h2>Test groups</h2>
63
  <p class="small mute">A named set of runners: those you choose, or a percentage of the site's runners (or one owner's). Give a group its own settings to try a build on it before the rest.</p>
64
  {% if groups.is_empty() %}<p class="mute small">No test groups yet.</p>{% endif %}
65
  {% for gv in groups %}{% let g = gv.g %}
66
  <div class="card wide update-group">
67
    <h3>{{ g.name }} <small class="mute">{{ gv.reach }} · {% if let Some(p) = g.percent %}{{ p }}%{% else %}chosen{% endif %} · {{ gv.members.len()|plural("runner", "runners") }} now</small></h3>
68
    <p class="small">{% if gv.members.is_empty() %}<span class="mute">No runners in it.</span>{% else %}{% for m in gv.members %}<span class="lbl">{{ m }}</span> {% endfor %}{% endif %}</p>
69
    <form class="pol-form" method="post" action="/admin/runners/updates/policy">
70
      <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="group"><input type="hidden" name="key" value="{{ g.id }}">
dc5c6429 Andy McCall ↶ 71
      {% call ruf::policy_fields(gv.policy, false) %}<button type="submit">Save settings</button>
8501fd58 Andy McCall ↶ 72
    </form>
73
    <details><summary class="small">Who is in it</summary>
74
    <form method="post" action="/admin/runners/updates/groups/{{ g.id }}">
75
      <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
76
      <label class="pol"><input type="radio" name="mode" value="percent"{% if g.percent.is_some() %} checked{% endif %}> a percentage of {{ gv.reach }}: <input type="number" name="percent" min="1" max="100" size="4" value="{% if let Some(p) = g.percent %}{{ p }}{% endif %}">%</label>
77
      <label class="pol"><input type="radio" name="mode" value="chosen"{% if g.percent.is_none() %} checked{% endif %}> these runners:</label>
78
      <div class="pol-row">{% for rv in runners %}{% if g.owner.is_empty() || g.owner == rv.r.scope_owner %}<label class="pol"><input type="checkbox" name="runner" value="{{ rv.r.id }}"{% if gv.chosen.contains(rv.r.id) %} checked{% endif %}> {{ rv.r.name }}</label>{% endif %}{% endfor %}</div>
79
      <button type="submit">Save members</button>
80
    </form>
81
    <form class="tiny" method="post" action="/admin/runners/updates/groups/{{ g.id }}/delete" data-confirm="Remove the test group {{ g.name }}? Its runners follow the levels above it."><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button class="danger-btn">remove group</button></form>
82
    </details>
83
  </div>
84
  {% endfor %}
85
  <form class="card wide" method="post" action="/admin/runners/updates/groups">
86
    <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
87
    <h3>New test group</h3>
88
    <div class="pol-row">
89
      <label class="pol">Name <input type="text" name="name" size="14" required placeholder="canary"></label>
90
      <label class="pol">Of <input type="text" name="owner" size="12" placeholder="the site's runners" title="Blank for the site's runners, or an owner for theirs"></label>
91
    </div>
92
    <label class="pol"><input type="radio" name="mode" value="percent"> a percentage: <input type="number" name="percent" min="1" max="100" size="4">%</label>
93
    <label class="pol"><input type="radio" name="mode" value="chosen" checked> these runners:</label>
94
    <div class="pol-row">{% for rv in runners %}<label class="pol"><input type="checkbox" name="runner" value="{{ rv.r.id }}"> {{ rv.r.name }}</label>{% endfor %}</div>
95
    <button type="submit">Make group</button>
96
  </form>
97
</section>
98
<section class="panel" id="runners">
99
  <h2>Runners</h2>
100
  {% if runners.is_empty() %}<p class="mute">No runners yet.</p>{% else %}
101
  <div class="table"><table>
102
  <tr><th>Runner</th><th>Runs</th><th>Groups</th><th>Its policy</th><th>Now</th><th>Last said</th><th>Its own settings</th></tr>
103
  {% for rv in runners %}{% let r = rv.r %}
104
  <tr>
105
    <td><a href="/admin/runners/{{ r.id }}"><strong>{{ r.name }}</strong></a>{% if !r.scope_owner.is_empty() %}<br><span class="small mute">{{ r.scope_owner }}'s</span>{% endif %}</td>
106
    <td class="small">{% if r.version.is_empty() %}<span class="mute">unknown</span>{% else %}flow {{ r.version }}{% endif %}</td>
107
    <td class="small">{% for g in rv.groups %}<span class="lbl">{{ g }}</span> {% endfor %}</td>
108
    <td class="small">{{ rv.effective_words() }}</td>
109
    <td class="small">{{ rv.offer }}</td>
110
    <td class="small">{% if r.update_note.is_empty() %}<span class="mute">nothing yet</span>{% else %}{{ r.update_note }}{% if let Some(t) = r.update_ms %} <time class="mute" title="{{ t|when_i }}">{{ t|ago_i }}</time>{% endif %}{% endif %}</td>
111
    <td><form class="pol-form tiny" method="post" action="/admin/runners/updates/policy">
112
      <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="runner"><input type="hidden" name="key" value="{{ r.id }}">
dc5c6429 Andy McCall ↶ 113
      {% call ruf::policy_fields(rv.policy, false) %}<button type="submit">set</button></form></td>
8501fd58 Andy McCall ↶ 114
  </tr>
115
  {% endfor %}
116
  </table></div>
117
  {% endif %}
118
</section>
119
</div>
120
</div>
121
{% endblock %}