Extract infrastructure operations state owner

This commit is contained in:
rcourtman 2026-03-20 16:08:57 +00:00
parent 4ff36e2b90
commit 1c202cdbee
11 changed files with 3828 additions and 3773 deletions

View file

@ -32,13 +32,14 @@ management, and fleet control surfaces.
8. `frontend-modern/src/api/agentProfiles.ts`
9. `frontend-modern/src/components/Settings/AgentProfilesPanel.tsx`
10. `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx`
11. `frontend-modern/src/components/Settings/UnifiedAgents.tsx`
12. `frontend-modern/src/components/Settings/NodeModal.tsx`
13. `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`
14. `frontend-modern/src/components/Infrastructure/deploy/ResultsStep.tsx`
15. `frontend-modern/src/utils/agentProfilesPresentation.ts`
16. `frontend-modern/src/utils/agentInstallCommand.ts`
17. `frontend-modern/src/api/nodes.ts`
11. `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx`
12. `frontend-modern/src/components/Settings/UnifiedAgents.tsx`
13. `frontend-modern/src/components/Settings/NodeModal.tsx`
14. `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`
15. `frontend-modern/src/components/Infrastructure/deploy/ResultsStep.tsx`
16. `frontend-modern/src/utils/agentProfilesPresentation.ts`
17. `frontend-modern/src/utils/agentInstallCommand.ts`
18. `frontend-modern/src/api/nodes.ts`
## Shared Boundaries
@ -46,12 +47,13 @@ management, and fleet control surfaces.
2. `frontend-modern/src/api/nodes.ts` shared with `api-contracts`: the shared Proxmox node client is both an agent lifecycle setup/install control surface and a canonical API payload contract boundary.
3. `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx` shared with `api-contracts`: the infrastructure operations controller is both an agent fleet lifecycle control surface and an API token, lookup, assignment, and reporting/install contract boundary.
4. `frontend-modern/src/components/Settings/UnifiedAgents.tsx` shared with `api-contracts`: the UnifiedAgents module is a compatibility shim for the canonical infrastructure operations controller and remains on the same shared agent lifecycle and API contract boundary while the old module path exists.
5. `frontend-modern/src/utils/agentInstallCommand.ts` shared with `api-contracts`: the shared frontend install-command helper is both an agent lifecycle control surface and a canonical API/install transport contract boundary.
6. `internal/api/agent_install_command_shared.go` shared with `api-contracts`: agent install command assembly is both an agent lifecycle control surface and a canonical API payload contract boundary.
7. `internal/api/config_setup_handlers.go` shared with `api-contracts`: auto-register and setup handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
8. `internal/api/unified_agent.go` shared with `api-contracts`: unified agent download and installer handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
9. `scripts/install.ps1` shared with `deployment-installability`: the Windows installer is both a deployment installability entry point and a canonical agent lifecycle runtime continuity boundary.
10. `scripts/install.sh` shared with `deployment-installability`: the shell installer is both a deployment installability entry point and a canonical agent lifecycle runtime continuity boundary.
5. `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx` shared with `api-contracts`: the shared infrastructure operations state hook is both an agent fleet lifecycle control surface and an API token, lookup, assignment, and reporting/install contract boundary.
6. `frontend-modern/src/utils/agentInstallCommand.ts` shared with `api-contracts`: the shared frontend install-command helper is both an agent lifecycle control surface and a canonical API/install transport contract boundary.
7. `internal/api/agent_install_command_shared.go` shared with `api-contracts`: agent install command assembly is both an agent lifecycle control surface and a canonical API payload contract boundary.
8. `internal/api/config_setup_handlers.go` shared with `api-contracts`: auto-register and setup handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
9. `internal/api/unified_agent.go` shared with `api-contracts`: unified agent download and installer handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
10. `scripts/install.ps1` shared with `deployment-installability`: the Windows installer is both a deployment installability entry point and a canonical agent lifecycle runtime continuity boundary.
11. `scripts/install.sh` shared with `deployment-installability`: the shell installer is both a deployment installability entry point and a canonical agent lifecycle runtime continuity boundary.
## Extension Points
@ -60,7 +62,7 @@ management, and fleet control surfaces.
3. Add or change runtime-side Unified Agent startup, first-report assembly, and enroll/runtime continuity through `internal/hostagent/`.
4. Keep legacy Unified Agent compatibility names explicitly secondary when touching shared `internal/api/` runtime helpers: the legacy host-route family and `host-agent:*` scope names may remain as ingress or migration aliases, but they must not retake primary ownership in router state, live runtime scope checks, handler commentary, or operator-facing guidance.
5. Add or change installer flags, persisted service arguments, or upgrade-safe re-entry behavior through `scripts/install.sh` and `scripts/install.ps1`.
6. Add or change profile management, shared frontend install-command assembly, Proxmox setup/install API transport, setup-completion install handoff transport, deploy-fallback manual install transport, and fleet-control presentation through `frontend-modern/src/api/agentProfiles.ts`, `frontend-modern/src/api/nodes.ts`, `frontend-modern/src/components/Settings/AgentProfilesPanel.tsx`, `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx`, `frontend-modern/src/components/Settings/NodeModal.tsx`, `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`, `frontend-modern/src/components/Infrastructure/deploy/ResultsStep.tsx`, and `frontend-modern/src/utils/agentInstallCommand.ts`.
6. Add or change profile management, shared frontend install-command assembly, Proxmox setup/install API transport, setup-completion install handoff transport, deploy-fallback manual install transport, and fleet-control presentation through `frontend-modern/src/api/agentProfiles.ts`, `frontend-modern/src/api/nodes.ts`, `frontend-modern/src/components/Settings/AgentProfilesPanel.tsx`, `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx`, `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx`, `frontend-modern/src/components/Settings/NodeModal.tsx`, `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`, `frontend-modern/src/components/Infrastructure/deploy/ResultsStep.tsx`, and `frontend-modern/src/utils/agentInstallCommand.ts`.
## Forbidden Paths

