Feedback on the add-node page: the command was hard to read and hard to copy. Both the command and the six-digit code now use .otp-value-row / .otp-value - the wide monospace value with its copy button beside it that the provisioning dialog already uses. Reusing that rather than writing new CSS, because a second styling of the same idea is how the two drift apart. The command was a <textarea> carrying a class named `code-block` that exists nowhere in the stylesheet - I had invented it - so it rendered as a default textarea rather than the monospace block it was meant to be. It is now a <code>, which means it is written with setText rather than .value; setting .value on a <code> silently does nothing, which is the same class of silent failure as the missing toast element. The standalone "Copy the command" and "Copy the code" buttons at the bottom are gone: each value now has its own button next to it, so there is one obvious way to copy each thing and no need to match a button to a field. Done is now labelled to say what it does - "clear this from the page" - because the code is only in the page's memory and the controller cannot produce it again.
216 lines
9.5 KiB
HTML
216 lines
9.5 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="description" content="UmbraTrans administrative console">
|
|
<title>Administration · UmbraTrans</title>
|
|
<link rel="icon" type="image/png" href="../assets/umbratrans-emblem.png">
|
|
<link rel="stylesheet" href="../assets/styles.css">
|
|
</head>
|
|
<body class="admin-page">
|
|
<header class="admin-topbar">
|
|
<div class="admin-topbar-inner">
|
|
<a class="admin-brand" href="./index.html">
|
|
<img src="../assets/umbratools-emblem.png" alt="" width="28" height="28" onerror="this.style.visibility='hidden'">
|
|
<span>UmbraTrans</span>
|
|
</a>
|
|
<div class="admin-identity">
|
|
<strong id="admin-display-name">Checking session…</strong>
|
|
<small id="admin-identity">No session</small>
|
|
</div>
|
|
<a class="button button--quiet" href="../settings.html">Sign out</a>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="admin-main">
|
|
<nav class="admin-nav" aria-label="Sections">
|
|
<a class="admin-nav-link" href="./index.html#dashboard">Console</a>
|
|
<a class="admin-nav-link admin-nav-link--active" href="#nodes" data-view-link="nodes"
|
|
aria-current="page"><span aria-hidden="true">◈</span>Nodes</a>
|
|
<a class="admin-nav-link" href="./index.html">Everything else</a>
|
|
<a class="admin-nav-link" href="../settings.html">Your account</a>
|
|
</nav>
|
|
|
|
<section class="admin-view" id="view-nodes" data-view="nodes">
|
|
<div class="admin-heading">
|
|
<div>
|
|
<p class="eyebrow">Compute</p>
|
|
<h1>Nodes</h1>
|
|
<p>
|
|
Machines that can be given work. A node is <em>online</em> because its agent
|
|
heartbeated, not because it was created — so a node you have just added will
|
|
sit at <strong>provisioning</strong> until it runs the bootstrap, and a node
|
|
whose agent has stopped will read <strong>offline</strong> about 45 seconds
|
|
after its last heartbeat even though the stored status still says online.
|
|
</p>
|
|
</div>
|
|
<button class="button button--primary" type="button" id="open-add-node">
|
|
Add a node
|
|
</button>
|
|
</div>
|
|
|
|
<div class="card" id="add-node-card" hidden>
|
|
<div class="card-body">
|
|
<h2>Add a node</h2>
|
|
<p class="card-note">
|
|
This produces a one-line command and a six-digit code. Run the command on the
|
|
machine; it asks for the code, pairs with the controller, installs the agent
|
|
and registers it as a service.
|
|
</p>
|
|
|
|
<form id="add-node-form" novalidate>
|
|
<div class="field">
|
|
<label for="node-organization">Organization</label>
|
|
<select id="node-organization" name="organization_id" required
|
|
aria-describedby="node-organization-hint">
|
|
<option value="">Choose an organization…</option>
|
|
</select>
|
|
<small id="node-organization-hint" class="field-hint">
|
|
A node belongs to exactly one organization, so the node list and every
|
|
job it takes are scoped by it. A platform administrator has no
|
|
organization of their own and must choose one — that is why this is a
|
|
field rather than something inferred.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="node-name">Node name</label>
|
|
<input id="node-name" name="node_name" type="text" autocomplete="off"
|
|
maxlength="120" required
|
|
aria-describedby="node-name-hint">
|
|
<small id="node-name-hint" class="field-hint">
|
|
The name this machine will report. The pairing code is bound to it, so a
|
|
code that ends up in the wrong hands cannot pair a different machine.
|
|
Lowercase letters, digits and dashes read best on a terminal.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="node-locality">Where it is</label>
|
|
<input id="node-locality" name="locality" type="text" autocomplete="off"
|
|
maxlength="120" placeholder="192.168.123.238 or a name you will recognise">
|
|
<small class="field-hint">For you, not for the system. Helps when six nodes
|
|
report at once.</small>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="node-trust-domain">Trust domain</label>
|
|
<select id="node-trust-domain" name="trust_domain">
|
|
<option value="local">local — on my own network</option>
|
|
<option value="remote">remote — reached across a network I do not control</option>
|
|
<option value="cloud">cloud — a hosted machine</option>
|
|
</select>
|
|
<small class="field-hint">
|
|
Used to refuse work across a boundary you have said is a boundary.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="node-controller">Controller address</label>
|
|
<input id="node-controller" name="controller_url" type="text" autocomplete="off"
|
|
value="http://192.168.123.14:8442">
|
|
<small class="field-hint">
|
|
The address the node will reach us on, from the node's side. If the node is
|
|
on another machine, use an address <em>it</em> can resolve.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="field field--check">
|
|
<input id="node-as-root" name="as_root" type="checkbox">
|
|
<label for="node-as-root">
|
|
Expect to run as root on an exclusive machine
|
|
</label>
|
|
<small class="field-hint">
|
|
Chooses the expected install layout — <code>/opt/umbra</code> with a system
|
|
service running as its own account, or the user's own directories with a
|
|
user service. The script checks for root when it runs and takes whichever
|
|
branch is actually available, so this is a hint, not a requirement.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<button class="button button--primary" type="submit" id="add-node-submit">
|
|
Create the pairing code
|
|
</button>
|
|
<button class="button button--quiet" type="button" id="add-node-cancel">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" id="pairing-result-card" hidden>
|
|
<div class="card-body">
|
|
<h2>Run this on the machine</h2>
|
|
<p class="card-note" id="pairing-node-name-note"></p>
|
|
|
|
<div class="otp-panel">
|
|
<div class="otp-panel-head">
|
|
<span class="field-label" id="pairing-command-label">Command</span>
|
|
</div>
|
|
<p class="otp-panel-target">
|
|
Paste this into a terminal on the machine you are onboarding. It downloads the
|
|
bootstrap; read it if you want to — it is a script fetched over the network.
|
|
</p>
|
|
<!-- The value and its copy button sit side by side, reusing the one-time-code
|
|
row the provisioning dialog already uses. A command a person is going to
|
|
retype should not be something they have to select by hand. -->
|
|
<div class="otp-value-row">
|
|
<code class="otp-value" id="pairing-command"></code>
|
|
<button class="button button--ghost" type="button" id="pairing-copy-command">
|
|
Copy
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="otp-panel">
|
|
<div class="otp-panel-head">
|
|
<span class="field-label" id="pairing-code-label">Six-digit code</span>
|
|
</div>
|
|
<p class="otp-panel-target">
|
|
The bootstrap asks for this. It expires, and it is spent by the first machine
|
|
that uses it — so copy it before you log out of this page.
|
|
</p>
|
|
<div class="otp-value-row">
|
|
<code class="otp-value" id="pairing-code"></code>
|
|
<button class="button button--ghost" type="button" id="pairing-copy-code">
|
|
Copy
|
|
</button>
|
|
</div>
|
|
<p class="otp-hint" id="pairing-code-hint" role="status" aria-live="polite"></p>
|
|
</div>
|
|
|
|
<div class="alert alert--warn" id="pairing-warning" hidden>
|
|
<strong>Write this down now.</strong>
|
|
<span id="pairing-warning-text"></span>
|
|
If you lose it, create a new one — a lost code is not a problem, but a code
|
|
that has expired is a wasted trip.
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<button class="button button--quiet" type="button" id="pairing-done">
|
|
Done — clear this from the page
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div id="node-list" class="table-wrap" aria-live="polite"></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- showToast() in api.js looks for id="toast". With a different id every message -
|
|
success and failure alike - is appended to nothing and returns silently, which
|
|
looks exactly like the button not working. -->
|
|
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
|
|
|
<script src="../assets/api.js" defer></script>
|
|
<script src="../assets/nodes.js" defer></script>
|
|
</body>
|
|
</html> |