From 45e6ff48eba4fbe1a5fb4482515f111192f58ac4 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 23 Oct 2025 18:23:38 +0000 Subject: [PATCH] fix: gate agent commands until token step confirmed --- .../src/components/Settings/DockerAgents.tsx | 87 +++++++++++++++++-- .../src/components/Settings/HostAgents.tsx | 83 ++++++++++++++++-- 2 files changed, 160 insertions(+), 10 deletions(-) diff --git a/frontend-modern/src/components/Settings/DockerAgents.tsx b/frontend-modern/src/components/Settings/DockerAgents.tsx index 6980c5d..d6ea5a6 100644 --- a/frontend-modern/src/components/Settings/DockerAgents.tsx +++ b/frontend-modern/src/components/Settings/DockerAgents.tsx @@ -45,6 +45,7 @@ export const DockerAgents: Component = () => { const [newTokenName, setNewTokenName] = createSignal(''); const [generateError, setGenerateError] = createSignal(null); const [latestRecord, setLatestRecord] = createSignal(null); + const [stepTwoComplete, setStepTwoComplete] = createSignal(false); const tokenStepLabel = 'Step 2 · Generate API token'; const commandStepLabel = 'Step 3 · Install command'; @@ -59,6 +60,12 @@ export const DockerAgents: Component = () => { legacyKeys: ['apiToken'], }); + createEffect(() => { + if (!apiToken()) { + setStepTwoComplete(false); + } + }); + const pulseUrl = () => { if (typeof window !== 'undefined') { const protocol = window.location.protocol; @@ -167,7 +174,21 @@ const modalCommandProgress = createMemo(() => { fetchSecurityStatus(); }); - const commandReady = () => !requiresToken() || Boolean(apiToken()); + const tokenReady = () => !requiresToken() || Boolean(apiToken()); + const commandsUnlocked = () => tokenReady() && stepTwoComplete(); + + const acknowledgeTokenUse = () => { + if (!requiresToken()) { + setStepTwoComplete(true); + return; + } + if (apiToken()) { + setStepTwoComplete(true); + notificationStore.success('Token ready to use in the install command.', 3500); + } else { + notificationStore.info('Generate or select a token before continuing.', 4000); + } + }; // Find the token record that matches the currently stored token const copyToClipboard = async (text: string): Promise => { @@ -206,6 +227,7 @@ const modalCommandProgress = createMemo(() => { const defaultName = `Docker host ${new Date().toISOString().slice(0, 10)}`; setNewTokenName(defaultName); setShowGenerateTokenModal(true); + setStepTwoComplete(false); }; const handleCreateToken = async () => { @@ -218,6 +240,7 @@ const modalCommandProgress = createMemo(() => { setShowGenerateTokenModal(false); setNewTokenName(''); setLatestRecord(record); + setStepTwoComplete(true); showTokenReveal({ token, record, @@ -472,10 +495,56 @@ WantedBy=multi-user.target`; > {isGeneratingToken() ? 'Generating…' : 'Generate token'} + + +
+
+ {stepTwoComplete() + ? 'Token ready. You can continue to the install command.' + : 'Stored token detected. Confirm to insert it into the install command.'} +
+ +
+
- + +
+
+ Tokens are optional on this Pulse instance. Confirm to continue to the install command. +
+ +
+
+ +
@@ -488,10 +557,9 @@ WantedBy=multi-user.target`; window.showToast(success ? 'success' : 'error', success ? 'Copied!' : 'Failed to copy'); } }} - disabled={requiresToken() && !apiToken()} - class="px-3 py-1.5 text-xs font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed bg-blue-600 text-white hover:bg-blue-700" + class="px-3 py-1.5 text-xs font-medium rounded transition-colors bg-blue-600 text-white hover:bg-blue-700" > - Copy + Copy first command
@@ -505,6 +573,15 @@ WantedBy=multi-user.target`;
+ +

+ Generate a new token or confirm the stored one to unlock the install command. +

+
+ +

Confirm the no-token setup to continue.

+
+
Advanced options (uninstall & manual install) diff --git a/frontend-modern/src/components/Settings/HostAgents.tsx b/frontend-modern/src/components/Settings/HostAgents.tsx index 78566c8..1f7b5bf 100644 --- a/frontend-modern/src/components/Settings/HostAgents.tsx +++ b/frontend-modern/src/components/Settings/HostAgents.tsx @@ -1,4 +1,4 @@ -import { Component, For, Show, createMemo, createSignal, onMount } from 'solid-js'; +import { Component, For, Show, createEffect, createMemo, createSignal, onMount } from 'solid-js'; import type { JSX } from 'solid-js'; import { useWebSocket } from '@/App'; import type { Host } from '@/types/api'; @@ -168,6 +168,7 @@ export const HostAgents: Component = (props) => { const [newTokenName, setNewTokenName] = createSignal(''); const [generateError, setGenerateError] = createSignal(null); const [latestRecord, setLatestRecord] = createSignal(null); + const [stepTwoComplete, setStepTwoComplete] = createSignal(false); const { token: apiToken, @@ -180,6 +181,12 @@ export const HostAgents: Component = (props) => { legacyKeys: ['apiToken'], }); + createEffect(() => { + if (!apiToken()) { + setStepTwoComplete(false); + } + }); + const hosts = createMemo(() => { const list = state.hosts ?? []; const filters = platformFilters[variant]; @@ -234,13 +241,28 @@ export const HostAgents: Component = (props) => { return true; }; - const commandReady = () => !requiresToken() || Boolean(apiToken()); + const tokenReady = () => !requiresToken() || Boolean(apiToken()); + const commandsUnlocked = () => tokenReady() && stepTwoComplete(); + + const acknowledgeTokenUse = () => { + if (!requiresToken()) { + setStepTwoComplete(true); + return; + } + if (apiToken()) { + setStepTwoComplete(true); + notificationStore.success('Token ready to embed in the install commands.', 3500); + } else { + notificationStore.info('Generate or select a token before continuing.', 4000); + } + }; const openGenerateTokenModal = () => { setGenerateError(null); const defaultName = `Host agent ${new Date().toISOString().slice(0, 10)}`; setNewTokenName(defaultName); setShowGenerateTokenModal(true); + setStepTwoComplete(false); }; const handleCreateToken = async () => { @@ -254,6 +276,7 @@ export const HostAgents: Component = (props) => { setShowGenerateTokenModal(false); setNewTokenName(''); setLatestRecord(record); + setStepTwoComplete(true); showTokenReveal({ token, record, @@ -382,6 +405,7 @@ export const HostAgents: Component = (props) => { setGenerateError(null); setLatestRecord(null); setApiToken(null); + setStepTwoComplete(false); }} >

{option.label}

@@ -431,10 +455,56 @@ export const HostAgents: Component = (props) => { > {isGeneratingToken() ? 'Generating…' : 'Generate token'} + + +
+
+ {stepTwoComplete() + ? 'Token ready. You can continue to the commands.' + : 'Stored token detected. Confirm to insert it into the commands below.'} +
+ +
+
- + +
+
+ Tokens are optional on this Pulse instance. Confirm to continue to the install commands. +
+ +
+
+ +

{commandStepLabel()}

@@ -487,11 +557,14 @@ export const HostAgents: Component = (props) => {
- +

- Select or generate an API token to embed it in the install commands. + Generate a new token or confirm the stored one to unlock the install commands.

+ +

Confirm the no-token setup to continue.

+