View file

@ -29,14 +29,15 @@ Own canonical runtime payload shapes between backend and frontend.
6. `frontend-modern/src/api/responseUtils.ts`
7. `frontend-modern/src/components/Settings/APITokenManager.tsx`
8. `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx`
9. `frontend-modern/src/components/Settings/UnifiedAgents.tsx`
10. `frontend-modern/src/utils/agentInstallCommand.ts`
11. `frontend-modern/src/api/nodes.ts`
12. `frontend-modern/src/api/license.ts`
13. `frontend-modern/src/api/monitoredSystemLedger.ts`
14. `frontend-modern/src/api/resources.ts`
15. `frontend-modern/src/api/monitoring.ts`
16. `internal/api/monitored_system_ledger.go`
9. `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx`
10. `frontend-modern/src/components/Settings/UnifiedAgents.tsx`
11. `frontend-modern/src/utils/agentInstallCommand.ts`
12. `frontend-modern/src/api/nodes.ts`
13. `frontend-modern/src/api/license.ts`
14. `frontend-modern/src/api/monitoredSystemLedger.ts`
15. `frontend-modern/src/api/resources.ts`
16. `frontend-modern/src/api/monitoring.ts`
17. `internal/api/monitored_system_ledger.go`
## Shared Boundaries
@ -50,24 +51,25 @@ Own canonical runtime payload shapes between backend and frontend.
8. `frontend-modern/src/components/Settings/APITokenManager.tsx` shared with `security-privacy`: the API token settings surface is both a security/privacy control surface and a canonical API payload contract boundary.
9. `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx` shared with `agent-lifecycle`: the infrastructure operations controller is both an agent fleet lifecycle control surface and an API token, lookup, assignment, and reporting/install contract boundary.
10. `frontend-modern/src/components/Settings/UnifiedAgents.tsx` shared with `agent-lifecycle`: the UnifiedAgents module is a compatibility shim for the canonical infrastructure operations controller and remains on the same shared agent lifecycle and API contract boundary while the old module path exists.
11. `frontend-modern/src/utils/agentInstallCommand.ts` shared with `agent-lifecycle`: the shared frontend install-command helper is both an agent lifecycle control surface and a canonical API/install transport contract boundary.
12. `internal/api/agent_install_command_shared.go` shared with `agent-lifecycle`: agent install command assembly is both an agent lifecycle control surface and a canonical API payload contract boundary.
13. `internal/api/ai_handler.go` shared with `ai-runtime`: Pulse Assistant handlers are both an AI runtime control surface and a canonical API payload contract boundary.
14. `internal/api/ai_handlers.go` shared with `ai-runtime`: AI settings and remediation handlers are both an AI runtime control surface and a canonical API payload contract boundary.
15. `internal/api/ai_intelligence_handlers.go` shared with `ai-runtime`: AI intelligence handlers are both an AI runtime control surface and a canonical API payload contract boundary.
16. `internal/api/config_setup_handlers.go` shared with `agent-lifecycle`: auto-register and setup handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
17. `internal/api/licensing_bridge.go` shared with `cloud-paid`: commercial licensing bridge handlers carry both API payload contract and cloud-paid entitlement boundary ownership.
18. `internal/api/licensing_handlers.go` shared with `cloud-paid`: commercial licensing handlers carry both API payload contract and cloud-paid entitlement boundary ownership.
19. `internal/api/notifications.go` shared with `notifications`: notification handlers are both a notification delivery control surface and a canonical API payload contract boundary.
20. `internal/api/payments_webhook_handlers.go` shared with `cloud-paid`: commercial payment webhook handlers carry both API payload contract and cloud-paid billing boundary ownership.
21. `internal/api/public_signup_handlers.go` shared with `cloud-paid`: hosted signup handlers carry both API payload contract and cloud-paid hosted provisioning boundary ownership.
22. `internal/api/resources.go` shared with `unified-resources`: the unified resource endpoint is both a backend payload contract surface and a unified-resource runtime boundary.
23. `internal/api/security.go` shared with `security-privacy`: the security handlers are both a security/privacy control surface and a canonical API payload contract boundary.
24. `internal/api/security_tokens.go` shared with `security-privacy`: the security token handlers are both a security/privacy control surface and a canonical API payload contract boundary.
25. `internal/api/slo.go` shared with `performance-and-scalability`: the SLO endpoint is both an API contract surface and a protected performance hot-path boundary.
26. `internal/api/system_settings.go` shared with `security-privacy`: the system settings telemetry and auth controls are both a security/privacy control surface and a canonical API payload contract boundary.
27. `internal/api/unified_agent.go` shared with `agent-lifecycle`: unified agent download and installer handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
28. `internal/api/updates.go` shared with `deployment-installability`: update handlers are both a deployment-installability control surface and a canonical API payload contract boundary.
11. `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx` shared with `agent-lifecycle`: the shared infrastructure operations state hook is both an agent fleet lifecycle control surface and an API token, lookup, assignment, and reporting/install contract boundary.
12. `frontend-modern/src/utils/agentInstallCommand.ts` shared with `agent-lifecycle`: the shared frontend install-command helper is both an agent lifecycle control surface and a canonical API/install transport contract boundary.
13. `internal/api/agent_install_command_shared.go` shared with `agent-lifecycle`: agent install command assembly is both an agent lifecycle control surface and a canonical API payload contract boundary.
14. `internal/api/ai_handler.go` shared with `ai-runtime`: Pulse Assistant handlers are both an AI runtime control surface and a canonical API payload contract boundary.
15. `internal/api/ai_handlers.go` shared with `ai-runtime`: AI settings and remediation handlers are both an AI runtime control surface and a canonical API payload contract boundary.
16. `internal/api/ai_intelligence_handlers.go` shared with `ai-runtime`: AI intelligence handlers are both an AI runtime control surface and a canonical API payload contract boundary.
17. `internal/api/config_setup_handlers.go` shared with `agent-lifecycle`: auto-register and setup handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
18. `internal/api/licensing_bridge.go` shared with `cloud-paid`: commercial licensing bridge handlers carry both API payload contract and cloud-paid entitlement boundary ownership.
19. `internal/api/licensing_handlers.go` shared with `cloud-paid`: commercial licensing handlers carry both API payload contract and cloud-paid entitlement boundary ownership.
20. `internal/api/notifications.go` shared with `notifications`: notification handlers are both a notification delivery control surface and a canonical API payload contract boundary.
21. `internal/api/payments_webhook_handlers.go` shared with `cloud-paid`: commercial payment webhook handlers carry both API payload contract and cloud-paid billing boundary ownership.
22. `internal/api/public_signup_handlers.go` shared with `cloud-paid`: hosted signup handlers carry both API payload contract and cloud-paid hosted provisioning boundary ownership.
23. `internal/api/resources.go` shared with `unified-resources`: the unified resource endpoint is both a backend payload contract surface and a unified-resource runtime boundary.
24. `internal/api/security.go` shared with `security-privacy`: the security handlers are both a security/privacy control surface and a canonical API payload contract boundary.
25. `internal/api/security_tokens.go` shared with `security-privacy`: the security token handlers are both a security/privacy control surface and a canonical API payload contract boundary.
26. `internal/api/slo.go` shared with `performance-and-scalability`: the SLO endpoint is both an API contract surface and a protected performance hot-path boundary.
27. `internal/api/system_settings.go` shared with `security-privacy`: the system settings telemetry and auth controls are both a security/privacy control surface and a canonical API payload contract boundary.
28. `internal/api/unified_agent.go` shared with `agent-lifecycle`: unified agent download and installer handlers are both an agent lifecycle control surface and a canonical API payload contract boundary.
29. `internal/api/updates.go` shared with `deployment-installability`: update handlers are both a deployment-installability control surface and a canonical API payload contract boundary.
## Extension Points
1. Add or change payload fields through handler + contract tests together
@ -91,7 +93,7 @@ Own canonical runtime payload shapes between backend and frontend.
and the shared `frontend-modern/src/components/Infrastructure/ResourceChangeSummary.tsx` and `frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx` cards' infrastructure resource-link default, so the Patrol page, resource drawer, and problem-resource dashboard panels inherit the canonical resource-filter path construction instead of rebuilding infrastructure URLs inline
8. Route frontend API-client parsed error propagation, API-error-status fallback handling, allowed-status handling, custom status-specific error handling, command-trigger success envelope handling, shared response parsing pipelines, missing-resource lookup handling, metadata CRUD routing, stream event consumption, response status, collection normalization, scalar payload coercion, and structured error normalization through canonical shared helpers under `frontend-modern/src/api/`
9. Add or change API token scope, assignment, and revocation presentation through `frontend-modern/src/components/Settings/APITokenManager.tsx`
10. Add or change infrastructure operations token generation, lookup, assignment, and reporting/install presentation through `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx`
10. Add or change infrastructure operations token generation, lookup, assignment, and reporting/install presentation through `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx` and `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx`
11. Keep `internal/api/session_store.go` on a fail-closed auth-persistence boundary: persisted OIDC refresh tokens may only round-trip through encrypted-at-rest session payloads, and any missing-crypto or invalid-ciphertext path must drop the token instead of preserving plaintext-at-rest session state.
12. Keep tenant AI handler wiring on canonical provider ownership: `internal/api/ai_handlers.go` may wire tenant `ReadState` and tenant-scoped unified-resource providers into AI services, but it must not revive tenant snapshot-provider bridges once Patrol can initialize and verify from those canonical providers directly.

