frontend: mint panel shows the full one-shot install command (node-26#4) #106
@@ -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't be shown again:
|
New token — copy it now, it won'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>
|
||||||
|
|||||||
Reference in New Issue
Block a user