From 5692325031ff7e2e5ce0f9d85dc24df28f85e870 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 27 May 2026 09:29:51 +0100 Subject: [PATCH] Promote Machines web interface editor --- .../Infrastructure/ResourceDetailDrawer.tsx | 3 + .../ResourceDetailDrawerOverviewTab.tsx | 223 +++++++++--------- .../useResourceDetailDrawerState.ts | 11 +- .../shared/WebInterfaceUrlField.tsx | 2 +- .../__tests__/WebInterfaceUrlField.test.tsx | 2 +- .../PlatformResourceDetailTableRow.tsx | 7 + .../standalone/AgentsMachinesTable.tsx | 30 ++- .../__tests__/AgentsMachinesTable.test.tsx | 11 +- 8 files changed, 174 insertions(+), 115 deletions(-) diff --git a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx index 6129ab8be..7bbdfbfad 100644 --- a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx +++ b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx @@ -26,6 +26,7 @@ interface ResourceDetailDrawerProps { onClose?: () => void; presentation?: ResourceDetailDrawerPresentation; resolveResourceLabel?: (resourceId: string) => string | null | undefined; + initialShowAccessContext?: boolean; initialShowTrueNASDetails?: boolean; } @@ -55,6 +56,7 @@ const DrawerContent: Component = (props) => { resource: props.resource, presentation: presentation(), resolveResourceLabel: props.resolveResourceLabel, + initialShowAccessContext: props.initialShowAccessContext, initialShowTrueNASDetails: props.initialShowTrueNASDetails, }); const headingId = () => `resource-detail-drawer-heading-${props.resource.id}`; @@ -337,6 +339,7 @@ export const ResourceDetailDrawer: Component = (props onClose={props.onClose} presentation={props.presentation} resolveResourceLabel={props.resolveResourceLabel} + initialShowAccessContext={props.initialShowAccessContext} initialShowTrueNASDetails={props.initialShowTrueNASDetails} /> ); diff --git a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx index f65a3da7d..fc9eb6cb9 100644 --- a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx +++ b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx @@ -181,6 +181,113 @@ const timelineSourceAdapterOptions: Array<{ })), ]; +const AccessDisclosure: Component<{ + drawer: UseResourceDetailDrawerStateResult; + class?: string; +}> = (props) => ( + props.drawer.setShowAccessContext((value) => !value)} + showLabel="Show access" + hideLabel="Hide access" + class={props.class} + contentClass="mt-3 space-y-3" + dataTestId="resource-access-section" + > + 0}> +
+
Links
+
+ + {(link) => ( + + {link.compactLabel} + + )} + +
+
+
+ + + {(config) => ( +
+ + + +
+
+
+
+ Analysis +
+ +
+ {props.drawer.discoveryContextSummary()} +
+
+
+ +
+ + + +
+ {getDiscoveryLoadingState().text} +
+ } + > + +
+
+
+
+
+ )} +
+
+); + export const ResourceDetailDrawerOverviewTab: Component = ( props, ) => { @@ -204,9 +311,15 @@ export const ResourceDetailDrawerOverviewTab: Component compactTableRow() && drawer.hasTrueNASDetails(); const shouldPromoteHostDetails = () => compactTableRow() && isPulseAgentPlatformResource(resource) && drawer.hasHostDetails(); + const shouldPromoteAccessContext = () => + compactTableRow() && drawer.showAccessContext() && drawer.hasAccessContext(); return (
+ + + + @@ -518,7 +631,7 @@ export const ResourceDetailDrawerOverviewTab: Component @@ -530,112 +643,8 @@ export const ResourceDetailDrawerOverviewTab: Component - - drawer.setShowAccessContext((value) => !value)} - showLabel="Show access" - hideLabel="Hide access" - class="h-full" - contentClass="mt-3 space-y-3" - dataTestId="resource-access-section" - > - 0}> -
-
- Links -
-
- - {(link) => ( - - {link.compactLabel} - - )} - -
-
-
- - - {(config) => ( -
- - - -
-
-
-
- Analysis -
- -
- {drawer.discoveryContextSummary()} -
-
-
- -
- - - -
- - {getDiscoveryLoadingState().text} - -
- } - > - -
-
-
-
-
- )} -
-
+ + diff --git a/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts b/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts index a84d8dbd7..f290d264d 100644 --- a/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts +++ b/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts @@ -23,6 +23,7 @@ export interface UseResourceDetailDrawerStateOptions { resource: Resource; resolveResourceLabel?: (resourceId: string) => string | null | undefined; presentation?: ResourceDetailDrawerPresentation; + initialShowAccessContext?: boolean; initialShowTrueNASDetails?: boolean; } @@ -36,7 +37,9 @@ export const useResourceDetailDrawerState = (options: UseResourceDetailDrawerSta const [copied, setCopied] = createSignal(false); const [showReportModal, setShowReportModal] = createSignal(false); const [showHistoryFilters, setShowHistoryFilters] = createSignal(false); - const [showAccessContext, setShowAccessContext] = createSignal(false); + const [showAccessContext, setShowAccessContext] = createSignal( + options.initialShowAccessContext === true, + ); const [showInvestigationContext, setShowInvestigationContext] = createSignal(false); const [showDiscoveryContext, setShowDiscoveryContext] = createSignal(false); const [showHostDetails, setShowHostDetails] = createSignal( @@ -73,6 +76,12 @@ export const useResourceDetailDrawerState = (options: UseResourceDetailDrawerSta } }); + createEffect(() => { + if (options.initialShowAccessContext === true) { + setShowAccessContext(true); + } + }); + createEffect(() => { const current = activeTab(); const available = new Set(derived.tabs().map((tab) => tab.id)); diff --git a/frontend-modern/src/components/shared/WebInterfaceUrlField.tsx b/frontend-modern/src/components/shared/WebInterfaceUrlField.tsx index b375d9fb9..81b0f000d 100644 --- a/frontend-modern/src/components/shared/WebInterfaceUrlField.tsx +++ b/frontend-modern/src/components/shared/WebInterfaceUrlField.tsx @@ -172,7 +172,7 @@ export const WebInterfaceUrlField: Component = (props

- Add a URL to quickly access this {state.targetLabel()}'s web interface from Workloads. + Add a URL to quickly access this {state.targetLabel()}'s web interface from Pulse.

diff --git a/frontend-modern/src/components/shared/__tests__/WebInterfaceUrlField.test.tsx b/frontend-modern/src/components/shared/__tests__/WebInterfaceUrlField.test.tsx index 9746e7869..dae652534 100644 --- a/frontend-modern/src/components/shared/__tests__/WebInterfaceUrlField.test.tsx +++ b/frontend-modern/src/components/shared/__tests__/WebInterfaceUrlField.test.tsx @@ -136,7 +136,7 @@ describe('WebInterfaceUrlField', () => { expect( await screen.findByText( - "Add a URL to quickly access this workload's web interface from Workloads.", + "Add a URL to quickly access this workload's web interface from Pulse.", ), ).toBeInTheDocument(); }); diff --git a/frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx b/frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx index c9bb6b1a2..fd9acc744 100644 --- a/frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx +++ b/frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx @@ -11,6 +11,7 @@ export type PlatformResourceDetailState = { expandedResourceId: () => string | null; isExpanded: (resource: ResourceLike) => boolean; detailRowId: (resource: ResourceLike) => string; + open: (resource: ResourceLike) => void; toggle: (resource: ResourceLike) => void; close: (resource?: ResourceLike) => void; handleActivationKey: ( @@ -31,6 +32,9 @@ export function createPlatformResourceDetailState(options: { const isExpanded = (resource: ResourceLike): boolean => expandedResourceId() === resource.id; const detailRowId = (resource: ResourceLike): string => `${options.idPrefix}-${resource.id}`; + const open = (resource: ResourceLike) => { + setExpandedResourceId(resource.id); + }; const toggle = (resource: ResourceLike) => { setExpandedResourceId((current) => (current === resource.id ? null : resource.id)); }; @@ -51,6 +55,7 @@ export function createPlatformResourceDetailState(options: { expandedResourceId, isExpanded, detailRowId, + open, toggle, close, handleActivationKey, @@ -70,6 +75,7 @@ export const PlatformResourceDetailTableRow: Component<{ detailRowId: string; colSpan: number; resolveResourceLabel?: (resourceId: string) => string | null | undefined; + initialShowAccessContext?: boolean; initialShowTrueNASDetails?: boolean; onClose?: () => void; }> = (props) => { @@ -92,6 +98,7 @@ export const PlatformResourceDetailTableRow: Component<{ resource={props.resource} presentation="table-row" resolveResourceLabel={props.resolveResourceLabel} + initialShowAccessContext={props.initialShowAccessContext} initialShowTrueNASDetails={initialShowTrueNASDetails()} onClose={props.onClose} /> diff --git a/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx b/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx index d091c3d38..17940a8e1 100644 --- a/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx +++ b/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx @@ -1008,6 +1008,7 @@ export const AgentsMachinesTable: Component<{ ); const drawer = createPlatformResourceDetailState({ idPrefix: 'agents-machine-drawer' }); const [agentMetadataById, setAgentMetadataById] = createSignal>({}); + const [accessTargetResourceId, setAccessTargetResourceId] = createSignal(null); const visibleColumns = createMemo( () => columnVisibility.visibleColumns() as AgentMachineColumn[], ); @@ -1209,6 +1210,22 @@ export const AgentsMachinesTable: Component<{ const detailRowId = () => drawer.detailRowId(machine); const agentMetadataId = () => agentMetadataIdFor(machine); const savedWebInterfaceUrl = () => savedAgentCustomUrlFor(agentMetadataId()); + const clearAccessTargetIfCurrent = () => { + if (accessTargetResourceId() === machine.id) setAccessTargetResourceId(null); + }; + const toggleDetails = () => { + const wasExpanded = isExpanded(); + drawer.toggle(machine); + if (wasExpanded) clearAccessTargetIfCurrent(); + }; + const handleDetailsActivationKey: JSX.EventHandler< + HTMLTableRowElement, + KeyboardEvent + > = (event) => { + if (event.key !== 'Enter' && event.key !== ' ') return; + event.preventDefault(); + toggleDetails(); + }; return ( <> @@ -1217,8 +1234,8 @@ export const AgentsMachinesTable: Component<{ aria-controls={isExpanded() ? detailRowId() : undefined} aria-expanded={isExpanded() ? 'true' : 'false'} data-agents-machine-row={machine.id} - onClick={() => drawer.toggle(machine)} - onKeyDown={drawer.handleActivationKey(machine)} + onClick={toggleDetails} + onKeyDown={handleDetailsActivationKey} tabIndex={0} > { - if (!isExpanded()) drawer.toggle(machine); + setAccessTargetResourceId(machine.id); + drawer.open(machine); }} /> @@ -1440,7 +1458,11 @@ export const AgentsMachinesTable: Component<{ open={isExpanded()} detailRowId={detailRowId()} colSpan={detailColspan()} - onClose={() => drawer.close(machine)} + initialShowAccessContext={accessTargetResourceId() === machine.id} + onClose={() => { + drawer.close(machine); + clearAccessTargetIfCurrent(); + }} /> ); diff --git a/frontend-modern/src/features/standalone/__tests__/AgentsMachinesTable.test.tsx b/frontend-modern/src/features/standalone/__tests__/AgentsMachinesTable.test.tsx index 0802d18eb..a403a5a4f 100644 --- a/frontend-modern/src/features/standalone/__tests__/AgentsMachinesTable.test.tsx +++ b/frontend-modern/src/features/standalone/__tests__/AgentsMachinesTable.test.tsx @@ -6,7 +6,12 @@ import { RESOURCE_METADATA_CHANGED_EVENT } from '@/utils/resourceMetadataEvents' import { AgentsMachinesTable } from '../AgentsMachinesTable'; vi.mock('@/components/Infrastructure/ResourceDetailDrawer', () => ({ - ResourceDetailDrawer: () =>
, + ResourceDetailDrawer: (props: { initialShowAccessContext?: boolean }) => ( +
+ ), })); vi.mock('@/api/agentMetadata', () => ({ @@ -277,6 +282,10 @@ describe('AgentsMachinesTable', () => { screen.getByRole('button', { name: 'Add web interface URL for Event Host' }), ); expect(screen.getByTestId('resource-detail-drawer')).toBeInTheDocument(); + expect(screen.getByTestId('resource-detail-drawer')).toHaveAttribute( + 'data-initial-show-access-context', + 'true', + ); window.dispatchEvent( new CustomEvent(RESOURCE_METADATA_CHANGED_EVENT, {