diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 6b549a949..918b72044 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -331,11 +331,19 @@ an add-only capacity posture. `NodeModalAuthenticationSection`, `NodeModalMonitoringSection`, and `NodeModalStatusFooter` primitives inline under the editor — dropping the Dialog wrapper and the surrounding discovery/configured-nodes - workspace. The add flow must not reintroduce the full Proxmox - workspace (discovery card, configured nodes table, node-modal stack) - into the credential slot, because that previously showed the - ledger-of-other-systems in the middle of entering one system's - credentials. + workspace. For TrueNAS and VMware, the credential slots are + `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx` + and + `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx`; + they extract the inner form bodies from the per-type panels and + render them inline under the editor while still driving the existing + `TrueNASSettingsPanelState` and `VMwareSettingsPanelState` APIs for + save, test, preview, and admission-preview behavior. The add flow + must not reintroduce the full per-type workspace (Proxmox discovery + card, configured nodes table, node-modal stack; TrueNAS/VMware + connection list with headers and row actions) into the credential + slot, because that previously showed the ledger-of-other-systems in + the middle of entering one system's credentials. ## Forbidden Paths diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 20395e3e4..de6763032 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -246,9 +246,15 @@ work extends shared components instead of creating new local variants. `NodeModalAuthenticationSection`, `NodeModalMonitoringSection`, and `NodeModalStatusFooter` inline under the editor shell rather than embedding the full Proxmox workspace (discovery card, configured - nodes table, delete dialog, node modal stack). Showing a ledger of - other systems inside the credential slot is exactly the ledger-inside- - editor drift this contract forbids. + nodes table, delete dialog, node modal stack). For TrueNAS and VMware + the credential slots are + `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx` + and + `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx` + and they must render only the connection form body inline under the + editor shell — no connection list, no row actions, no surrounding + panel chrome. Showing a ledger of other systems inside the credential + slot is exactly the ledger-inside-editor drift this contract forbids. 6. Keep Proxmox deep-link route selection on the shared settings-navigation boundary. `frontend-modern/src/components/Settings/settingsNavigationModel.ts` and `frontend-modern/src/components/Settings/useSettingsNavigation.ts` must treat the canonical PBS and PMG Proxmox deep links as agent-selection authority even though those URLs resolve to the shared `infrastructure-operations` tab. Reloading or remounting on a PBS or PMG deep link must not silently fall back to the PVE selector state. 7. Keep shared storage feature presenters on canonical platform truth. When reusable storage presenters under `frontend-modern/src/features/storageBackups/` classify canonical resources for the shared storage route, API-backed virtualization datastores such as VMware must stay inventory-only datastores instead of inheriting PBS-specific backup-repository or protected-target copy from older fallback branches. 8. Keep shared source/platform vocabulary on the governed manifest boundary. `frontend-modern/src/utils/platformSupportManifest.generated.ts` must be the tracked frontend projection of `docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json`, `frontend-modern/src/utils/platformSupportManifest.ts`, `frontend-modern/src/utils/sourcePlatforms.ts`, and `frontend-modern/src/utils/sourcePlatformOptions.ts` must consume that generated projection instead of embedding divergent future-label lists, setup/onboarding path allowlists, or presentation-only guesses, and `frontend-modern/scripts/canonical-platform-audit.mjs` must fail when the generated projection drifts from the governed manifest. diff --git a/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx b/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx new file mode 100644 index 000000000..415860946 --- /dev/null +++ b/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx @@ -0,0 +1,275 @@ +import { Component, Show, createEffect, onMount } from 'solid-js'; +import { + formCheckbox, + formControl, + formField, + formHelpText, + formLabel, + formSelect, +} from '@/components/shared/Form'; +import { MonitoredSystemAdmissionPreview } from '../../MonitoredSystemAdmissionPreview'; +import type { TrueNASSettingsPanelState } from '../../useTrueNASSettingsPanelState'; + +const buttonClass = + '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 disabled:cursor-not-allowed disabled:opacity-60'; +const primaryButtonClass = + 'inline-flex min-h-10 sm:min-h-9 items-center justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60'; + +export interface TrueNASCredentialSlotProps { + state: TrueNASSettingsPanelState; + onCancel: () => void; + onSaved: () => void; +} + +export const TrueNASCredentialSlot: Component = (props) => { + let primed = false; + + onMount(() => { + if (!props.state.dialogOpen()) { + props.state.openCreateDialog(); + } + primed = true; + }); + + createEffect(() => { + const open = props.state.dialogOpen(); + if (primed && !open && !props.state.saving()) { + props.onSaved(); + } + }); + + const handleCancel = () => { + props.state.closeDialog(); + props.onCancel(); + }; + + return ( +
+ +
+ {props.state.featureDisabledMessage() || + 'TrueNAS connections are disabled for this Pulse tier.'} +
+
+ + +
+ + + + + +
+ + + + + + +
+ + +
+
+ +
+ +
+ + + +
+
+ + + +
+ + + + +
+
+
+ ); +}; diff --git a/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx b/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx new file mode 100644 index 000000000..2c03edd84 --- /dev/null +++ b/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx @@ -0,0 +1,213 @@ +import { Component, Show, createEffect, onMount } from 'solid-js'; +import ShieldAlert from 'lucide-solid/icons/shield-alert'; +import { CalloutCard } from '@/components/shared/CalloutCard'; +import { + formCheckbox, + formControl, + formField, + formHelpText, + formLabel, +} from '@/components/shared/Form'; +import { MonitoredSystemAdmissionPreview } from '../../MonitoredSystemAdmissionPreview'; +import type { VMwareSettingsPanelState } from '../../useVMwareSettingsPanelState'; + +const buttonClass = + '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 disabled:cursor-not-allowed disabled:opacity-60'; +const primaryButtonClass = + 'inline-flex min-h-10 sm:min-h-9 items-center justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60'; + +export interface VMwareCredentialSlotProps { + state: VMwareSettingsPanelState; + onCancel: () => void; + onSaved: () => void; +} + +export const VMwareCredentialSlot: Component = (props) => { + let primed = false; + + onMount(() => { + if (!props.state.dialogOpen()) { + props.state.openCreateDialog(); + } + primed = true; + }); + + createEffect(() => { + const open = props.state.dialogOpen(); + if (primed && !open && !props.state.saving()) { + props.onSaved(); + } + }); + + const handleCancel = () => { + props.state.closeDialog(); + props.onCancel(); + }; + + return ( +
+ +
+ {props.state.featureDisabledMessage() || + 'VMware connections are disabled for this Pulse tier.'} +
+
+ + + + {(failure) => ( + +

{failure().message}

+ +

{failure().guidance}

+
+ + } + icon={} + /> + )} +
+ +
+ + + + + +
+ +
+ + +
+ + + +
+ + + + +
+
+
+ ); +}; diff --git a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx index cb800ace2..c9d890878 100644 --- a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx +++ b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx @@ -11,13 +11,13 @@ import { } from './connectionsTableModel'; import { ConnectionEditor } from './ConnectionEditor/ConnectionEditor'; import { NodeCredentialSlot } from './ConnectionEditor/CredentialSlots/NodeCredentialSlot'; +import { TrueNASCredentialSlot } from './ConnectionEditor/CredentialSlots/TrueNASCredentialSlot'; +import { VMwareCredentialSlot } from './ConnectionEditor/CredentialSlots/VMwareCredentialSlot'; import type { ConnectionType } from '@/api/connections'; import { InfrastructureActiveRowDetails } from './InfrastructureActiveRowDetails'; import { InfrastructureInstallerSection } from './InfrastructureInstallerSection'; import { InfrastructureIgnoredRowDetails } from './InfrastructureIgnoredRowDetails'; import { InfrastructureStopMonitoringDialog } from './InfrastructureStopMonitoringDialog'; -import { TrueNASSettingsPanel } from './TrueNASSettingsPanel'; -import { VMwareSettingsPanel } from './VMwareSettingsPanel'; import { buildInfrastructureWorkspacePath, deriveAddStepFromLegacyPath, @@ -178,9 +178,21 @@ const InfrastructureWorkspaceContent: Component = case 'pmg': return renderNodeSlot(type); case 'truenas': - return ; + return ( + + ); case 'vmware': - return ; + return ( + + ); case 'agent': return (
diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx index 1cd6fadef..e60efda73 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx @@ -62,12 +62,12 @@ vi.mock('../ConnectionEditor/CredentialSlots/NodeCredentialSlot', () => ({ ), })); -vi.mock('../TrueNASSettingsPanel', () => ({ - TrueNASSettingsPanel: () =>
truenas
, +vi.mock('../ConnectionEditor/CredentialSlots/TrueNASCredentialSlot', () => ({ + TrueNASCredentialSlot: () =>
truenas
, })); -vi.mock('../VMwareSettingsPanel', () => ({ - VMwareSettingsPanel: () =>
vmware
, +vi.mock('../ConnectionEditor/CredentialSlots/VMwareCredentialSlot', () => ({ + VMwareCredentialSlot: () =>
vmware
, })); vi.mock('../InfrastructureActiveRowDetails', () => ({