frontend: install command uses the node id from the mint form (node-26#4)

The mint panel's copy command hard-coded --node-id node-XXX. Now the label
just entered (the operator types the node id there — placeholder relabeled
"Node ID, e.g. node-003") is captured on mint and interpolated into the
command: spaces → dashes, non [A-Za-z0-9_-] stripped (install.sh's rule),
falling back to node-XXX only if that yields nothing. The "edit node-XXX"
hint now only shows in the fallback case.

Not typechecked (no node/npm here); one useState<string|null>, one derived
string, a JSX conditional. `next build` in deploy.yml gates it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-09-06 20:13:23 -04:00
co-authored by Claude Sonnet 5
parent de03f5bcaf
commit 93fa3a6054
+19 -8
View File
@@ -41,19 +41,26 @@ function EnrollmentTokensPanel() {
const [justMinted, setJustMinted] = useState<string | null>(null);
const [cmdCopied, setCmdCopied] = useState(false);
const [tokenCopied, setTokenCopied] = useState(false);
// The label the operator typed for the token that was just minted — used as
// the node id in the install command below. Captured on mint because `label`
// itself is cleared afterward.
const [mintedLabel, setMintedLabel] = useState<string | null>(null);
// The paste-ready one-shot install command for a fresh Pi. node id and the
// MQTT broker host aren't known here — left as placeholders the operator
// edits. C2 URL comes from this deployment's own config; the broker is the
// documented mqtt.<domain> sibling of the api host (install.sh header).
// The paste-ready one-shot install command for a fresh Pi. The node id comes
// from the label just entered (spaces → dashes; install.sh requires
// [A-Za-z0-9_-]); if that yields nothing it falls back to a node-XXX
// placeholder. The MQTT broker host is the documented mqtt.<domain> sibling
// of the api host (install.sh header) — a DNS assumption the operator checks.
const c2Url = (process.env.NEXT_PUBLIC_C2_URL ?? "https://api.example.net").replace(/\/$/, "");
const mqttBroker = (() => {
try { return `mqtt.${new URL(c2Url).hostname.replace(/^api\./, "")}`; }
catch { return "mqtt.example.net"; }
})();
const nodeIdForCmd =
(mintedLabel ?? "").trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9_-]/g, "") || "node-XXX";
const installCmd = justMinted
? `curl -fsSL https://git.vpn.cusano.net/logan/node-26/raw/tag/v1/install.sh \\
| sudo bash -s -- --token ${justMinted} --node-id node-XXX \\
| sudo bash -s -- --token ${justMinted} --node-id ${nodeIdForCmd} \\
--c2-url ${c2Url} --mqtt-broker ${mqttBroker}`
: "";
@@ -74,6 +81,7 @@ function EnrollmentTokensPanel() {
try {
const result = await c2api.mintEnrollmentToken(label.trim());
setJustMinted(result.token);
setMintedLabel(label.trim());
setLabel("");
load();
} catch (err) {
@@ -119,7 +127,10 @@ function EnrollmentTokensPanel() {
</div>
<p className="text-xs text-indigo-200 font-mono mt-3 mb-1">
…or run this on a fresh Pi (edit <span className="text-indigo-100">node-XXX</span> and the broker host):
…or run this on a fresh Pi{" "}
{nodeIdForCmd === "node-XXX"
? <>(edit <span className="text-indigo-100">node-XXX</span> and check the broker host)</>
: <>(check the broker host)</>}:
</p>
<div className="flex items-start gap-2">
<pre className="flex-1 text-xs text-indigo-100 font-mono whitespace-pre-wrap break-all bg-gray-900 rounded px-2 py-1.5">{installCmd}</pre>
@@ -136,7 +147,7 @@ function EnrollmentTokensPanel() {
<button
type="button"
onClick={() => setJustMinted(null)}
onClick={() => { setJustMinted(null); setMintedLabel(null); }}
className="text-xs text-indigo-300 hover:text-indigo-200 mt-3 transition-colors"
>
Dismiss
@@ -151,7 +162,7 @@ function EnrollmentTokensPanel() {
<input
value={label}
onChange={(e) => setLabel(e.target.value)}
placeholder="Label, e.g. 'node-003 field kit'"
placeholder="Node ID, e.g. node-003"
className="flex-1 min-w-[12rem] bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-white text-sm focus:outline-none focus:border-indigo-500"
/>
<Button type="submit" size="sm" disabled={minting || !label.trim()}>