View file

@ -129,6 +129,14 @@
"api-contracts"
]
},
{
"path": "frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx",
"rationale": "the shared infrastructure operations state hook is both an agent fleet lifecycle control surface and an API token, lookup, assignment, and reporting/install contract boundary",
"subsystems": [
"agent-lifecycle",
"api-contracts"
]
},
{
"path": "frontend-modern/src/utils/agentInstallCommand.ts",
"rationale": "the shared frontend install-command helper is both an agent lifecycle control surface and a canonical API/install transport contract boundary",
@ -307,6 +315,7 @@
"frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx",
"frontend-modern/src/components/Settings/NodeModal.tsx",
"frontend-modern/src/components/Settings/UnifiedAgents.tsx",
"frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx",
"frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx",
"frontend-modern/src/utils/agentInstallCommand.ts",
"frontend-modern/src/utils/agentProfilesPresentation.ts",
@ -503,7 +512,8 @@
"match_prefixes": [],
"match_files": [
"frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx",
"frontend-modern/src/components/Settings/UnifiedAgents.tsx"
"frontend-modern/src/components/Settings/UnifiedAgents.tsx",
"frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx"
],
"allow_same_subsystem_tests": false,
"test_prefixes": [],
@ -696,6 +706,7 @@
"frontend-modern/src/components/Settings/APITokenManager.tsx",
"frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx",
"frontend-modern/src/components/Settings/UnifiedAgents.tsx",
"frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx",
"frontend-modern/src/types/api.ts",
"frontend-modern/src/utils/agentInstallCommand.ts"
],
@ -904,7 +915,8 @@
"match_prefixes": [],
"match_files": [
"frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx",
"frontend-modern/src/components/Settings/UnifiedAgents.tsx"
"frontend-modern/src/components/Settings/UnifiedAgents.tsx",
"frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx"
],
"allow_same_subsystem_tests": false,
"test_prefixes": [],

