diff --git a/frontend-modern/src/components/Settings/DockerAgents.tsx b/frontend-modern/src/components/Settings/DockerAgents.tsx index 43c7851..614a25a 100644 --- a/frontend-modern/src/components/Settings/DockerAgents.tsx +++ b/frontend-modern/src/components/Settings/DockerAgents.tsx @@ -3,13 +3,12 @@ import { useWebSocket } from '@/App'; import { Card } from '@/components/shared/Card'; import { formatRelativeTime, formatAbsoluteTime } from '@/utils/format'; import { MonitoringAPI } from '@/api/monitoring'; +import { SecurityAPI } from '@/api/security'; import { notificationStore } from '@/stores/notifications'; import type { SecurityStatus } from '@/types/config'; import type { DockerHost } from '@/types/api'; import type { APITokenRecord } from '@/api/security'; -import { showTokenReveal } from '@/stores/tokenReveal'; -import { DOCKER_MANAGE_SCOPE, DOCKER_REPORT_SCOPE } from '@/constants/apiScopes'; -import { useScopedTokenManager } from '@/hooks/useScopedTokenManager'; +import { DOCKER_REPORT_SCOPE } from '@/constants/apiScopes'; export const DockerAgents: Component = () => { const { state } = useWebSocket(); @@ -40,30 +39,10 @@ export const DockerAgents: Component = () => { const [removeActionLoading, setRemoveActionLoading] = createSignal<'queue' | 'force' | 'hide' | null>(null); const [showAdvancedOptions, setShowAdvancedOptions] = createSignal(false); const [securityStatus, setSecurityStatus] = createSignal(null); - const [showGenerateTokenModal, setShowGenerateTokenModal] = createSignal(false); - const [newTokenName, setNewTokenName] = createSignal(''); - const [generateError, setGenerateError] = createSignal(null); + const [isGeneratingToken, setIsGeneratingToken] = createSignal(false); + const [currentToken, setCurrentToken] = 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'; - - const { - token: apiToken, - isGeneratingToken, - generateToken, - } = useScopedTokenManager({ - scope: DOCKER_REPORT_SCOPE, - storageKey: 'dockerAgentToken', - legacyKeys: ['apiToken'], - }); - - createEffect(() => { - if (!apiToken()) { - setStepTwoComplete(false); - } - }); + const [tokenName, setTokenName] = createSignal(''); const pulseUrl = () => { if (typeof window !== 'undefined') { @@ -173,21 +152,7 @@ const modalCommandProgress = createMemo(() => { fetchSecurityStatus(); }); - 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); - } - }; + const showInstallCommand = () => !requiresToken() || Boolean(currentToken()); // Find the token record that matches the currently stored token const copyToClipboard = async (text: string): Promise => { @@ -221,35 +186,22 @@ const modalCommandProgress = createMemo(() => { } }; - const openGenerateTokenModal = () => { - setGenerateError(null); - const defaultName = `Docker host ${new Date().toISOString().slice(0, 10)}`; - setNewTokenName(defaultName); - setShowGenerateTokenModal(true); - setStepTwoComplete(false); - }; - - const handleCreateToken = async () => { + const handleGenerateToken = async () => { if (isGeneratingToken()) return; - setGenerateError(null); + setIsGeneratingToken(true); try { - const desiredName = newTokenName().trim() || `Docker host ${new Date().toISOString().slice(0, 10)}`; - const { token, record } = await generateToken(desiredName); + const name = tokenName().trim() || `Docker host ${new Date().toISOString().slice(0, 10)}`; + const { token, record } = await SecurityAPI.createToken(name, [DOCKER_REPORT_SCOPE]); - setShowGenerateTokenModal(false); - setNewTokenName(''); + setCurrentToken(token); setLatestRecord(record); - showTokenReveal({ - token, - record, - source: 'docker', - note: `Copy this token into the install command for your Docker agent. Scope: ${DOCKER_REPORT_SCOPE}.`, - }); - notificationStore.success('New Docker reporting token generated and added to the install command.', 6000); + setTokenName(''); + notificationStore.success('Token generated and inserted into the command below.', 4000); } catch (err) { console.error('Failed to generate API token', err); - setGenerateError('Failed to generate API token. Confirm you are signed in as an administrator.'); - notificationStore.error('Failed to generate API token', 6000); + notificationStore.error('Failed to generate API token. Confirm you are signed in as an administrator.', 6000); + } finally { + setIsGeneratingToken(false); } }; @@ -415,49 +367,6 @@ WantedBy=multi-user.target`; return (
- {/* Summary Stats */} -
-
-
-
- - - -
-
-

{dockerHosts().length}

-

Docker Hosts

-
-
-
-
-
-
- - - -
-
-

{dockerHosts().filter(h => h.status?.toLowerCase() === 'online').length}

-

Online Now

-
-
-
-
-
-
- - - -
-
-

{dockerHosts().reduce((sum, h) => sum + (h.containers?.length || 0), 0)}

-

Total Containers

-
-
-
-
-

Setup & Management

@@ -503,99 +412,58 @@ WantedBy=multi-user.target`; -
+
-

{tokenStepLabel}

+

Generate API token

- Generate a fresh credential for this Docker host. Each token created here is limited to the {DOCKER_REPORT_SCOPE} scope. -

-

- Need lifecycle control or bespoke scopes? Visit Security → API tokens to craft a custom token and add {DOCKER_MANAGE_SCOPE} if you plan to issue lifecycle commands. + Create a fresh token scoped to {DOCKER_REPORT_SCOPE}

- -
- {generateError()} -
-
+
+ setTokenName(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && !isGeneratingToken()) { + handleGenerateToken(); + } + }} + placeholder="Token name (optional)" + class="flex-1 rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:focus:border-blue-400 dark:focus:ring-blue-900/60" + /> + +
-
+
- Token {latestRecord()?.name} created ({latestRecord()?.prefix}…{latestRecord()?.suffix}). Copy the full value from the pop-up and store it securely—this is the only time it is shown. + Token {latestRecord()?.name} created and inserted into the command below.
- - - - -
-
- {stepTwoComplete() - ? 'Token inserted. Proceed to the install command below.' - : 'Stored token detected. Press confirm to insert it into the command.'} -
- -
-
- -
-
- Tokens are optional on this Pulse instance. Confirm to generate commands without embedding a token. -
- -
-
- - +
- +
- {getInstallCommandTemplate().replace(TOKEN_PLACEHOLDER, apiToken() || TOKEN_PLACEHOLDER)} + {getInstallCommandTemplate().replace(TOKEN_PLACEHOLDER, currentToken() || TOKEN_PLACEHOLDER)}

- The installer downloads the agent, creates a systemd service, and starts reporting automatically. + Run as root on your Docker host. The installer downloads the agent, creates a systemd service, and starts reporting automatically.

- +

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

- -

Confirm the no-token setup to continue.

-
@@ -710,57 +575,6 @@ WantedBy=multi-user.target`; - {/* Generate token modal */} - -
-
-
-

Generate a new Docker API token

-

- Pulse will create a scoped token and insert it into the install command. You can manage or revoke tokens anytime from Security Settings. -

-
-
- - setNewTokenName(event.currentTarget.value)} - class="w-full rounded border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:focus:border-blue-400 dark:focus:ring-blue-900/60" - placeholder="Docker host token" - /> -

- Friendly names make it easier to audit tokens later (e.g. docker-prod-01). -

-
-
- - -
-
-
-
- {/* Remove Docker Host Modal */}
@@ -1240,51 +1054,6 @@ WantedBy=multi-user.target`;
- - {/* Info Cards */} -
- -
-
- - - -
-
-

Agent-based monitoring

-

- Docker hosts run the Pulse agent and push metrics to this server. No inbound firewall rules required. -

-
-
-
- - -
-
- - - -
-
-

Agent requirements

-

- The agent needs access to the Docker socket (/var/run/docker.sock) and network connectivity to this Pulse instance. -

-
-
-
-
); }; diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index f685ebd..cedd654 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -2929,10 +2929,6 @@ const Settings: Component = (props) => { {/* Docker Tab */}
-
@@ -2966,10 +2962,6 @@ const Settings: Component = (props) => { {/* Host Agents */}
-