OXYGENHUB
# b6dfcb61cf5e oxygenhub / templates / admin_runner_updates.html
10.1 KiB · HTML · last changed Runner updates: Windows runners are offered flow-windows-x86_64.exe 10 days ago source blame history raw
1
{% extends "base.html" %}
2
{% import "admin_nav.html" as an %}
3
{# One level's update settings (oxygenhub#322); `top` is the site, which inherits from nothing. #}
4
{% macro policy_fields(p, top) %}
5
  <label class="pol">Updates <select name="updates">{% if !top %}<option value="inherit"{% if p.updates == "inherit" %} selected{% endif %}>inherit</option>{% endif %}<option value="off"{% if p.updates == "off" || (top && p.updates == "inherit") %} selected{% endif %}>off</option><option value="on"{% if p.updates == "on" %} selected{% endif %}>on</option></select></label>
6
  <label class="pol">Channel <input type="text" name="channel" value="{{ p.channel }}" placeholder="{% if top %}stable{% else %}inherit{% endif %}" size="9" list="update-channels"></label>
7
  <label class="pol">Window, UTC <input type="text" name="window" value="{{ p.window }}" placeholder="{% if top %}any time{% else %}inherit{% endif %}" size="7" title="Hours in which updates may start, from-to, e.g. 02-05"></label>
8
  <label class="pol">Dry run <select name="dry_run">{% if !top %}<option value="inherit"{% if p.dry_run == "inherit" %} selected{% endif %}>inherit</option>{% endif %}<option value="off"{% if p.dry_run == "off" || (top && p.dry_run == "inherit") %} selected{% endif %}>off</option><option value="on"{% if p.dry_run == "on" %} selected{% endif %}>on</option></select></label>
9
{% endmacro %}
10
{% block title %}Runner updates · Administration{% endblock %}
11
{% block content %}
12
<div class="head"><h1>Runners <small><a href="/admin">Administration</a></small></h1></div>
13
<div class="settings-layout">
14
{% call an::nav(crate::web::admin::AdminSection::RunnerUpdates) %}
15
<div class="sections settings-body">
16
<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>
17
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
18
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
19
<datalist id="update-channels"><option value="stable"><option value="canary"></datalist>
20
<section class="panel">
21
  <h2>Runners update themselves</h2>
22
  <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>
23
  <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>
24
</section>
25
<form class="card wide" method="post" action="/admin/runners/updates/source">
26
  <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
27
  <h2>Where builds come from</h2>
28
  <label for="update-repo">The repository whose releases carry runner builds</label>
29
  <input type="text" id="update-repo" name="repo" value="{{ repo }}" placeholder="owner/name, e.g. andymccall/oxygenflow">
30
  <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>
31
  <button type="submit">Save</button>
32
</form>
33
<form class="card wide" method="post" action="/admin/runners/updates/policy">
34
  <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="site">
35
  <h2>The site</h2>
36
  <p class="small mute">What every runner does unless a nearer level says otherwise: {{ site.summary() }}.</p>
37
  <div class="pol-row">{% call policy_fields(site, true) %}</div>
38
  <button type="submit">Save</button>
39
</form>
40
<section class="panel" id="owners">
41
  <h2>Owners</h2>
42
  <p class="small mute">For the runners an account or organisation registered for its own repositories.</p>
43
  {% if owners.is_empty() %}<p class="mute small">No owner sets anything; their runners follow the site.</p>{% endif %}
44
  {% for o in owners %}
45
  <form class="pol-form" method="post" action="/admin/runners/updates/policy">
46
    <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="owner"><input type="hidden" name="key" value="{{ o.key }}">
47
    <strong>{{ o.key }}</strong>{% call policy_fields(o, false) %}<button type="submit">Save</button>
48
  </form>
49
  {% endfor %}
50
  <form class="pol-form" method="post" action="/admin/runners/updates/policy">
51
    <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="owner">
52
    <label class="pol">Owner <input type="text" name="key" size="12" placeholder="account or org" required></label>{% call policy_fields(site, false) %}<button type="submit">Add</button>
53
  </form>
54
</section>
55
<section class="panel" id="groups">
56
  <h2>Test groups</h2>
57
  <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>
58
  {% if groups.is_empty() %}<p class="mute small">No test groups yet.</p>{% endif %}
59
  {% for gv in groups %}{% let g = gv.g %}
60
  <div class="card wide update-group">
61
    <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>
62
    <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>
63
    <form class="pol-form" method="post" action="/admin/runners/updates/policy">
64
      <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="group"><input type="hidden" name="key" value="{{ g.id }}">
65
      {% call policy_fields(gv.policy, false) %}<button type="submit">Save settings</button>
66
    </form>
67
    <details><summary class="small">Who is in it</summary>
68
    <form method="post" action="/admin/runners/updates/groups/{{ g.id }}">
69
      <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
70
      <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>
71
      <label class="pol"><input type="radio" name="mode" value="chosen"{% if g.percent.is_none() %} checked{% endif %}> these runners:</label>
72
      <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>
73
      <button type="submit">Save members</button>
74
    </form>
75
    <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>
76
    </details>
77
  </div>
78
  {% endfor %}
79
  <form class="card wide" method="post" action="/admin/runners/updates/groups">
80
    <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
81
    <h3>New test group</h3>
82
    <div class="pol-row">
83
      <label class="pol">Name <input type="text" name="name" size="14" required placeholder="canary"></label>
84
      <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>
85
    </div>
86
    <label class="pol"><input type="radio" name="mode" value="percent"> a percentage: <input type="number" name="percent" min="1" max="100" size="4">%</label>
87
    <label class="pol"><input type="radio" name="mode" value="chosen" checked> these runners:</label>
88
    <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>
89
    <button type="submit">Make group</button>
90
  </form>
91
</section>
92
<section class="panel" id="runners">
93
  <h2>Runners</h2>
94
  {% if runners.is_empty() %}<p class="mute">No runners yet.</p>{% else %}
95
  <div class="table"><table>
96
  <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>
97
  {% for rv in runners %}{% let r = rv.r %}
98
  <tr>
99
    <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>
100
    <td class="small">{% if r.version.is_empty() %}<span class="mute">unknown</span>{% else %}flow {{ r.version }}{% endif %}</td>
101
    <td class="small">{% for g in rv.groups %}<span class="lbl">{{ g }}</span> {% endfor %}</td>
102
    <td class="small">{{ rv.effective_words() }}</td>
103
    <td class="small">{{ rv.offer }}</td>
104
    <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>
105
    <td><form class="pol-form tiny" method="post" action="/admin/runners/updates/policy">
106
      <input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="scope" value="runner"><input type="hidden" name="key" value="{{ r.id }}">
107
      {% call policy_fields(rv.policy, false) %}<button type="submit">set</button></form></td>
108
  </tr>
109
  {% endfor %}
110
  </table></div>
111
  {% endif %}
112
</section>
113
</div>
114
</div>
115
{% endblock %}