View file

@ -1,8 +1,10 @@
import type { Component } from 'solid-js';
import { InfrastructureOperationsController } from './InfrastructureOperationsController';
import { useInfrastructureOperationsState } from './useInfrastructureOperationsState';
export const InfrastructureInstallPanel: Component = () => (
<InfrastructureOperationsController embedded showInventory={false} />
);
export const InfrastructureInstallPanel: Component = () => {
const state = useInfrastructureOperationsState({ embedded: true });
return state.renderInstallerSection();
};
export default InfrastructureInstallPanel;

View file

@ -2,7 +2,7 @@ import type { Component } from 'solid-js';
import { Card } from '@/components/shared/Card';
import { AgentProfilesPanel } from './AgentProfilesPanel';
import type { ProxmoxSettingsPanelProps } from './ProxmoxSettingsPanel';
import { InfrastructureOperationsController } from './InfrastructureOperationsController';
import { useInfrastructureOperationsState } from './useInfrastructureOperationsState';
interface InfrastructureReportingPanelProps extends ProxmoxSettingsPanelProps {
onManageDirectConnections: () => void;
@ -10,55 +10,60 @@ interface InfrastructureReportingPanelProps extends ProxmoxSettingsPanelProps {
export const InfrastructureReportingPanel: Component<InfrastructureReportingPanelProps> = (
props,
) => (
<div class="space-y-6">
<InfrastructureOperationsController embedded showInstaller={false} />
) => {
const state = useInfrastructureOperationsState();
<div class="grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)]">
<Card padding="lg" class="rounded-xl border border-border shadow-sm">
<div class="space-y-4">
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 class="text-base font-semibold text-base-content">Direct Proxmox connections</h3>
<p class="text-sm text-muted">
Review fallback direct coverage separately from agent-managed hosts.
</p>
</div>
<button
type="button"
onClick={props.onManageDirectConnections}
class="inline-flex min-h-10 sm:min-h-9 items-center justify-center rounded-md border border-border px-3 py-2 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
>
Manage direct connections
</button>
</div>
return (
<div class="space-y-6">
{state.renderStopMonitoringDialog()}
{state.renderInventorySection()}
<div class="grid gap-3 sm:grid-cols-3">
<div class="rounded-lg border border-border bg-surface-alt px-4 py-3">
<div class="text-sm font-medium text-base-content">PVE</div>
<div class="mt-1 text-xl font-semibold text-base-content">
{props.pveNodes().length}
<div class="grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)]">
<Card padding="lg" class="rounded-xl border border-border shadow-sm">
<div class="space-y-4">
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 class="text-base font-semibold text-base-content">Direct Proxmox connections</h3>
<p class="text-sm text-muted">
Review fallback direct coverage separately from agent-managed hosts.
</p>
</div>
<button
type="button"
onClick={props.onManageDirectConnections}
class="inline-flex min-h-10 sm:min-h-9 items-center justify-center rounded-md border border-border px-3 py-2 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
>
Manage direct connections
</button>
</div>
<div class="rounded-lg border border-border bg-surface-alt px-4 py-3">
<div class="text-sm font-medium text-base-content">PBS</div>
<div class="mt-1 text-xl font-semibold text-base-content">
{props.pbsNodes().length}
<div class="grid gap-3 sm:grid-cols-3">
<div class="rounded-lg border border-border bg-surface-alt px-4 py-3">
<div class="text-sm font-medium text-base-content">PVE</div>
<div class="mt-1 text-xl font-semibold text-base-content">
{props.pveNodes().length}
</div>
</div>
</div>
<div class="rounded-lg border border-border bg-surface-alt px-4 py-3">
<div class="text-sm font-medium text-base-content">PMG</div>
<div class="mt-1 text-xl font-semibold text-base-content">
{props.pmgNodes().length}
<div class="rounded-lg border border-border bg-surface-alt px-4 py-3">
<div class="text-sm font-medium text-base-content">PBS</div>
<div class="mt-1 text-xl font-semibold text-base-content">
{props.pbsNodes().length}
</div>
</div>
<div class="rounded-lg border border-border bg-surface-alt px-4 py-3">
<div class="text-sm font-medium text-base-content">PMG</div>
<div class="mt-1 text-xl font-semibold text-base-content">
{props.pmgNodes().length}
</div>
</div>
</div>
</div>
</div>
</Card>
</Card>
</div>
<AgentProfilesPanel />
</div>
<AgentProfilesPanel />
</div>
);
);
};
export default InfrastructureReportingPanel;

View file

@ -14,26 +14,18 @@ vi.mock('@solidjs/router', async () => {
};
});
vi.mock('../InfrastructureOperationsController', () => ({
InfrastructureOperationsController: (props: { showInventory?: boolean; showInstaller?: boolean }) => (
<div data-testid="unified-agents">
{props.showInventory === false
? 'install'
: props.showInstaller === false
? 'inventory'
: 'default'}
</div>
),
vi.mock('../InfrastructureInstallPanel', () => ({
InfrastructureInstallPanel: () => <div data-testid="unified-agents">install</div>,
}));
vi.mock('../InfrastructureReportingPanel', () => ({
InfrastructureReportingPanel: () => <div data-testid="agent-profiles">profiles</div>,
}));
vi.mock('../ProxmoxSettingsPanel', () => ({
ProxmoxSettingsPanel: () => <div data-testid="proxmox-settings">direct</div>,
}));
vi.mock('../AgentProfilesPanel', () => ({
AgentProfilesPanel: () => <div data-testid="agent-profiles">profiles</div>,
}));
describe('InfrastructureWorkspace', () => {
beforeEach(() => {
navigateSpy.mockReset();

View file

@ -4,6 +4,10 @@ import { createSignal } from 'solid-js';
import { createStore } from 'solid-js/store';
import { Router, Route } from '@solidjs/router';
import { UnifiedAgents } from '../UnifiedAgents';
import infrastructureInstallPanelSource from '../InfrastructureInstallPanel.tsx?raw';
import infrastructureOperationsControllerSource from '../InfrastructureOperationsController.tsx?raw';
import infrastructureReportingPanelSource from '../InfrastructureReportingPanel.tsx?raw';
import infrastructureOperationsStateSource from '../useInfrastructureOperationsState.tsx?raw';
import type {
Agent,
ConnectedInfrastructureItem,
@ -42,6 +46,17 @@ const refetchResourcesMock = vi.fn();
const [mockResources, setMockResources] = createSignal<any[]>([]);
let securityStatusResponse = { requiresAuth: true, apiTokenConfigured: false };
describe('UnifiedAgents ownership guardrails', () => {
it('routes controller and workspace panels through the shared infrastructure operations state owner', () => {
expect(infrastructureOperationsControllerSource).toContain('useInfrastructureOperationsState');
expect(infrastructureInstallPanelSource).toContain('useInfrastructureOperationsState');
expect(infrastructureReportingPanelSource).toContain('useInfrastructureOperationsState');
expect(infrastructureOperationsStateSource).toContain('renderInstallerSection');
expect(infrastructureOperationsStateSource).toContain('renderInventorySection');
expect(infrastructureOperationsStateSource).toContain('renderStopMonitoringDialog');
});
});
vi.mock('@/App', () => ({
useWebSocket: () => mockWsStore,
}));

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import agentProfilesPanelSource from '../AgentProfilesPanel.tsx?raw';
import apiTokenManagerSource from '../APITokenManager.tsx?raw';
import infrastructureOperationsControllerSource from '../InfrastructureOperationsController.tsx?raw';
import infrastructureOperationsStateSource from '../useInfrastructureOperationsState.tsx?raw';
import agentLedgerPanelSource from '../MonitoredSystemLedgerPanel.tsx?raw';
import alertsPageSource from '@/pages/Alerts.tsx?raw';
import setupCompletionPanelSource from '@/components/SetupWizard/SetupCompletionPanel.tsx?raw';
@ -156,43 +156,43 @@ describe('monitored-system model guardrails', () => {
});
it('keeps UnifiedAgents free of v5 merge-workaround patterns', () => {
expect(infrastructureOperationsControllerSource).not.toContain('previousHostTypes');
expect(infrastructureOperationsControllerSource).not.toContain('const allHosts = createMemo(');
expect(infrastructureOperationsControllerSource).toContain('@/utils/unifiedAgentStatusPresentation');
expect(infrastructureOperationsControllerSource).not.toContain('const MONITORING_STOPPED_STATUS_LABEL =');
expect(infrastructureOperationsControllerSource).not.toContain('const ALLOW_RECONNECT_LABEL =');
expect(infrastructureOperationsControllerSource).toContain('withPrivilegeEscalation');
expect(infrastructureOperationsControllerSource).toContain('@/utils/agentCapabilityPresentation');
expect(infrastructureOperationsControllerSource).not.toContain('const getCapabilityLabel =');
expect(infrastructureOperationsControllerSource).not.toContain('const getCapabilityBadgeClass =');
expect(infrastructureOperationsStateSource).not.toContain('previousHostTypes');
expect(infrastructureOperationsStateSource).not.toContain('const allHosts = createMemo(');
expect(infrastructureOperationsStateSource).toContain('@/utils/unifiedAgentStatusPresentation');
expect(infrastructureOperationsStateSource).not.toContain('const MONITORING_STOPPED_STATUS_LABEL =');
expect(infrastructureOperationsStateSource).not.toContain('const ALLOW_RECONNECT_LABEL =');
expect(infrastructureOperationsStateSource).toContain('withPrivilegeEscalation');
expect(infrastructureOperationsStateSource).toContain('@/utils/agentCapabilityPresentation');
expect(infrastructureOperationsStateSource).not.toContain('const getCapabilityLabel =');
expect(infrastructureOperationsStateSource).not.toContain('const getCapabilityBadgeClass =');
expect(agentCapabilityPresentationSource).toContain("export type AgentCapability = 'agent'");
expect(agentCapabilityPresentationSource).toContain('export function getAgentCapabilityLabel');
expect(agentCapabilityPresentationSource).toContain(
'export function getAgentCapabilityBadgeClass',
);
expect(infrastructureOperationsControllerSource).not.toContain('isConnectedHealthStatus');
expect(infrastructureOperationsControllerSource).not.toContain('const connectedFromStatus =');
expect(infrastructureOperationsStateSource).not.toContain('isConnectedHealthStatus');
expect(infrastructureOperationsStateSource).not.toContain('const connectedFromStatus =');
expect(agentProfilesPanelSource).toContain('isConnectedHealthStatus');
expect(agentProfilesPanelSource).not.toContain('const connectedFromStatus =');
expect(statusUtilsSource).toContain('export function isConnectedHealthStatus');
expect(infrastructureOperationsControllerSource).toContain('@/utils/unifiedAgentStatusPresentation');
expect(infrastructureOperationsControllerSource).not.toContain('const statusBadgeClass =');
expect(infrastructureOperationsControllerSource).not.toContain('const statusBadgeClasses =');
expect(infrastructureOperationsControllerSource).toContain('getUnifiedAgentLookupStatusPresentation');
expect(infrastructureOperationsStateSource).toContain('@/utils/unifiedAgentStatusPresentation');
expect(infrastructureOperationsStateSource).not.toContain('const statusBadgeClass =');
expect(infrastructureOperationsStateSource).not.toContain('const statusBadgeClasses =');
expect(infrastructureOperationsStateSource).toContain('getUnifiedAgentLookupStatusPresentation');
expect(unifiedAgentStatusPresentationSource).toContain(
'export function getUnifiedAgentStatusPresentation',
);
expect(unifiedAgentStatusPresentationSource).toContain(
'export function getUnifiedAgentLookupStatusPresentation',
);
expect(infrastructureOperationsControllerSource).toContain('getInventorySubjectLabel');
expect(infrastructureOperationsControllerSource).toContain('getMonitoringStoppedEmptyState');
expect(infrastructureOperationsControllerSource).toContain('getRemovedUnifiedAgentItemLabel');
expect(infrastructureOperationsControllerSource).toContain('getUnifiedAgentLastSeenLabel');
expect(infrastructureOperationsControllerSource).not.toContain('const getInventorySubjectLabel =');
expect(infrastructureOperationsControllerSource).not.toContain('const getRemovedItemLabel =');
expect(infrastructureOperationsControllerSource).not.toContain('const lastSeenLabel = () => {');
expect(infrastructureOperationsControllerSource).not.toContain(
expect(infrastructureOperationsStateSource).toContain('getInventorySubjectLabel');
expect(infrastructureOperationsStateSource).toContain('getMonitoringStoppedEmptyState');
expect(infrastructureOperationsStateSource).toContain('getRemovedUnifiedAgentItemLabel');
expect(infrastructureOperationsStateSource).toContain('getUnifiedAgentLastSeenLabel');
expect(infrastructureOperationsStateSource).not.toContain('const getInventorySubjectLabel =');
expect(infrastructureOperationsStateSource).not.toContain('const getRemovedItemLabel =');
expect(infrastructureOperationsStateSource).not.toContain('const lastSeenLabel = () => {');
expect(infrastructureOperationsStateSource).not.toContain(
'No monitoring-stopped items match the current filters.',
);
expect(unifiedAgentInventoryPresentationSource).toContain(
@ -225,15 +225,15 @@ describe('monitored-system model guardrails', () => {
expect(unifiedAgentInventoryPresentationSource).toContain(
'export function getUnifiedAgentClipboardCopySuccessMessage',
);
expect(infrastructureOperationsControllerSource).not.toContain(
expect(infrastructureOperationsStateSource).not.toContain(
'No host identifiers are available to stop monitoring.',
);
expect(infrastructureOperationsControllerSource).not.toContain(
expect(infrastructureOperationsStateSource).not.toContain(
'Failed to update agent configuration',
);
expect(infrastructureOperationsControllerSource).not.toContain('Uninstall command copied');
expect(infrastructureOperationsControllerSource).not.toContain('Upgrade command copied');
expect(infrastructureOperationsControllerSource).not.toContain(
expect(infrastructureOperationsStateSource).not.toContain('Uninstall command copied');
expect(infrastructureOperationsStateSource).not.toContain('Upgrade command copied');
expect(infrastructureOperationsStateSource).not.toContain(
"notificationStore.error('Failed to copy')",
);
expect(relaySettingsPanelSource).toContain('getRelayConnectionPresentation');
@ -255,11 +255,11 @@ describe('monitored-system model guardrails', () => {
expect(relayOnboardingCardSource).toContain('RELAY_ONBOARDING_DISCONNECTED_LABEL');
expect(relayOnboardingCardSource).not.toContain('Pair Your Mobile Device');
expect(relayOnboardingCardSource).not.toContain('Relay is currently disconnected.');
expect(infrastructureOperationsControllerSource).toContain('STORAGE_KEYS.SETUP_HANDOFF');
expect(infrastructureOperationsControllerSource).toContain(
expect(infrastructureOperationsStateSource).toContain('STORAGE_KEYS.SETUP_HANDOFF');
expect(infrastructureOperationsStateSource).toContain(
'Security configured. Save these first-run credentials now.',
);
expect(infrastructureOperationsControllerSource).toContain(
expect(infrastructureOperationsStateSource).toContain(
'Generate a scoped install token below before copying agent commands.',
);
expect(setupCompletionPanelSource).toContain('@/utils/relayPresentation');

View file

@ -5,6 +5,7 @@ import infrastructureWorkspaceSource from '../InfrastructureWorkspace.tsx?raw';
import infrastructureInstallPanelSource from '../InfrastructureInstallPanel.tsx?raw';
import infrastructureOperationsControllerSource from '../InfrastructureOperationsController.tsx?raw';
import infrastructureReportingPanelSource from '../InfrastructureReportingPanel.tsx?raw';
import infrastructureOperationsStateSource from '../useInfrastructureOperationsState.tsx?raw';
import apiAccessPanelSource from '../APIAccessPanel.tsx?raw';
import auditLogPanelSource from '../AuditLogPanel.tsx?raw';
import auditWebhookPanelSource from '../AuditWebhookPanel.tsx?raw';
@ -38,6 +39,7 @@ const extractedModules = [
'../settingsFeatureGates.ts',
'../BackupTransferDialogs.tsx',
'../InfrastructureOperationsController.tsx',
'../useInfrastructureOperationsState.tsx',
'../InfrastructureWorkspace.tsx',
'../InfrastructureInstallPanel.tsx',
'../InfrastructureReportingPanel.tsx',
@ -277,9 +279,10 @@ describe('Settings architecture guardrails', () => {
expect(infrastructureWorkspaceSource).not.toContain('tracking-[0.22em]');
expect(infrastructureWorkspaceSource).toContain('InfrastructureInstallPanel');
expect(infrastructureWorkspaceSource).toContain('InfrastructureReportingPanel');
expect(infrastructureInstallPanelSource).toContain('InfrastructureOperationsController');
expect(infrastructureReportingPanelSource).toContain('InfrastructureOperationsController');
expect(infrastructureOperationsControllerSource).toContain('export const InfrastructureOperationsController');
expect(infrastructureInstallPanelSource).toContain('useInfrastructureOperationsState');
expect(infrastructureReportingPanelSource).toContain('useInfrastructureOperationsState');
expect(infrastructureOperationsControllerSource).toContain('useInfrastructureOperationsState');
expect(infrastructureOperationsStateSource).toContain('export const useInfrastructureOperationsState');
expect(infrastructureInstallPanelSource).not.toContain('<PageHeader');
expect(infrastructureReportingPanelSource).not.toContain('<PageHeader');
});

File diff suppressed because it is too large Load diff