diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 1a6f0857c..476746309 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,48 +1,39 @@ { "version": 1, - "base_sha": "9ddfc69ec71d8db6b313522f54c2f68db88ec914", - "verified_at": "2026-08-26T22:30:00Z", + "base_sha": "ee35d178b5c7dad09877c20bf20acd2aba8f0b51", + "verified_at": "2026-08-26T22:53:00Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx", - "frontend-modern/src/components/Workloads/diskListModel.ts", - "frontend-modern/src/components/shared/DetailSectionTable.tsx", - "frontend-modern/src/components/shared/ProgressBar.tsx", - "frontend-modern/src/components/shared/detailSectionModel.ts" + "frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx", + "frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx" ], "content_sha256": { - "frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx": "21bf8fd35d7c79f9bceade2addf8a952a10dfac724a90d3f892769d1d3dbdde1", - "frontend-modern/src/components/Workloads/diskListModel.ts": "7e446cbc5b6b87abb12af69e4dc5a78ae26258ffbbc8f5f70235924d3d535084", - "frontend-modern/src/components/shared/DetailSectionTable.tsx": "eff0236a3d524a041aefe18fa021cc619f61337fb9b80e74bbea07789a90e69e", - "frontend-modern/src/components/shared/ProgressBar.tsx": "8fda21a7dd22a1c00557686604dc07d7b69011d690a9618cf480eaf939e61103", - "frontend-modern/src/components/shared/detailSectionModel.ts": "42a6f8e70a03303d366efe8f8d490b1bf1bc8bfd6338d0510db35890d19ddeec" + "frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx": "b323df464d7d42fea5b38f553afce74cf3d56c4459199f95a45fc4d48ef17084", + "frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx": "e7fbdac65fe0ec225bdf898eac4bad112cdbd490322d37861677faf4be6ae74e" }, "routes": [ - "/proxmox/overview" + "/settings/infrastructure?add=docker" ], "viewports": [ { "width": 1440, "height": 900 }, - { - "width": 768, - "height": 900 - }, { "width": 390, "height": 844 } ], "states": [ - "multi-filesystem Proxmox guest expanded to its Overview tab on desktop", - "same expanded filesystem state in the narrow table layout at tablet width", - "same expanded filesystem state at phone width with wrapped detail text" + "Docker agent installer immediately after manually generating an install token", + "token-only reveal reopened from the generated-token confirmation", + "token-only reveal at phone width" ], "interactions": [ - "expanded checkout-web-373 and confirmed both known filesystems retain percentage, used/total, and type text", - "confirmed each filesystem exposes an accessible progress value matching its rendered fill", - "resized through 1440x900, 768x900, and 390x844 and confirmed bar alignment, readable wrapping, and zero document-level horizontal overflow", - "reloaded the rebased current build and reconfirmed matching progress values plus zero document-level horizontal overflow at desktop and phone widths" + "generated a fresh Docker agent install token and confirmed the token-only dialog opened automatically", + "confirmed the dialog exposes the token value and Copy token action without an install command", + "dismissed the token dialog and reopened it with Show token only without minting another credential", + "confirmed the token wraps, the copy action spans the mobile card, and the page has no horizontal overflow at 390x844", + "confirmed the built frontend produced no browser console errors during the verified flows" ] } diff --git a/frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx b/frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx index d01d35352..8c857e02f 100644 --- a/frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx +++ b/frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx @@ -380,29 +380,39 @@ export const InfrastructureInstallerSection: Component -
- +
+ + + + + {state.latestTokenSource() === 'setup_handoff' ? ( + <> + First-host install token {state.latestRecord()?.name}{' '} + prepared automatically. Commands below already include this credential. + + ) : ( + <> + Install token {state.latestRecord()?.name} created. Commands + below now include this credential. + + )} + +
+
diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx index d4e381ea1..66b59abf6 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx @@ -261,6 +261,7 @@ describe('infrastructure operations model', () => { ); expect(infrastructureInstallerSectionSource).toContain('Generate install token'); expect(infrastructureInstallerSectionSource).toContain('Generate token'); + expect(infrastructureInstallerSectionSource).toContain('Show token only'); expect(infrastructureInstallerSectionSource).toContain( 'This is the Pulse Agent handoff from first-run setup inside Add infrastructure.', ); diff --git a/frontend-modern/src/components/Settings/__tests__/useInfrastructureOperationsState.branchcov0722pm.test.tsx b/frontend-modern/src/components/Settings/__tests__/useInfrastructureOperationsState.branchcov0722pm.test.tsx index 568026888..d85dae041 100644 --- a/frontend-modern/src/components/Settings/__tests__/useInfrastructureOperationsState.branchcov0722pm.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/useInfrastructureOperationsState.branchcov0722pm.test.tsx @@ -27,6 +27,7 @@ const mocks = vi.hoisted(() => ({ notificationSuccess: vi.fn(), notificationError: vi.fn(), notificationInfo: vi.fn(), + showTokenReveal: vi.fn(), loggerError: vi.fn(), loggerWarn: vi.fn(), navigate: vi.fn(), @@ -69,6 +70,10 @@ vi.mock('@/stores/notifications', () => ({ }, })); +vi.mock('@/stores/tokenReveal', () => ({ + showTokenReveal: mocks.showTokenReveal, +})); + vi.mock('@/utils/logger', () => ({ logger: { error: mocks.loggerError, @@ -194,6 +199,30 @@ describe('useInfrastructureOperationsState command-building closures', () => { const cmd = state.getUninstallCommand(); expect(cmd).toContain("--token 'tok-1'"); expect(cmd).not.toContain(''); + expect(mocks.showTokenReveal).toHaveBeenCalledWith({ + token: 'tok-1', + record: expect.objectContaining({ id: 'rec-1', name: 'Agent' }), + source: 'agent', + note: expect.stringContaining('PULSE_TOKEN or Compose environment configuration'), + }); + dispose(); + }); + + it('reopens the token-only reveal without minting another credential', async () => { + const { state, dispose } = mountHook(); + await flushAsync(); + await state.handleGenerateToken(); + mocks.showTokenReveal.mockClear(); + + state.showCurrentTokenOnly(); + + expect(mocks.createHostAgentInstallToken).toHaveBeenCalledTimes(1); + expect(mocks.showTokenReveal).toHaveBeenCalledWith({ + token: 'tok-1', + record: expect.objectContaining({ id: 'rec-1', name: 'Agent' }), + source: 'agent', + note: expect.stringContaining('PULSE_TOKEN or Compose environment configuration'), + }); dispose(); }); diff --git a/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx b/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx index 015c65ce3..e9228c638 100644 --- a/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx +++ b/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx @@ -4,6 +4,7 @@ import { MonitoringAPI } from '@/api/monitoring'; import { NodesAPI } from '@/api/nodes'; import { SecurityAPI, type APITokenRecord } from '@/api/security'; import { notificationStore } from '@/stores/notifications'; +import { showTokenReveal } from '@/stores/tokenReveal'; import type { AgentLookupResponse, ConnectedInfrastructureItem } from '@/types/api'; import type { SecurityStatus } from '@/types/config'; import { copyToClipboard } from '@/utils/clipboard'; @@ -45,6 +46,8 @@ const SETUP_HANDOFF_INSTALL_STEPS = new Set([ 'docker', 'kubernetes', ]); +const INSTALL_TOKEN_REVEAL_NOTE = + 'Copy just this token for PULSE_TOKEN or Compose environment configuration. The generated install commands remain available after you close this dialog.'; const isActiveInfrastructureItem = (item: ConnectedInfrastructureItem) => item.status === 'active'; @@ -245,6 +248,19 @@ Pulse prepares the first-host install token from setup so you can move straight notificationStore.success('Confirmed install commands without an API token.', 3500); }; + const showCurrentTokenOnly = () => { + const token = currentToken(); + const record = latestRecord(); + if (!token || !record) return; + + showTokenReveal({ + token, + record, + source: 'agent', + note: INSTALL_TOKEN_REVEAL_NOTE, + }); + }; + const generateInstallToken = async ( source: 'manual' | 'setup_handoff', options: { notifySuccess?: boolean } = {}, @@ -271,6 +287,14 @@ Pulse prepares the first-host install token from setup so you can move straight setTokenName(''); setConfirmedNoToken(false); setSetupHandoffAutoTokenFailed(false); + if (source === 'manual') { + showTokenReveal({ + token, + record, + source: 'agent', + note: INSTALL_TOKEN_REVEAL_NOTE, + }); + } if (options.notifySuccess) { notificationStore.success( withCommands @@ -606,6 +630,7 @@ Pulse prepares the first-host install token from setup so you can move straight setupHandoff, setupHandoffAutoTokenFailed, setupHandoffAutoTokenPending, + showCurrentTokenOnly, tokenName, }; }; diff --git a/frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx b/frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx index 29b4dcd75..67f4852d0 100644 --- a/frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx +++ b/frontend-modern/src/features/alerts/AlertDeliveryLogCard.tsx @@ -42,8 +42,16 @@ export const mergeDeliveryLogRows = ( heldEvents: AlertEvent[], ): DeliveryLogRow[] => { const rows: DeliveryLogRow[] = [ - ...entries.map((entry): DeliveryLogRow => ({ kind: 'attempt', timestamp: entry.timestamp, entry })), - ...heldEvents.map((event): DeliveryLogRow => ({ kind: 'held', timestamp: event.occurredAt, event })), + ...entries.map((entry): DeliveryLogRow => ({ + kind: 'attempt', + timestamp: entry.timestamp, + entry, + })), + ...heldEvents.map((event): DeliveryLogRow => ({ + kind: 'held', + timestamp: event.occurredAt, + event, + })), ]; return rows.sort((a, b) => { const at = new Date(a.timestamp).getTime() || 0; diff --git a/frontend-modern/src/features/alerts/useAlertDestinationsTabState.ts b/frontend-modern/src/features/alerts/useAlertDestinationsTabState.ts index 9af30d164..68320d633 100644 --- a/frontend-modern/src/features/alerts/useAlertDestinationsTabState.ts +++ b/frontend-modern/src/features/alerts/useAlertDestinationsTabState.ts @@ -43,8 +43,13 @@ export function useAlertDestinationsTabState(props: AlertDestinationsTabStatePro deliveryNeedsAttention, loadDeliveryHealth, } = useNotificationDeliveryHealth(); - const { deliveryLog, deliveryLogUnavailable, refreshingDeliveryLog, heldEvents, loadDeliveryLog } = - useNotificationDeliveryLog(); + const { + deliveryLog, + deliveryLogUnavailable, + refreshingDeliveryLog, + heldEvents, + loadDeliveryLog, + } = useNotificationDeliveryLog(); const webhookState = useAlertWebhookDestinationsState(); const isLoading = createMemo(