{% extends "op_layout.html" %}
{% block title %}{{ verb }} {{ slug }} — {{ brand_name | default(value='Rustango Operator Console') }}{% endblock %}
{% block content %}
<p class="breadcrumb"><a href="{{ console_prefix }}/orgs">Organizations</a><span class="sep">/</span><a href="{{ console_prefix }}/orgs/provision">Runs</a><span class="sep">/</span>{{ verb }} {{ slug }}</p>
<h1>{{ verb }} <code>{{ slug }}</code></h1>
<p class="lead">
Run #{{ run_id }} — <strong id="run-state" data-state="{{ state }}">{{ state }}</strong>
{% if not terminal %}<span id="live-note"> · following live</span>{% endif %}
</p>
{% if error %}
<div class="alert alert-error alert-lines">{{ error }}</div>
{% endif %}
{% if handover %}
<div class="alert alert-secret" id="first-admin">
{% if handover.mailed_to %}
A set-password link for <code>{{ handover.username }}</code> was mailed to
<code>{{ handover.mailed_to }}</code>. It works once and expires in {{ handover.hours }} hours.
{% elif handover.link %}
{% if handover.mail_failed %}The mail could not be sent. {% endif %}
Give this set-password link to <code>{{ handover.username }}</code>. It is shown
only now, works once, and expires in {{ handover.hours }} hours.
<div class="link-copy">
<input type="text" id="handover-link" readonly value="{{ handover.link }}" aria-label="Set-password link"
autocomplete="off">
<button type="button" class="btn btn-secondary" id="copy-link">Copy</button>
</div>
<script nonce="{{ csp_nonce }}">
document.getElementById("copy-link").addEventListener("click", async (e) => {
const input = document.getElementById("handover-link");
try {
await navigator.clipboard.writeText(input.value);
} catch (_) {
input.select();
document.execCommand("copy");
}
e.target.textContent = "Copied";
});
</script>
{% else %}
The first admin <code>{{ handover.username }}</code> was created, but no link could
be issued. Run <code>set-password-link {{ slug }} {{ handover.username }}</code>.
{% endif %}
</div>
{% endif %}
<table>
<thead>
<tr><th>#</th><th>Step</th><th>Status</th><th>Detail</th></tr>
</thead>
<tbody id="events">
{#- One row per step at its latest status; `data-step` lets the live
stream update it in place. `info` rows are details, one per event. -#}
{% for e in events %}
<tr data-seq="{{ e.seq }}"{% if e.status != "info" %} data-step="{{ e.step }}"{% endif %}>
<td>{{ loop.index }}</td>
<td><code>{{ e.step }}</code></td>
<td class="status-{{ e.status }}">{{ e.status }}</td>
<td class="detail">{% for p in e.parts %}{% if p.code %}<code>{{ p.text }}</code>{% else %}{{ p.text }}{% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="form-actions"><a href="{{ console_prefix }}/orgs" class="btn btn-secondary">Back to tenants</a></p>
{% if not terminal %}
<script nonce="{{ csp_nonce }}">
(function () {
const tbody = document.getElementById("events");
let last = 0;
for (const r of tbody.querySelectorAll("tr[data-seq]")) {
last = Math.max(last, Number(r.dataset.seq));
}
const es = new EventSource(consoleUrl("/orgs/provision/{{ run_id }}/stream?after=" + last));
function fillMessage(td, message) {
const parts = String(message || "").split("`");
td.replaceChildren();
if (parts.length % 2 === 0) { td.textContent = message; return; }
parts.forEach((p, i) => {
if (!p) { return; }
if (i % 2) {
const c = document.createElement("code");
c.textContent = p;
td.appendChild(c);
} else {
td.append(p);
}
});
}
es.addEventListener("step", (msg) => {
const e = JSON.parse(msg.data);
let tr = e.status === "info"
? null
: tbody.querySelector('tr[data-step="' + CSS.escape(e.step) + '"]');
if (!tr) {
tr = document.createElement("tr");
if (e.status !== "info") { tr.dataset.step = e.step; }
const n = document.createElement("td");
n.textContent = tbody.rows.length + 1;
const step = document.createElement("td");
const code = document.createElement("code");
code.textContent = e.step;
step.appendChild(code);
tr.append(n, step, document.createElement("td"), document.createElement("td"));
tbody.appendChild(tr);
}
tr.dataset.seq = e.seq;
const status = tr.cells[2];
const wasFailed = status.textContent === "failed";
status.className = "status-" + e.status;
status.textContent = e.status;
if (e.message || wasFailed) { fillMessage(tr.cells[3], e.message); }
});
es.addEventListener("done", () => {
es.close();
window.location.reload();
});
es.addEventListener("error", (msg) => {
const note = document.getElementById("live-note");
if (note && msg.data) { note.textContent = " · " + msg.data; }
});
})();
</script>
{% endif %}
{% endblock %}