mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Converge connection editor notices on CalloutCard
This commit is contained in:
@@ -1013,7 +1013,11 @@ class helper.
|
||||
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 impact-preview behavior. The add flow
|
||||
save, test, preview, and impact-preview behavior. Feature-disabled,
|
||||
delete-error, and probe guidance notices in the connection editor may
|
||||
compose frontend-primitives' `CalloutCard`; agent-lifecycle owns the
|
||||
selected-source lifecycle semantics and form handoff, not colored local
|
||||
notice shells. 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
|
||||
|
||||
@@ -406,6 +406,10 @@ payload shape change when the portal presents compact client rows.
|
||||
`frontend-modern/src/components/Settings/AvailabilitySettingsPanel.tsx`,
|
||||
`frontend-modern/src/components/Settings/availabilitySettingsModel.ts`,
|
||||
and `AvailabilityTargetSlot.tsx` own the browser transport shape.
|
||||
Availability target probe-result and error notices may compose
|
||||
frontend-primitives' `CalloutCard` for shared settings callout chrome;
|
||||
API contracts own the target CRUD/test payload semantics and endpoint
|
||||
routing, not local colored alert shells.
|
||||
Connections ledger rows with type
|
||||
`availability` must route management handoffs, pause, remove, and test
|
||||
actions to those availability-target endpoints and must not reuse node,
|
||||
|
||||
@@ -1357,6 +1357,10 @@ not a replacement status card, CTA band, or page-local nested card.
|
||||
`frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts`
|
||||
and
|
||||
`frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts`.
|
||||
Connection-editor status, feature-disabled, delete-error, and probe-result
|
||||
notices are part of the same settings callout boundary: the editor and
|
||||
credential slots own the source-specific lifecycle or API meaning, while
|
||||
`CalloutCard` owns the warning/success/danger shell and compact density.
|
||||
8. Keep hosted settings-shell framing imports safe for bundle initialization.
|
||||
Self-hosted billing titles, descriptions, and referral copy used by
|
||||
`settingsHeaderMeta.ts`, `settingsNavCatalog.ts`, and adjacent settings
|
||||
@@ -3349,6 +3353,13 @@ maintaining feature-local colored bordered wrappers. The primitive owns the tone
|
||||
palette and the `scale="compact"` density used by smaller settings notices, and
|
||||
the `settings-callout-card-shell` shared-template registry rule requires current
|
||||
settings consumers to compose it instead of reintroducing local panel shells.
|
||||
Connection-editor status, feature-disabled, delete-error, and probe-result
|
||||
notices are part of that same settings callout boundary: the editor and
|
||||
credential slots own the source-specific lifecycle or API meaning, while
|
||||
`CalloutCard` owns the warning/success/danger shell and compact density.
|
||||
The `settings-connection-editor-local-*-callout-shell` pattern guards block
|
||||
future connection-editor files from reintroducing amber, red, or rose local
|
||||
notice shells.
|
||||
|
||||
Settings external documentation text links must route through
|
||||
`ExternalTextLink`, while button-styled external actions route through
|
||||
|
||||
@@ -1558,6 +1558,7 @@
|
||||
"owned_files": [
|
||||
"frontend-modern/src/components/Settings/APITokenManager.tsx",
|
||||
"frontend-modern/src/components/Settings/apiTokenManagerModel.ts",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx",
|
||||
"frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx",
|
||||
"frontend-modern/src/components/Settings/NodeModalAuthenticationSection.tsx",
|
||||
@@ -1821,6 +1822,27 @@
|
||||
"frontend-modern/src/types/api.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "availability-target-settings-client-surface",
|
||||
"label": "availability target settings client proof",
|
||||
"match_prefixes": [],
|
||||
"match_files": [
|
||||
"frontend-modern/src/api/availabilityTargets.ts",
|
||||
"frontend-modern/src/components/Settings/availabilitySettingsModel.ts",
|
||||
"frontend-modern/src/components/Settings/AvailabilitySettingsPanel.tsx",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx",
|
||||
"internal/api/availability_handlers.go"
|
||||
],
|
||||
"allow_same_subsystem_tests": false,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"frontend-modern/src/api/__tests__/availabilityTargets.test.ts",
|
||||
"frontend-modern/src/components/Settings/__tests__/AvailabilitySettingsPanel.test.tsx",
|
||||
"frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts",
|
||||
"frontend-modern/src/components/Settings/ConnectionEditor/__tests__/AvailabilityTargetSlot.test.tsx",
|
||||
"internal/api/availability_handlers_test.go"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontend-api-clients",
|
||||
"label": "frontend API client proof",
|
||||
|
||||
@@ -301,6 +301,13 @@
|
||||
},
|
||||
"requiredConsumers": [
|
||||
{ "path": "src/components/Settings/AIProviderConfigurationSection.tsx" },
|
||||
{ "path": "src/components/Settings/ConnectionEditor/AddressProbeStep.tsx" },
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx"
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx"
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx"
|
||||
},
|
||||
@@ -316,6 +323,35 @@
|
||||
"path": "src/components/Settings/AIProviderConfigurationSection.tsx",
|
||||
"patterns": ["rounded border border-red-200", "bg-red-50 dark:bg-red-900 px-2 py-1.5"]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/AddressProbeStep.tsx",
|
||||
"patterns": [
|
||||
"rounded-md border border-red-300 bg-red-50",
|
||||
"rounded-md border border-amber-300 bg-amber-50"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx",
|
||||
"patterns": [
|
||||
"border-green-300 bg-green-50",
|
||||
"rounded-md border border-rose-300 bg-rose-50",
|
||||
"testToneClass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx",
|
||||
"patterns": [
|
||||
"rounded-md border border-amber-300 bg-amber-50",
|
||||
"rounded-md border border-rose-300 bg-rose-50"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx",
|
||||
"patterns": [
|
||||
"rounded-md border border-amber-300 bg-amber-50",
|
||||
"rounded-md border border-rose-300 bg-rose-50"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/DiagnosticsResultsPanel.tsx",
|
||||
"patterns": [
|
||||
@@ -2407,6 +2443,72 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "settings-connection-editor-local-warning-callout-shell",
|
||||
"category": "callout",
|
||||
"summary": "Connection editor settings code must not recreate amber warning callout shells locally; compose CalloutCard compact notices so tone, spacing, and dark-mode styling stay shared.",
|
||||
"canonical": {
|
||||
"path": "src/components/shared/CalloutCard.tsx",
|
||||
"export": "CalloutCard"
|
||||
},
|
||||
"scopes": ["src/components/Settings/ConnectionEditor"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["rounded-md border", "bg-amber-50"],
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [
|
||||
"src/components/Settings/ConnectionEditor/__tests__/ConnectionEditor.test.tsx",
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts"
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/components/shared/CalloutCard.test.tsx",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "settings-connection-editor-local-danger-callout-shell",
|
||||
"category": "callout",
|
||||
"summary": "Connection editor settings code must not recreate red or rose danger callout shells locally; compose CalloutCard compact notices so alert tone, spacing, and dark-mode styling stay shared.",
|
||||
"canonical": {
|
||||
"path": "src/components/shared/CalloutCard.tsx",
|
||||
"export": "CalloutCard"
|
||||
},
|
||||
"scopes": ["src/components/Settings/ConnectionEditor"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["rounded-md border", "bg-rose-50"],
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [
|
||||
"src/components/Settings/ConnectionEditor/__tests__/ConnectionEditor.test.tsx",
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts"
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/components/shared/CalloutCard.test.tsx",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "settings-connection-editor-local-red-callout-shell",
|
||||
"category": "callout",
|
||||
"summary": "Connection editor settings code must not recreate red danger callout shells locally; compose CalloutCard compact notices so alert tone, spacing, and dark-mode styling stay shared.",
|
||||
"canonical": {
|
||||
"path": "src/components/shared/CalloutCard.tsx",
|
||||
"export": "CalloutCard"
|
||||
},
|
||||
"scopes": ["src/components/Settings/ConnectionEditor"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["rounded-md border", "bg-red-50"],
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [
|
||||
"src/components/Settings/ConnectionEditor/__tests__/ConnectionEditor.test.tsx",
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts"
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/components/shared/CalloutCard.test.tsx",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-inline-notice-local-amber-shell",
|
||||
"category": "callout",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, For, Show } from 'solid-js';
|
||||
import type { ProbeCandidate } from '@/api/connections';
|
||||
import { Button } from '@/components/shared/Button';
|
||||
import { CalloutCard } from '@/components/shared/CalloutCard';
|
||||
import { formControl, formField, formHelpText, formLabel } from '@/components/shared/Form';
|
||||
import { formatConnectionErrorMessage } from '@/utils/connectionErrorPresentation';
|
||||
import { getInfrastructureAgentHostProfileSupportText } from '@/utils/infrastructureOnboardingPresentation';
|
||||
@@ -63,41 +64,52 @@ export const AddressProbeStep: Component<AddressProbeStepProps> = (props) => {
|
||||
</div>
|
||||
|
||||
<Show when={props.state.phase() === 'error' && props.state.errorMessage().length > 0}>
|
||||
<div class="rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-950/40 dark:text-red-200">
|
||||
{formatConnectionErrorMessage(props.state.errorMessage()) ?? props.state.errorMessage()}
|
||||
</div>
|
||||
<CalloutCard
|
||||
role="alert"
|
||||
tone="danger"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={
|
||||
formatConnectionErrorMessage(props.state.errorMessage()) ?? props.state.errorMessage()
|
||||
}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Show when={props.state.phase() === 'no-match'}>
|
||||
<div class="rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-900 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100">
|
||||
<div class="font-medium">No supported API-backed platform detected at that address.</div>
|
||||
<div class="mt-1 text-xs">
|
||||
<Show when={props.onChooseSourceTypeInstead}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onChooseSourceTypeInstead}
|
||||
class="font-medium underline underline-offset-2 hover:text-amber-950 dark:hover:text-amber-50"
|
||||
<CalloutCard
|
||||
tone="warning"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
title="No supported API-backed platform detected at that address."
|
||||
description={
|
||||
<>
|
||||
<Show when={props.onChooseSourceTypeInstead}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onChooseSourceTypeInstead}
|
||||
class="font-semibold underline underline-offset-2"
|
||||
>
|
||||
Choose a source type instead
|
||||
</button>
|
||||
<span>. </span>
|
||||
</Show>
|
||||
If this is one of the supported {getInfrastructureAgentHostProfileSupportText()},{' '}
|
||||
<Show
|
||||
when={props.onInstallAgent}
|
||||
fallback={<span class="font-medium">install Pulse Agent instead</span>}
|
||||
>
|
||||
Choose a source type instead
|
||||
</button>
|
||||
<span>. </span>
|
||||
</Show>
|
||||
If this is one of the supported {getInfrastructureAgentHostProfileSupportText()},{' '}
|
||||
<Show
|
||||
when={props.onInstallAgent}
|
||||
fallback={<span class="font-medium">install Pulse Agent instead</span>}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onInstallAgent}
|
||||
class="font-medium underline underline-offset-2 hover:text-amber-950 dark:hover:text-amber-50"
|
||||
>
|
||||
install Pulse Agent instead
|
||||
</button>
|
||||
</Show>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onInstallAgent}
|
||||
class="font-semibold underline underline-offset-2"
|
||||
>
|
||||
install Pulse Agent instead
|
||||
</button>
|
||||
</Show>
|
||||
.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Show when={props.state.phase() === 'detected' && props.state.candidates().length > 0}>
|
||||
|
||||
+24
-20
@@ -1,5 +1,6 @@
|
||||
import { Component, Show, createSignal, onMount } from 'solid-js';
|
||||
import { Button } from '@/components/shared/Button';
|
||||
import { CalloutCard } from '@/components/shared/CalloutCard';
|
||||
import {
|
||||
formCheckbox,
|
||||
formControl,
|
||||
@@ -103,11 +104,6 @@ const payloadFromForm = (form: AvailabilityForm): AvailabilityTarget => {
|
||||
};
|
||||
};
|
||||
|
||||
const testToneClass = (result: AvailabilityTestResponse) =>
|
||||
result.success
|
||||
? 'border-green-300 bg-green-50 text-green-800 dark:border-green-900 dark:bg-green-950 dark:text-green-200'
|
||||
: 'border-rose-300 bg-rose-50 text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200';
|
||||
|
||||
const presetSensitiveFormKeys: ReadonlySet<keyof AvailabilityForm> = new Set([
|
||||
'path',
|
||||
'port',
|
||||
@@ -379,33 +375,41 @@ export const AvailabilityTargetSlot: Component<AvailabilityTargetSlotProps> = (p
|
||||
|
||||
<Show when={testResult()}>
|
||||
{(result) => (
|
||||
<div class={`rounded-md border px-4 py-3 text-sm ${testToneClass(result())}`}>
|
||||
{result().success
|
||||
? `Probe reached the target in ${result().latencyMillis} ms.`
|
||||
: result().error || 'Probe failed.'}
|
||||
</div>
|
||||
<CalloutCard
|
||||
role={result().success ? 'status' : 'alert'}
|
||||
tone={result().success ? 'success' : 'danger'}
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={
|
||||
result().success
|
||||
? `Probe reached the target in ${result().latencyMillis} ms.`
|
||||
: result().error || 'Probe failed.'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Show when={error()}>
|
||||
{(message) => (
|
||||
<div
|
||||
<CalloutCard
|
||||
role="alert"
|
||||
class="rounded-md border border-rose-300 bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200"
|
||||
>
|
||||
{message()}
|
||||
</div>
|
||||
tone="danger"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={message()}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Show when={props.deleteError}>
|
||||
{(message) => (
|
||||
<div
|
||||
<CalloutCard
|
||||
role="alert"
|
||||
class="rounded-md border border-rose-300 bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200"
|
||||
>
|
||||
{message()}
|
||||
</div>
|
||||
tone="danger"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={message()}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
|
||||
+16
-9
@@ -1,5 +1,6 @@
|
||||
import { Component, Show, createEffect, onMount } from 'solid-js';
|
||||
import { Button } from '@/components/shared/Button';
|
||||
import { CalloutCard } from '@/components/shared/CalloutCard';
|
||||
import {
|
||||
formCheckbox,
|
||||
formControl,
|
||||
@@ -58,10 +59,15 @@ export const TrueNASCredentialSlot: Component<TrueNASCredentialSlotProps> = (pro
|
||||
return (
|
||||
<div class="space-y-6">
|
||||
<Show when={props.state.featureDisabled()}>
|
||||
<div class="rounded-md border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-200">
|
||||
{props.state.featureDisabledMessage() ||
|
||||
'TrueNAS connections are disabled for this Pulse tier.'}
|
||||
</div>
|
||||
<CalloutCard
|
||||
tone="warning"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={
|
||||
props.state.featureDisabledMessage() ||
|
||||
'TrueNAS connections are disabled for this Pulse tier.'
|
||||
}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.state.featureDisabled()}>
|
||||
@@ -299,12 +305,13 @@ export const TrueNASCredentialSlot: Component<TrueNASCredentialSlotProps> = (pro
|
||||
|
||||
<Show when={props.deleteError}>
|
||||
{(message) => (
|
||||
<div
|
||||
<CalloutCard
|
||||
role="alert"
|
||||
class="rounded-md border border-rose-300 bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200"
|
||||
>
|
||||
{message()}
|
||||
</div>
|
||||
tone="danger"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={message()}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
|
||||
+19
-21
@@ -59,10 +59,15 @@ export const VMwareCredentialSlot: Component<VMwareCredentialSlotProps> = (props
|
||||
return (
|
||||
<div class="space-y-6">
|
||||
<Show when={props.state.featureDisabled()}>
|
||||
<div class="rounded-md border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-200">
|
||||
{props.state.featureDisabledMessage() ||
|
||||
'VMware connections are disabled for this Pulse tier.'}
|
||||
</div>
|
||||
<CalloutCard
|
||||
tone="warning"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={
|
||||
props.state.featureDisabledMessage() ||
|
||||
'VMware connections are disabled for this Pulse tier.'
|
||||
}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.state.featureDisabled()}>
|
||||
@@ -119,9 +124,7 @@ export const VMwareCredentialSlot: Component<VMwareCredentialSlotProps> = (props
|
||||
<input
|
||||
class={formControl}
|
||||
value={props.state.form().username}
|
||||
onInput={(event) =>
|
||||
props.state.updateForm({ username: event.currentTarget.value })
|
||||
}
|
||||
onInput={(event) => props.state.updateForm({ username: event.currentTarget.value })}
|
||||
placeholder="administrator@vsphere.local"
|
||||
/>
|
||||
</label>
|
||||
@@ -131,9 +134,7 @@ export const VMwareCredentialSlot: Component<VMwareCredentialSlotProps> = (props
|
||||
class={formControl}
|
||||
type="password"
|
||||
value={props.state.form().password}
|
||||
onInput={(event) =>
|
||||
props.state.updateForm({ password: event.currentTarget.value })
|
||||
}
|
||||
onInput={(event) => props.state.updateForm({ password: event.currentTarget.value })}
|
||||
placeholder={
|
||||
props.state.form().hasStoredPassword
|
||||
? 'Saved password retained unless replaced'
|
||||
@@ -169,9 +170,7 @@ export const VMwareCredentialSlot: Component<VMwareCredentialSlotProps> = (props
|
||||
type="checkbox"
|
||||
class={formCheckbox}
|
||||
checked={props.state.form().enabled}
|
||||
onChange={(event) =>
|
||||
props.state.updateForm({ enabled: event.currentTarget.checked })
|
||||
}
|
||||
onChange={(event) => props.state.updateForm({ enabled: event.currentTarget.checked })}
|
||||
/>
|
||||
<span class="text-sm text-base-content">Enable this vCenter connection</span>
|
||||
</label>
|
||||
@@ -246,12 +245,13 @@ export const VMwareCredentialSlot: Component<VMwareCredentialSlotProps> = (props
|
||||
|
||||
<Show when={props.deleteError}>
|
||||
{(message) => (
|
||||
<div
|
||||
<CalloutCard
|
||||
role="alert"
|
||||
class="rounded-md border border-rose-300 bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200"
|
||||
>
|
||||
{message()}
|
||||
</div>
|
||||
tone="danger"
|
||||
scale="compact"
|
||||
padding="sm"
|
||||
description={message()}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -326,9 +326,7 @@ export const VMwareCredentialSlot: Component<VMwareCredentialSlotProps> = (props
|
||||
variant="outline"
|
||||
size="settingsAction"
|
||||
onClick={() => void props.state.previewCurrentForm()}
|
||||
disabled={
|
||||
props.state.saving() || props.state.testing() || props.state.previewing()
|
||||
}
|
||||
disabled={props.state.saving() || props.state.testing() || props.state.previewing()}
|
||||
title="Show the resources Pulse would ingest from this connection without saving or starting polling."
|
||||
>
|
||||
{props.state.previewing() ? 'Previewing…' : 'Preview impact'}
|
||||
|
||||
@@ -59,6 +59,7 @@ import nodeModalMonitoringSectionSource from '../NodeModalMonitoringSection.tsx?
|
||||
import nodeModalSetupGuideSectionSource from '../NodeModalSetupGuideSection.tsx?raw';
|
||||
import nodeModalStatusFooterSource from '../NodeModalStatusFooter.tsx?raw';
|
||||
import nodeModalStateSource from '../useNodeModalState.ts?raw';
|
||||
import availabilityTargetSlotSource from '../ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx?raw';
|
||||
import trueNASCredentialSlotSource from '../ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx?raw';
|
||||
import vmwareCredentialSlotSource from '../ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx?raw';
|
||||
import organizationAccessManagementSectionSource from '../OrganizationAccessManagementSection.tsx?raw';
|
||||
@@ -321,6 +322,9 @@ describe('settings architecture guardrails', () => {
|
||||
reportingPanelSource,
|
||||
securityAuthPanelSource,
|
||||
securityOverviewPanelSource,
|
||||
addressProbeStepSource,
|
||||
availabilityTargetSlotSource,
|
||||
trueNASCredentialSlotSource,
|
||||
vmwareCredentialSlotSource,
|
||||
]) {
|
||||
expect(source).toContain('CalloutCard');
|
||||
@@ -329,6 +333,15 @@ describe('settings architecture guardrails', () => {
|
||||
expect(discoverySettingsFormSource).toContain('scale="compact"');
|
||||
expect(aiProviderConfigurationSectionSource).toContain('scale="compact"');
|
||||
expect(diagnosticsResultsPanelSource).toContain('scale="compact"');
|
||||
for (const source of [
|
||||
addressProbeStepSource,
|
||||
availabilityTargetSlotSource,
|
||||
trueNASCredentialSlotSource,
|
||||
vmwareCredentialSlotSource,
|
||||
]) {
|
||||
expect(source).toContain('scale="compact"');
|
||||
expect(source).toContain('padding="sm"');
|
||||
}
|
||||
expect(discoverySettingsFormSource).not.toContain(
|
||||
'rounded-md border border-amber-200 bg-amber-50/80',
|
||||
);
|
||||
@@ -336,6 +349,25 @@ describe('settings architecture guardrails', () => {
|
||||
expect(diagnosticsResultsPanelSource).not.toContain(
|
||||
'rounded-md border border-amber-200 bg-amber-50',
|
||||
);
|
||||
expect(addressProbeStepSource).not.toContain('rounded-md border border-red-300 bg-red-50');
|
||||
expect(addressProbeStepSource).not.toContain('rounded-md border border-amber-300 bg-amber-50');
|
||||
expect(availabilityTargetSlotSource).not.toContain('testToneClass');
|
||||
expect(availabilityTargetSlotSource).not.toContain('border-green-300 bg-green-50');
|
||||
expect(availabilityTargetSlotSource).not.toContain(
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
);
|
||||
expect(trueNASCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-amber-300 bg-amber-50',
|
||||
);
|
||||
expect(trueNASCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
);
|
||||
expect(vmwareCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-amber-300 bg-amber-50',
|
||||
);
|
||||
expect(vmwareCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps telemetry disclosure aligned with the security privacy contract', () => {
|
||||
|
||||
@@ -3256,15 +3256,42 @@ describe('shared primitive guardrails', () => {
|
||||
requiredConsumers?: Array<{ path?: string }>;
|
||||
forbiddenPatterns?: Array<{ path?: string; patterns?: string[] }>;
|
||||
}>;
|
||||
patternGuards?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
scopes?: string[];
|
||||
allPatterns?: string[];
|
||||
}>;
|
||||
};
|
||||
const registeredRule = registry.rules?.find(
|
||||
(rule) => rule.id === 'settings-callout-card-shell',
|
||||
);
|
||||
const registeredWarningGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'settings-connection-editor-local-warning-callout-shell',
|
||||
);
|
||||
const registeredDangerGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'settings-connection-editor-local-danger-callout-shell',
|
||||
);
|
||||
const registeredRedGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'settings-connection-editor-local-red-callout-shell',
|
||||
);
|
||||
|
||||
expect(registeredRule?.canonical?.path).toBe('src/components/shared/CalloutCard.tsx');
|
||||
expect(registeredRule?.canonical?.export).toBe('CalloutCard');
|
||||
for (const guard of [registeredWarningGuard, registeredDangerGuard, registeredRedGuard]) {
|
||||
expect(guard?.canonical?.path).toBe('src/components/shared/CalloutCard.tsx');
|
||||
expect(guard?.canonical?.export).toBe('CalloutCard');
|
||||
expect(guard?.scopes).toEqual(['src/components/Settings/ConnectionEditor']);
|
||||
expect(guard?.allPatterns).toContain('rounded-md border');
|
||||
}
|
||||
expect(registeredWarningGuard?.allPatterns).toContain('bg-amber-50');
|
||||
expect(registeredDangerGuard?.allPatterns).toContain('bg-rose-50');
|
||||
expect(registeredRedGuard?.allPatterns).toContain('bg-red-50');
|
||||
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual([
|
||||
'src/components/Settings/AIProviderConfigurationSection.tsx',
|
||||
'src/components/Settings/ConnectionEditor/AddressProbeStep.tsx',
|
||||
'src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx',
|
||||
'src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx',
|
||||
'src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx',
|
||||
'src/components/Settings/DiagnosticsResultsPanel.tsx',
|
||||
'src/components/Settings/DiscoverySettingsForm.tsx',
|
||||
@@ -3279,6 +3306,35 @@ describe('shared primitive guardrails', () => {
|
||||
path: 'src/components/Settings/AIProviderConfigurationSection.tsx',
|
||||
patterns: expect.arrayContaining(['rounded border border-red-200']),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/ConnectionEditor/AddressProbeStep.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'rounded-md border border-red-300 bg-red-50',
|
||||
'rounded-md border border-amber-300 bg-amber-50',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'border-green-300 bg-green-50',
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
'testToneClass',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'rounded-md border border-amber-300 bg-amber-50',
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'rounded-md border border-amber-300 bg-amber-50',
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/DiagnosticsResultsPanel.tsx',
|
||||
patterns: expect.arrayContaining(['rounded-md border border-amber-200 bg-amber-50']),
|
||||
@@ -3303,11 +3359,42 @@ describe('shared primitive guardrails', () => {
|
||||
reportingPanelSource,
|
||||
securityAuthPanelSource,
|
||||
securityOverviewPanelSource,
|
||||
addressProbeStepSource,
|
||||
availabilityTargetSlotSource,
|
||||
trueNASCredentialSlotSource,
|
||||
vmwareCredentialSlotSource,
|
||||
]) {
|
||||
expect(source).toContain('CalloutCard');
|
||||
}
|
||||
for (const source of [
|
||||
addressProbeStepSource,
|
||||
availabilityTargetSlotSource,
|
||||
trueNASCredentialSlotSource,
|
||||
vmwareCredentialSlotSource,
|
||||
]) {
|
||||
expect(source).toContain('scale="compact"');
|
||||
expect(source).toContain('padding="sm"');
|
||||
}
|
||||
expect(aiProviderConfigurationSectionSource).not.toContain('rounded border border-red-200');
|
||||
expect(addressProbeStepSource).not.toContain('rounded-md border border-red-300 bg-red-50');
|
||||
expect(addressProbeStepSource).not.toContain('rounded-md border border-amber-300 bg-amber-50');
|
||||
expect(availabilityTargetSlotSource).not.toContain('testToneClass');
|
||||
expect(availabilityTargetSlotSource).not.toContain('border-green-300 bg-green-50');
|
||||
expect(availabilityTargetSlotSource).not.toContain(
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
);
|
||||
expect(trueNASCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-amber-300 bg-amber-50',
|
||||
);
|
||||
expect(trueNASCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
);
|
||||
expect(vmwareCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-amber-300 bg-amber-50',
|
||||
);
|
||||
expect(vmwareCredentialSlotSource).not.toContain(
|
||||
'rounded-md border border-rose-300 bg-rose-50',
|
||||
);
|
||||
expect(diagnosticsResultsPanelSource).not.toContain(
|
||||
'rounded-md border border-amber-200 bg-amber-50',
|
||||
);
|
||||
|
||||
@@ -2860,7 +2860,7 @@ class SubsystemLookupTest(unittest.TestCase):
|
||||
{
|
||||
"heading": "## Shared Boundaries",
|
||||
"path": "internal/api/access_control_handlers.go",
|
||||
"line": 465,
|
||||
"line": 469,
|
||||
"heading_line": 123,
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user