frontend: mint panel shows the full one-shot install command (node-26#4) #106

Merged
logan merged 1 commits from feat/mint-panel-install-command into main 2026-09-06 19:31:34 -04:00
+48 -2
View File
@@ -39,6 +39,23 @@ function EnrollmentTokensPanel() {
const [label, setLabel] = useState(""); const [label, setLabel] = useState("");
const [minting, setMinting] = useState(false); const [minting, setMinting] = useState(false);
const [justMinted, setJustMinted] = useState<string | null>(null); const [justMinted, setJustMinted] = useState<string | null>(null);
const [cmdCopied, setCmdCopied] = useState(false);
const [tokenCopied, setTokenCopied] = useState(false);
// 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).
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 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 \\
--c2-url ${c2Url} --mqtt-broker ${mqttBroker}`
: "";
const load = useCallback(() => { const load = useCallback(() => {
c2api.listEnrollmentTokens() c2api.listEnrollmentTokens()
@@ -87,11 +104,40 @@ function EnrollmentTokensPanel() {
<p className="text-xs text-indigo-200 font-mono mb-1"> <p className="text-xs text-indigo-200 font-mono mb-1">
New token — copy it now, it won&apos;t be shown again: New token — copy it now, it won&apos;t be shown again:
</p> </p>
<p className="text-xs text-indigo-100 font-mono break-all bg-gray-900 rounded px-2 py-1.5">{justMinted}</p>
<div className="flex items-start gap-2">
<p className="flex-1 text-xs text-indigo-100 font-mono break-all bg-gray-900 rounded px-2 py-1.5">{justMinted}</p>
<button
type="button"
onClick={() => navigator.clipboard?.writeText(justMinted).then(() => {
setTokenCopied(true); setTimeout(() => setTokenCopied(false), 2000);
})}
className="text-xs text-indigo-300 hover:text-indigo-200 px-2 py-1.5 transition-colors shrink-0"
>
{tokenCopied ? "Copied" : "Copy"}
</button>
</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):
</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>
<button
type="button"
onClick={() => navigator.clipboard?.writeText(installCmd).then(() => {
setCmdCopied(true); setTimeout(() => setCmdCopied(false), 2000);
})}
className="text-xs text-indigo-300 hover:text-indigo-200 px-2 py-1.5 transition-colors shrink-0"
>
{cmdCopied ? "Copied" : "Copy"}
</button>
</div>
<button <button
type="button" type="button"
onClick={() => setJustMinted(null)} onClick={() => setJustMinted(null)}
className="text-xs text-indigo-300 hover:text-indigo-200 mt-2 transition-colors" className="text-xs text-indigo-300 hover:text-indigo-200 mt-3 transition-colors"
> >
Dismiss Dismiss
</button> </button>