mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Align first-run infrastructure source paths
Route first-run guidance through the unified infrastructure source picker and align setup, tour, dashboard, and empty-state copy with the source-strategy model.
This commit is contained in:
parent
2a85408a7f
commit
4fb67cd547
30 changed files with 376 additions and 353 deletions
|
|
@ -477,21 +477,21 @@ an add-only capacity posture.
|
|||
6. Keep Proxmox registration continuity self-healing: stale local registration markers must be verified against Pulse before the host agent skips setup, and a missing matching node on the Pulse side must drive canonical re-registration instead of asking operators to delete marker files manually.
|
||||
7. Keep first-session lifecycle handoff explicit: the live setup completion
|
||||
surface in `frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`
|
||||
must route the primary CTA into `/settings/infrastructure/install`, frame
|
||||
that route as the first-host install step, and present `Platform
|
||||
connections` as the named API-backed alternative for Proxmox, TrueNAS, and
|
||||
future provider integrations rather than leaving post-setup next actions
|
||||
implicit. That API-backed alternative must be a real first-run handoff
|
||||
control, not prose-only guidance.
|
||||
must route the primary CTA into `/settings/infrastructure?add=pick`, frame
|
||||
that route as source strategy selection, and present platform API inventory
|
||||
plus Pulse Agent telemetry as peer choices for Proxmox, TrueNAS, VMware,
|
||||
standalone hosts, and future provider integrations rather than leaving
|
||||
post-setup next actions implicit. A direct Pulse Agent handoff may remain as
|
||||
a secondary control for operators who already know the first source is
|
||||
agent-managed, but the primary first-run path is the unified source picker.
|
||||
Once the completion surface observes connected systems, that same handoff
|
||||
model must derive its follow-up actions from the canonical connected-system
|
||||
path classification rather than a raw connected-agent count. API-backed
|
||||
first-session states must keep `Platform connections` visible without
|
||||
hiding `Infrastructure Install` when the next system should run the unified
|
||||
agent, and install-managed first-session states must not suppress the
|
||||
explicit API-backed alternative when the runtime has already connected
|
||||
platform-owned systems. The API-backed versus install-workspace split must
|
||||
come from the governed onboarding paths in
|
||||
first-session states must keep `Add infrastructure` visible for both
|
||||
API-backed and agent-managed next systems instead of reviving separate
|
||||
`Platform connections` and `Infrastructure Install` branches. The
|
||||
API-backed versus agent-managed classification must come from the governed
|
||||
onboarding paths in
|
||||
`docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json` through
|
||||
the shared frontend manifest helper, not from a Setup Wizard-local platform
|
||||
allowlist. When preview-only browser proof needs a deterministic connected
|
||||
|
|
@ -520,9 +520,10 @@ connections` as the named API-backed alternative for Proxmox, TrueNAS, and
|
|||
still belong to the reporting inventory and inline lifecycle detail, but
|
||||
they must not appear as peer connection rows on that top ledger. Adding a
|
||||
new system must stay a single entry point on that ledger:
|
||||
one `Add connection` entry point that keeps `Install on a host` explicit
|
||||
for the agent path while opening the saved-connection create flow for
|
||||
API-backed platforms on the same page. `/settings/infrastructure/install`,
|
||||
one `Add infrastructure` entry point that opens the source picker, keeps
|
||||
`Install on a host` explicit only after the operator chooses Pulse Agent,
|
||||
and opens the saved-connection create flow for API-backed platforms on the
|
||||
same page. `/settings/infrastructure/install`,
|
||||
`/settings/infrastructure/platforms`, and
|
||||
`/settings/infrastructure/operations` remain valid deep links, but they
|
||||
must resolve to section focus on that same single-page workspace rather
|
||||
|
|
@ -544,15 +545,15 @@ connections` as the named API-backed alternative for Proxmox, TrueNAS, and
|
|||
11. Keep the dev first-session proof deterministic on the real wizard path:
|
||||
`tests/integration/tests/helpers.ts` and
|
||||
`tests/integration/tests/11-first-session.spec.ts` must refresh first-run
|
||||
state through `/api/security/dev/reset-first-run`, then prove both the
|
||||
canonical `Open Infrastructure Install` handoff and the explicit
|
||||
`Open Platform connections` handoff against the live setup wizard instead
|
||||
of relying on stale bootstrap tokens, dashboard fallbacks, or preview-only
|
||||
coverage. That API-backed handoff may keep the operator-facing `Platform
|
||||
connections` label, but it must land on the shared infrastructure
|
||||
onboarding contract at `/settings/infrastructure?add=pick` and normalize
|
||||
back to `/settings/infrastructure` instead of reviving a separate
|
||||
platform-management shell.
|
||||
state through `/api/security/dev/reset-first-run`, then prove the
|
||||
canonical `Add infrastructure` handoff and the explicit `Install Pulse
|
||||
Agent` secondary handoff against the live setup wizard instead of relying
|
||||
on stale bootstrap tokens, dashboard fallbacks, or preview-only coverage.
|
||||
The primary handoff must land on the shared infrastructure onboarding
|
||||
contract at `/settings/infrastructure?add=pick` and normalize back to
|
||||
`/settings/infrastructure` instead of reviving a separate
|
||||
platform-management shell. The secondary agent handoff must land on
|
||||
`/settings/infrastructure?add=agent`.
|
||||
When the first host reports successfully, the install workflow must treat
|
||||
that as a completion handoff with direct navigation into `/dashboard` and
|
||||
`/settings/infrastructure/operations` instead of leaving operators on a
|
||||
|
|
@ -572,16 +573,19 @@ connections` label, but it must land on the shared infrastructure
|
|||
ordered around the actual first-run operator sequence: credentials that must
|
||||
be saved now should be visible before the operator leaves the screen, and
|
||||
the completion surface should present one canonical primary next-step path
|
||||
into Infrastructure Install instead of repeating competing install or
|
||||
dashboard CTAs across multiple sections. Once the first monitored host is
|
||||
into Add infrastructure instead of repeating competing install or dashboard
|
||||
CTAs across multiple sections. Once the first monitored system is
|
||||
already connected, that same surface must pivot its primary CTA and headline
|
||||
to `/` so the operator is sent to the dashboard rather than being told to
|
||||
install the first host again. While the first host is still pending, that
|
||||
same completion narrative must describe Infrastructure Install as the place
|
||||
where the first-host scoped install token is prepared from setup handoff,
|
||||
and when it names the shared settings workspace for follow-up lifecycle
|
||||
control it must use the canonical `Infrastructure` label instead of
|
||||
reviving the retired `Infrastructure Operations` wording.
|
||||
connect the first source again. While the first source is still pending,
|
||||
that same completion narrative must describe Add infrastructure as the
|
||||
place where the operator chooses platform API inventory, Pulse Agent
|
||||
telemetry, or both. If the operator selects the direct agent path from that
|
||||
completion surface, the agent install body may prepare the first-host
|
||||
scoped install token from setup handoff, and when it names the shared
|
||||
settings workspace for follow-up lifecycle control it must use the
|
||||
canonical `Infrastructure` label instead of reviving the retired
|
||||
`Infrastructure Operations` wording.
|
||||
not as a second manual token-generation task the operator still needs to
|
||||
figure out.
|
||||
13. Keep API-backed platform onboarding explicit across
|
||||
|
|
@ -590,11 +594,12 @@ connections` label, but it must land on the shared infrastructure
|
|||
`frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx`,
|
||||
`frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx`, and
|
||||
`frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx`.
|
||||
TrueNAS must be presented as a Platform connections workflow first, not as
|
||||
a dedicated Unified Agent install profile. The install workspace may remain
|
||||
available for optional later agent augmentation on TrueNAS, but first-run
|
||||
copy, alternative CTAs, and install-profile lists must not imply that an
|
||||
agent install is the required bootstrap for TrueNAS support in Pulse.
|
||||
TrueNAS must be presented as an API-backed source flow through Add
|
||||
infrastructure first, not as a dedicated Unified Agent install profile. The
|
||||
agent install path may remain available for optional later agent
|
||||
augmentation on TrueNAS, but first-run copy, alternative CTAs, and
|
||||
install-profile lists must not imply that an agent install is the required
|
||||
bootstrap for TrueNAS support in Pulse.
|
||||
14. Keep first-session and lifecycle-adjacent frontend resource handling on the
|
||||
canonical unified-resource boundary. Top-level TrueNAS appliances may reach
|
||||
setup-completion or infrastructure lifecycle surfaces only as canonical
|
||||
|
|
@ -610,9 +615,10 @@ connections` label, but it must land on the shared infrastructure
|
|||
16. Keep onboarding ownership aligned with
|
||||
`docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md`: agent-backed
|
||||
first-class platforms belong to the install/reporting lifecycle path,
|
||||
API-backed first-class platforms belong to Platform connections, and any
|
||||
later unified-agent augmentation on an API-backed platform must remain an
|
||||
optional secondary path instead of silently becoming the required bootstrap.
|
||||
API-backed first-class platforms belong to the Add infrastructure API
|
||||
source flow, and any later unified-agent augmentation on an API-backed
|
||||
platform must remain an optional secondary path instead of silently
|
||||
becoming the required bootstrap.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
@ -753,12 +759,12 @@ just to decide whether to show assistant-adjacent UI.
|
|||
That same platform-connections ownership now also includes mock-runtime
|
||||
continuity for API-backed platforms. When `/api/system/mock-mode` flips a
|
||||
running server between real and mock data, the canonical TrueNAS and VMware
|
||||
settings routes must keep surfacing through the same Platform connections
|
||||
workspace and handoff URLs instead of depending on process-start-only wiring
|
||||
or a mock-only alternate shell.
|
||||
settings routes must keep surfacing through the same Add infrastructure source
|
||||
picker and handoff URLs instead of depending on process-start-only wiring or a
|
||||
mock-only alternate shell.
|
||||
That same lifecycle-owned mock path now also requires one shared fixture owner
|
||||
for API-backed platform onboarding. TrueNAS and VMware connection-list payloads
|
||||
shown in Platform connections must be assembled from the canonical
|
||||
shown in Add infrastructure must be assembled from the canonical
|
||||
`internal/mock/` platform fixture layer, so settings handoff metadata cannot
|
||||
drift from the runtime mock inventory and shared storage/recovery context.
|
||||
That same lifecycle-adjacent mock path must stay graph-first at the shared
|
||||
|
|
@ -1043,10 +1049,11 @@ Infrastructure workspace. `ConnectionsTable.tsx`,
|
|||
install/direct/reporting operator flow, with `ConnectionsTable.tsx` plus
|
||||
`connectionsTableModel.ts` as the canonical top-level infrastructure ledger
|
||||
and the governed add/edit modals as the API-backed add/edit surface.
|
||||
Operator-facing setup copy may still use `Platform connections`, but that
|
||||
label now means the shared Infrastructure onboarding path
|
||||
(`/settings/infrastructure?add=pick`) rather than a standalone
|
||||
`PlatformConnectionsWorkspace.tsx` shell.
|
||||
Operator-facing setup copy should use `Add infrastructure` and source-strategy
|
||||
language for the shared Infrastructure onboarding path
|
||||
(`/settings/infrastructure?add=pick`) rather than reviving the standalone
|
||||
`PlatformConnectionsWorkspace.tsx` shell or the old `Platform connections`
|
||||
label.
|
||||
That infrastructure destination now has one canonical mental model:
|
||||
configured infrastructure sources stay visible on the landing page as the
|
||||
primary objects the operator manages. The landing table is instance-first, not
|
||||
|
|
@ -1124,7 +1131,7 @@ surfaces grow a second VMware availability fetch or a VMware-only handoff
|
|||
path.
|
||||
That same infrastructure workspace boundary now also owns the first-run
|
||||
handoff copy for new operators. `InfrastructureWorkspace.tsx` must keep
|
||||
`Install on a host` and `Platform connections` explicit in the shared
|
||||
platform API inventory and Pulse Agent telemetry explicit in the shared
|
||||
workspace instead of leaving first-session guidance implicit in generic
|
||||
settings-shell prose or retreating to one provider's name or one onboarding
|
||||
mode as the primary story.
|
||||
|
|
@ -2107,17 +2114,17 @@ advertising automatic token rotation after each copy once the active transport
|
|||
is explicitly tokenless.
|
||||
The same first-session contract now also owns the landing handoff after secure
|
||||
setup: RC-proof and helpers must treat direct navigation into
|
||||
`/settings/infrastructure/install` as the canonical completion path, rather
|
||||
than assuming the legacy dashboard-only landing still defines successful
|
||||
wizard completion.
|
||||
`/settings/infrastructure?add=pick` as the canonical completion path, rather
|
||||
than assuming an agent-only install landing or the legacy dashboard-only
|
||||
landing still defines successful wizard completion.
|
||||
That same `SetupCompletionPanel` boundary must also stay on the direct
|
||||
`setup-completion-install-surface` proof path, rather than relying only on shared
|
||||
helper coverage or downstream install tests to catch lifecycle drift in the
|
||||
setup completion surface.
|
||||
`setup-completion-source-picker-surface` proof path, rather than relying only
|
||||
on shared helper coverage or downstream install tests to catch lifecycle drift
|
||||
in the setup completion surface.
|
||||
That same first-session browser proof must also exercise the explicit
|
||||
`Platform connections` completion action through the real setup wizard flow
|
||||
for API-backed starts like TrueNAS, rather than relying only on the preview
|
||||
route or prose-level assertions to represent the API-backed alternative.
|
||||
`Install Pulse Agent` secondary action through the real setup wizard flow,
|
||||
rather than relying only on the preview route or prose-level assertions to
|
||||
represent the agent-managed alternative.
|
||||
The same ownership also covers manual install fallback in the infrastructure
|
||||
settings surface: active and ignored Connected infrastructure rows must now
|
||||
come from the backend-owned `connectedInfrastructure` projection instead of a
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ work extends shared components instead of creating new local variants.
|
|||
The shared navigation guide owns route-aware first focus: when it opens
|
||||
from a top-level product route such as `/recovery`, the first highlighted
|
||||
step should match that route instead of always restarting at Dashboard.
|
||||
5. Keep shared infrastructure shell state on the reusable settings boundary: `frontend-modern/src/components/Settings/useSettingsInfrastructurePanelProps.ts` and `frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx` must continue to derive provider counts, availability, and shared subtab copy from one infrastructure-settings source — via the unified aggregator through `frontend-modern/src/components/Settings/useConnectionsLedger.ts` — instead of creating provider-local summary fetches or VMware-only shell vocabulary. Phase 9 retired the old `PlatformConnectionsWorkspace` per-type shell, but setup guidance may still use `Platform connections` as the operator-facing label for the shared API-backed onboarding path.
|
||||
5. Keep shared infrastructure shell state on the reusable settings boundary: `frontend-modern/src/components/Settings/useSettingsInfrastructurePanelProps.ts` and `frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx` must continue to derive provider counts, availability, and shared subtab copy from one infrastructure-settings source — via the unified aggregator through `frontend-modern/src/components/Settings/useConnectionsLedger.ts` — instead of creating provider-local summary fetches or VMware-only shell vocabulary. Phase 9 retired the old `PlatformConnectionsWorkspace` per-type shell; setup guidance should now use `Add infrastructure` plus source-strategy language for API-backed onboarding.
|
||||
That same shared shell boundary now owns the first-run posture for
|
||||
`/settings/infrastructure`: the landing route should read as one
|
||||
source-manager workspace with configured infrastructure instances first
|
||||
|
|
@ -584,16 +584,17 @@ work extends shared components instead of creating new local variants.
|
|||
path to the bare `/settings/infrastructure`, which renders the unified
|
||||
Connections table, not to a separate install subview or to reporting/
|
||||
control. The first-session story is owned by that table's own empty state
|
||||
and the `Add connection` entry point on it, not by a second landing route,
|
||||
and the `Add infrastructure` entry point on it, not by a second landing route,
|
||||
so first-time operators and returning operators see one consistent
|
||||
infrastructure surface by default.
|
||||
14. Keep dashboard onboarding copy on the shared presentation owner in
|
||||
`frontend-modern/src/utils/dashboardEmptyStatePresentation.ts`. Both the
|
||||
infrastructure empty state and the dashboard route's no-resources state
|
||||
must name the canonical install workspace explicitly, keep `Platform
|
||||
connections` visible as the API-backed alternative for Proxmox and
|
||||
TrueNAS, and expose the same first-host next step instead of falling back
|
||||
to passive “nothing here yet” wording.
|
||||
must route first-time operators into the canonical
|
||||
`/settings/infrastructure?add=pick` source picker, describe platform API
|
||||
inventory and Pulse Agent telemetry as equal source strategies, and avoid
|
||||
falling back to either passive “nothing here yet” wording or the retired
|
||||
install-first / `Platform connections` split.
|
||||
15. Keep cross-surface investigation handoffs on shared route ownership.
|
||||
Feature shells such as Alerts and Patrol may decide which governed
|
||||
destination chips to render, but canonical href, label, dedupe, and
|
||||
|
|
@ -642,9 +643,10 @@ connections` visible as the API-backed alternative for Proxmox and
|
|||
that helper instead of maintaining page-local copies of the same hover/focus
|
||||
rules.
|
||||
`frontend-modern/src/App.tsx` must land `/` on the dashboard shell and let
|
||||
the governed dashboard empty state route first-time operators into
|
||||
Infrastructure Install, instead of preserving a separate root-only jump to
|
||||
`/infrastructure` that drifts from the rest of the onboarding contract.
|
||||
the governed dashboard empty state route first-time operators into the
|
||||
`Add infrastructure` source picker, instead of preserving a separate
|
||||
root-only jump to `/infrastructure` or an agent-only install jump that
|
||||
drifts from the rest of the onboarding contract.
|
||||
The same entry-shell contract must also canonicalize authenticated
|
||||
`/login`: once auth succeeds, the shared shell must resolve that route back
|
||||
onto the governed dashboard landing path instead of rendering a page-local
|
||||
|
|
@ -1446,6 +1448,9 @@ the new navigation in one pass without needing historical layout context.
|
|||
That copy should stay direct and present-tense. Each guided step should say
|
||||
what the destination does, not depend on v5 comparisons, migration framing, or
|
||||
older information architecture to make sense.
|
||||
The Infrastructure tour step must describe the source model directly: platform
|
||||
API inventory, Pulse Agent telemetry, and discovered candidates are managed as
|
||||
infrastructure sources in one place.
|
||||
That guided welcome surface should stay compact. The canonical shape is a
|
||||
coachmark-sized card centered on the current destination with one short
|
||||
step-specific sentence, a small clickable step strip, and minimal footer
|
||||
|
|
@ -1596,7 +1601,16 @@ implementation fallback language.
|
|||
The estate summary may surface resource and alert issue counts only as
|
||||
below-the-summary detail references; it must not claim the whole dashboard has
|
||||
no issues when storage or recovery widgets still own independent health
|
||||
signals.
|
||||
signals. Those detail references must use governed dashboard section anchors so
|
||||
the first viewport can move focus to Problem Resources or Alerts without
|
||||
inventing separate dashboard drill-down routes.
|
||||
The dashboard may add an optional Pulse Brief below that estate orientation
|
||||
only when Assistant and Patrol are actually enabled and configured. That brief
|
||||
must stay additive to the factual dashboard source of truth, derive its first
|
||||
render from the already-owned estate, overview, action, storage, and recovery
|
||||
facts, and hand off to Pulse Assistant through a structured context prompt
|
||||
instead of replacing the route's canonical numbers, tables, or lane-owned
|
||||
widgets with model prose.
|
||||
The recovery feature shell now also depends on the shared
|
||||
`frontend-modern/src/components/shared/Subtabs.tsx` primitive for its primary
|
||||
protected-items versus recovery-events workspace switch. The recovery lane may
|
||||
|
|
@ -2415,18 +2429,18 @@ reference cases, and
|
|||
`frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts`
|
||||
locks that direct-root contract so single-surface pages do not quietly regain
|
||||
redundant outer spacing chrome.
|
||||
The same shared settings-shell boundary now also owns the API-backed
|
||||
alternative path inside Infrastructure.
|
||||
The same shared settings-shell boundary now also owns the API-backed source
|
||||
path inside Infrastructure.
|
||||
`frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx`,
|
||||
`frontend-modern/src/components/Settings/settingsHeaderMeta.ts`,
|
||||
`frontend-modern/src/components/Settings/settingsNavigationModel.ts`,
|
||||
`frontend-modern/src/utils/dashboardEmptyStatePresentation.ts`,
|
||||
`frontend-modern/src/utils/infrastructureEmptyStatePresentation.ts`, and
|
||||
adjacent setup guidance may still use `Platform connections` as the
|
||||
operator-facing first-run label for API-backed onboarding, but that label must
|
||||
resolve to the shared `Infrastructure` destination and its inline
|
||||
`ConnectionEditor` add flow rather than reviving a standalone platform shell
|
||||
or provider-local route.
|
||||
adjacent setup guidance must use `Add infrastructure` as the operator-facing
|
||||
first-run label for API-backed onboarding, resolve that label to the shared
|
||||
`Infrastructure` destination and its inline `ConnectionEditor` add flow, and
|
||||
avoid reviving a standalone platform shell, `Platform connections` label, or
|
||||
provider-local route.
|
||||
That same settings-shell contract also owns the shared infrastructure summary
|
||||
state. `frontend-modern/src/components/Settings/useInfrastructureSettingsState.ts`,
|
||||
`frontend-modern/src/components/Settings/useSettingsInfrastructurePanelProps.ts`,
|
||||
|
|
|
|||
|
|
@ -256,11 +256,11 @@ assembly branch.
|
|||
5. Keep the infrastructure landing empty state on canonical first-run routing:
|
||||
when inventory is empty, `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
|
||||
and `frontend-modern/src/utils/infrastructureEmptyStatePresentation.ts`
|
||||
must send operators directly to `/settings/infrastructure/install`, name
|
||||
first-host install as the default next step, and keep `Platform connections`
|
||||
as the explicit API-backed alternative for Proxmox, TrueNAS, and future
|
||||
provider-backed platforms instead of regressing to generic settings-root
|
||||
CTAs or provider-specific one-off routes.
|
||||
must send operators directly to `/settings/infrastructure?add=pick`, name
|
||||
source strategy selection as the default next step, and present platform
|
||||
API inventory plus Pulse Agent telemetry as peer source options instead of
|
||||
regressing to generic settings-root CTAs, an agent-only install jump, the
|
||||
retired `Platform connections` split, or provider-specific one-off routes.
|
||||
6. Keep infrastructure route-backed source filters on canonical unified-resource
|
||||
truth. `frontend-modern/src/features/infrastructure/` must preserve a
|
||||
route-owned source such as `truenas` in the filter option set even when the
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export function DashboardStateCards(props: DashboardStateCardsProps) {
|
|||
!props.kioskMode() ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.navigate(buildInfrastructureOnboardingPath('agent'))}
|
||||
onClick={() => props.navigate(buildInfrastructureOnboardingPath('pick'))}
|
||||
class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
{props.dashboardInfrastructureEmptyState().actionLabel}
|
||||
|
|
|
|||
|
|
@ -985,7 +985,7 @@ describe('Dashboard performance contract', () => {
|
|||
expect(workloadPanelSource).not.toContain('TableHead');
|
||||
expect(dashboardStateCardsSource).toContain('dashboardInfrastructureEmptyState().title');
|
||||
expect(dashboardStateCardsSource).toContain('dashboardDisconnectedState().actionLabel');
|
||||
expect(dashboardStateCardsSource).toContain("buildInfrastructureOnboardingPath('agent')");
|
||||
expect(dashboardStateCardsSource).toContain("buildInfrastructureOnboardingPath('pick')");
|
||||
expect(dashboardStatsStripSource).toContain('totalStats().running');
|
||||
expect(dashboardStatsStripSource).toContain('totalStats().stopped');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ describe('DashboardStateCards', () => {
|
|||
description: 'No guests match your current filters',
|
||||
})}
|
||||
dashboardInfrastructureEmptyState={() => ({
|
||||
title: 'No infrastructure hosts connected',
|
||||
title: 'No infrastructure sources connected',
|
||||
description:
|
||||
'To start using Pulse, first add your infrastructure in Settings → Infrastructure → Install on a host. If you want an API-backed platform such as Proxmox or TrueNAS instead, use Settings → Infrastructure → Platform connections.',
|
||||
actionLabel: 'Open infrastructure setup',
|
||||
'Start in Settings → Infrastructure by choosing a source strategy. Connect a platform API for inventory and health, install Pulse Agent for host telemetry, or use both when you want full coverage.',
|
||||
actionLabel: 'Add infrastructure source',
|
||||
})}
|
||||
dashboardLoadingState={() => ({
|
||||
title: 'Loading dashboard data...',
|
||||
|
|
@ -45,8 +45,8 @@ describe('DashboardStateCards', () => {
|
|||
/>
|
||||
));
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open infrastructure setup' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add infrastructure source' }));
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith('/settings/infrastructure?add=agent');
|
||||
expect(navigate).toHaveBeenCalledWith('/settings/infrastructure?add=pick');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export const InfrastructureInstallerSection: Component = () => {
|
|||
<div class="space-y-2">
|
||||
<p class="font-semibold">Security configured. Save these first-run credentials now.</p>
|
||||
<p class="text-xs text-emerald-800 dark:text-emerald-200">
|
||||
This is the canonical handoff from first-run setup into Infrastructure Install.
|
||||
This is the Pulse Agent handoff from first-run setup inside Add infrastructure.
|
||||
<Show
|
||||
when={state.setupHandoffAutoTokenPending()}
|
||||
fallback={
|
||||
|
|
@ -475,8 +475,8 @@ export const InfrastructureInstallerSection: Component = () => {
|
|||
{state.getSelectedInstallProfile().description}
|
||||
</p>
|
||||
<p class="mt-1.5 text-xs text-muted">
|
||||
API-backed platforms such as TrueNAS connect through Add connection →
|
||||
choose the platform type.
|
||||
API-backed platforms such as TrueNAS connect through Add infrastructure →
|
||||
choose source type.
|
||||
</p>
|
||||
<Show when={state.getInstallProfileFlags().length > 0}>
|
||||
<p class="mt-1.5 text-xs text-muted">
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ describe('ConnectionsTable', () => {
|
|||
|
||||
expect(screen.getByText('Start monitoring infrastructure')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Available system types: VMware vCenter, TrueNAS SCALE/i),
|
||||
screen.getByText(/Supported source types include VMware vCenter, TrueNAS SCALE/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByRole('table')).toBeNull();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -387,9 +387,9 @@ describe('InfrastructureWorkspace', () => {
|
|||
expect(screen.getByText('Start monitoring infrastructure')).toBeInTheDocument(),
|
||||
);
|
||||
expect(
|
||||
screen.getByText('Add infrastructure systems to start monitoring your environment.'),
|
||||
screen.getByText('Choose an infrastructure source to start monitoring your environment.'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Available system types: VMware vCenter/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Supported source types include VMware vCenter/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/standalone hosts through Pulse Agent/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
Component,
|
||||
createSignal,
|
||||
createEffect,
|
||||
createMemo,
|
||||
onCleanup,
|
||||
Show,
|
||||
For,
|
||||
} from 'solid-js';
|
||||
import { Component, createSignal, createEffect, createMemo, onCleanup, Show, For } from 'solid-js';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { apiFetchJSON } from '@/utils/apiClient';
|
||||
|
|
@ -30,33 +22,33 @@ interface CompleteStepProps {
|
|||
const UNIFIED_RESOURCE_GUIDANCE = {
|
||||
title: 'What happens next',
|
||||
description:
|
||||
'Pulse is now secured. Next, choose the first infrastructure path: use Infrastructure Install for a host that should run the unified agent, or use Platform connections for API-backed platforms like Proxmox, TrueNAS, and VMware.',
|
||||
'Pulse is now secured. Next, choose how the first system should enter the unified infrastructure model: platform API inventory, Pulse Agent telemetry, or both.',
|
||||
steps: [
|
||||
{
|
||||
title: 'Open Infrastructure Install',
|
||||
title: 'Open Add infrastructure',
|
||||
description:
|
||||
'Use the canonical install workspace where Pulse prepares the first-host install token from setup and keeps Platform connections beside it when the first target is API-backed.',
|
||||
'Review the supported source types in one place before choosing a platform API, Pulse Agent, or both.',
|
||||
},
|
||||
{
|
||||
title: 'Copy the command for your target system',
|
||||
title: 'Choose the source strategy',
|
||||
description:
|
||||
'Choose Linux, macOS, Windows, or another supported target only when the first system should run the unified agent directly.',
|
||||
'Connect a platform API for inventory and health, install Pulse Agent for node-local telemetry, or combine both where full coverage matters.',
|
||||
},
|
||||
{
|
||||
title: 'Run it on the first host you want to monitor',
|
||||
title: 'Save the source and confirm coverage',
|
||||
description:
|
||||
'When that agent-managed host connects, Pulse creates your first monitored system and you can add more infrastructure from there.',
|
||||
'When the source connects, Pulse creates the first monitored system and the dashboard becomes the live estate overview.',
|
||||
},
|
||||
],
|
||||
inventoryFacts: [
|
||||
'Start with one host, then add more systems later from the same install workspace.',
|
||||
'Infrastructure Install owns the token, connection URL, TLS/CA settings, and platform-specific commands.',
|
||||
'API-backed platforms like Proxmox, TrueNAS, and VMware use Platform connections instead of a dedicated install profile in Infrastructure Install.',
|
||||
'Start with one source, then add more systems later from Settings → Infrastructure.',
|
||||
'Platform APIs own inventory and health. Pulse Agent owns host telemetry, local services, Docker, and Kubernetes discovery.',
|
||||
'VMware, TrueNAS, Proxmox, PBS, and PMG use API-backed source flows; standalone hosts use Pulse Agent.',
|
||||
],
|
||||
} as const;
|
||||
|
||||
const INFRASTRUCTURE_INSTALL_PATH = buildInfrastructureOnboardingPath('agent');
|
||||
const PLATFORM_CONNECTIONS_PATH = buildInfrastructureOnboardingPath('pick');
|
||||
const ADD_INFRASTRUCTURE_PATH = buildInfrastructureOnboardingPath('pick');
|
||||
const AGENT_INSTALL_PATH = buildInfrastructureOnboardingPath('agent');
|
||||
const SETUP_WIZARD_TELEMETRY_SURFACE = 'setup_wizard_complete';
|
||||
|
||||
export const SetupCompletionPanel: Component<CompleteStepProps> = (props) => {
|
||||
|
|
@ -104,7 +96,7 @@ export const SetupCompletionPanel: Component<CompleteStepProps> = (props) => {
|
|||
|
||||
if (
|
||||
!firstAgentConnectionTracked &&
|
||||
nextConnectedSystems.some((system) => system.connectionPath === 'install')
|
||||
nextConnectedSystems.some((system) => system.connectionPath === 'agent')
|
||||
) {
|
||||
trackAgentFirstConnected(SETUP_WIZARD_TELEMETRY_SURFACE, 'first_agent');
|
||||
firstAgentConnectionTracked = true;
|
||||
|
|
@ -142,7 +134,7 @@ export const SetupCompletionPanel: Component<CompleteStepProps> = (props) => {
|
|||
|
||||
const downloadCredentials = () => {
|
||||
const baseUrl = getPulseBaseUrl();
|
||||
const infrastructureUrl = `${baseUrl.replace(/\/$/, '')}${INFRASTRUCTURE_INSTALL_PATH}`;
|
||||
const infrastructureUrl = `${baseUrl.replace(/\/$/, '')}${ADD_INFRASTRUCTURE_PATH}`;
|
||||
const content = `Pulse Credentials
|
||||
==================
|
||||
Generated: ${new Date().toISOString()}
|
||||
|
|
@ -161,8 +153,8 @@ Infrastructure:
|
|||
---------------
|
||||
${infrastructureUrl}
|
||||
|
||||
Use Add connection to connect your first host or API-backed platform
|
||||
(Proxmox, TrueNAS, VMware, and others).
|
||||
Use Add infrastructure to choose a platform API, Pulse Agent, or both
|
||||
for the first system Pulse should monitor.
|
||||
|
||||
Keep these credentials secure!
|
||||
`;
|
||||
|
|
@ -178,21 +170,19 @@ Keep these credentials secure!
|
|||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleOpenInstallWorkspace = () => {
|
||||
props.onComplete(INFRASTRUCTURE_INSTALL_PATH);
|
||||
const handleOpenAddInfrastructure = () => {
|
||||
props.onComplete(ADD_INFRASTRUCTURE_PATH);
|
||||
};
|
||||
|
||||
const handleOpenPlatformConnections = () => {
|
||||
props.onComplete(PLATFORM_CONNECTIONS_PATH);
|
||||
const handleOpenAgentInstall = () => {
|
||||
props.onComplete(AGENT_INSTALL_PATH);
|
||||
};
|
||||
|
||||
const handleGoToDashboard = () => {
|
||||
props.onComplete('/');
|
||||
};
|
||||
|
||||
const completionViewModel = createMemo(() =>
|
||||
buildSetupCompletionViewModel(connectedSystems()),
|
||||
);
|
||||
const completionViewModel = createMemo(() => buildSetupCompletionViewModel(connectedSystems()));
|
||||
|
||||
return (
|
||||
<div class="max-w-2xl mx-auto bg-surface border border-border overflow-hidden animate-fade-in relative rounded-md p-6 sm:p-8 text-center text-base-content">
|
||||
|
|
@ -406,7 +396,7 @@ Keep these credentials secure!
|
|||
Infrastructure
|
||||
</div>
|
||||
<code class="text-base-content font-mono text-xs break-all">
|
||||
{INFRASTRUCTURE_INSTALL_PATH}
|
||||
{ADD_INFRASTRUCTURE_PATH}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
|
|
@ -507,8 +497,8 @@ Keep these credentials secure!
|
|||
</h3>
|
||||
<p class="mt-2 text-xs text-muted max-w-xl">
|
||||
{completionViewModel().hasConnectedSystems
|
||||
? 'Pulse already has a live monitored system. Open the dashboard to confirm the first overview, then return to Infrastructure when you want to continue with the next system path.'
|
||||
: 'The canonical install flow now lives in Infrastructure. Open that workspace to continue with the first-host install token Pulse prepares from setup, adjust the agent connection URL only if needed, configure TLS or custom CA options, and copy the correct command for the first system you want Pulse to monitor.'}
|
||||
? 'Pulse already has a live monitored system. Open the dashboard to confirm the first overview, then return to Add infrastructure when you want to connect the next API or Agent source.'
|
||||
: 'Add infrastructure now owns the first source decision. Open the picker to choose a platform API for inventory, Pulse Agent for host telemetry, or both when the first system needs full coverage.'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-sm bg-blue-50 px-2 py-1 text-[10px] font-medium text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||
|
|
@ -516,48 +506,43 @@ Keep these credentials secure!
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-4 rounded-md border border-border bg-surface-alt p-4">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wider text-muted">
|
||||
Next step
|
||||
</div>
|
||||
<div class="text-[11px] font-medium uppercase tracking-wider text-muted">Next step</div>
|
||||
<div class="mt-2 text-sm text-base-content">
|
||||
{completionViewModel().nextStepSummary}
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-muted">
|
||||
{completionViewModel().nextStepDetail}
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-muted">{completionViewModel().nextStepDetail}</div>
|
||||
</div>
|
||||
<div class="mt-4 flex flex-col gap-3 sm:flex-row">
|
||||
<button
|
||||
onClick={() =>
|
||||
completionViewModel().primaryAction === 'dashboard'
|
||||
? handleGoToDashboard()
|
||||
: handleOpenInstallWorkspace()
|
||||
: handleOpenAddInfrastructure()
|
||||
}
|
||||
class="inline-flex items-center justify-center gap-2 rounded-md bg-blue-600 px-4 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700"
|
||||
>
|
||||
{completionViewModel().primaryAction === 'dashboard'
|
||||
? 'Go to Dashboard'
|
||||
: 'Open Infrastructure Install'}
|
||||
: 'Add infrastructure'}
|
||||
</button>
|
||||
<Show when={completionViewModel().showPlatformConnectionsAction}>
|
||||
<Show when={completionViewModel().showAddInfrastructureAction}>
|
||||
<button
|
||||
onClick={handleOpenPlatformConnections}
|
||||
onClick={handleOpenAddInfrastructure}
|
||||
class="inline-flex items-center justify-center gap-2 rounded-md border border-border px-4 py-3 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
|
||||
>
|
||||
Open Platform connections
|
||||
Add infrastructure
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={completionViewModel().showInstallAction}>
|
||||
<Show when={completionViewModel().showAgentInstallAction}>
|
||||
<button
|
||||
onClick={handleOpenInstallWorkspace}
|
||||
onClick={handleOpenAgentInstall}
|
||||
class="inline-flex items-center justify-center gap-2 rounded-md border border-border px-4 py-3 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
|
||||
>
|
||||
Open Infrastructure Install
|
||||
Install Pulse Agent
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,29 +3,27 @@ import setupCompletionPanelSource from '../SetupCompletionPanel.tsx?raw';
|
|||
import setupCompletionModelSource from '../setupCompletionModel.ts?raw';
|
||||
|
||||
describe('SetupCompletionPanel guardrails', () => {
|
||||
it('keeps setup completion aligned with the canonical infrastructure install workspace', () => {
|
||||
it('keeps setup completion aligned with the canonical add-infrastructure picker', () => {
|
||||
expect(setupCompletionPanelSource).toContain('buildInfrastructureOnboardingPath');
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
"const INFRASTRUCTURE_INSTALL_PATH = buildInfrastructureOnboardingPath('agent');",
|
||||
"const ADD_INFRASTRUCTURE_PATH = buildInfrastructureOnboardingPath('pick');",
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
"const PLATFORM_CONNECTIONS_PATH = buildInfrastructureOnboardingPath('pick');",
|
||||
"const AGENT_INSTALL_PATH = buildInfrastructureOnboardingPath('agent');",
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain('Open Infrastructure Install');
|
||||
expect(setupCompletionPanelSource).toContain('Open Platform connections');
|
||||
expect(setupCompletionPanelSource).toContain('Add infrastructure');
|
||||
expect(setupCompletionPanelSource).toContain('Install Pulse Agent');
|
||||
expect(setupCompletionPanelSource).toContain('Credentials you must save now');
|
||||
expect(setupCompletionPanelSource).toContain('Shown during setup');
|
||||
expect(setupCompletionPanelSource).toContain('props.onComplete(INFRASTRUCTURE_INSTALL_PATH);');
|
||||
expect(setupCompletionPanelSource).toContain('props.onComplete(PLATFORM_CONNECTIONS_PATH);');
|
||||
expect(setupCompletionPanelSource).toContain('props.onComplete(ADD_INFRASTRUCTURE_PATH);');
|
||||
expect(setupCompletionPanelSource).toContain('props.onComplete(AGENT_INSTALL_PATH);');
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'Use Add connection to connect your first host or API-backed platform',
|
||||
'Use Add infrastructure to choose a platform API, Pulse Agent, or both',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'continue with the first-host install token Pulse prepares from setup',
|
||||
);
|
||||
expect(setupCompletionPanelSource).not.toContain(
|
||||
'Use Add infrastructure to connect your first host or API-backed platform',
|
||||
'Open the picker to choose a platform API for inventory, Pulse Agent for host telemetry',
|
||||
);
|
||||
expect(setupCompletionPanelSource).not.toContain('Use Add connection to connect');
|
||||
expect(setupCompletionPanelSource).not.toContain("from '@/stores/licenseCommercial';");
|
||||
expect(setupCompletionPanelSource).not.toContain('runStartProTrialAction');
|
||||
expect(setupCompletionPanelSource).not.toContain('loadCommercialPosture');
|
||||
|
|
@ -39,19 +37,19 @@ describe('SetupCompletionPanel guardrails', () => {
|
|||
it('describes setup completion through the unified resource model instead of legacy install-command copy', () => {
|
||||
expect(setupCompletionPanelSource).toContain("title: 'What happens next'");
|
||||
expect(setupCompletionPanelSource).toContain('Pulse is now secured.');
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Open Infrastructure Install'");
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'Pulse prepares the first-host install token from setup',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Run it on the first host you want to monitor'");
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Open Add infrastructure'");
|
||||
expect(setupCompletionPanelSource).toContain('Review the supported source types in one place');
|
||||
expect(setupCompletionPanelSource).toContain("title: 'Save the source and confirm coverage'");
|
||||
expect(setupCompletionPanelSource).toContain('What to expect');
|
||||
expect(setupCompletionPanelSource).toContain('First system first');
|
||||
expect(setupCompletionPanelSource).toContain('Start with one host, then add more systems later from the same install workspace.');
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'API-backed platforms like Proxmox, TrueNAS, and VMware use Platform connections instead of a dedicated install profile in Infrastructure Install.',
|
||||
'Start with one source, then add more systems later from Settings',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'Platform APIs own inventory and health. Pulse Agent owns host telemetry',
|
||||
);
|
||||
expect(setupCompletionModelSource).toContain(
|
||||
'If the first system is API-backed, use Platform connections instead of starting with host install.',
|
||||
'Start with a platform API when a platform manages the estate.',
|
||||
);
|
||||
expect(setupCompletionPanelSource).not.toContain('Smart Auto-Detection');
|
||||
expect(setupCompletionPanelSource).not.toContain('Agent Metrics');
|
||||
|
|
@ -61,14 +59,16 @@ describe('SetupCompletionPanel guardrails', () => {
|
|||
it('keeps connected infrastructure classification on the canonical setup model', () => {
|
||||
expect(setupCompletionPanelSource).toContain('buildSetupCompletionConnectedSystems');
|
||||
expect(setupCompletionPanelSource).toContain('buildSetupCompletionViewModel');
|
||||
expect(setupCompletionPanelSource).toContain("props.connectedResourcesOverride !== undefined");
|
||||
expect(setupCompletionPanelSource).toContain("setConnectedSystems(buildSetupCompletionConnectedSystems(props.connectedResourcesOverride));");
|
||||
expect(setupCompletionPanelSource).toContain('props.connectedResourcesOverride !== undefined');
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'setConnectedSystems(buildSetupCompletionConnectedSystems(props.connectedResourcesOverride));',
|
||||
);
|
||||
expect(setupCompletionModelSource).toContain('isAgentFacetInfrastructureResource');
|
||||
expect(setupCompletionModelSource).toContain('getPreferredInfrastructureDisplayName');
|
||||
expect(setupCompletionModelSource).toContain('getPreferredResourceHostname');
|
||||
expect(setupCompletionModelSource).toContain('getSourcePlatformManifestEntry');
|
||||
expect(setupCompletionModelSource).toContain("sourcePlatformSupportsOnboardingPath");
|
||||
expect(setupCompletionModelSource).toContain("displayTokens[displayTokens.length - 1]");
|
||||
expect(setupCompletionModelSource).toContain('sourcePlatformSupportsOnboardingPath');
|
||||
expect(setupCompletionModelSource).toContain('displayTokens[displayTokens.length - 1]');
|
||||
expect(setupCompletionModelSource).not.toContain('PLATFORM_CONNECTION_PLATFORM_TYPES');
|
||||
expect(setupCompletionModelSource).not.toContain("resource.type === 'truenas'");
|
||||
expect(setupCompletionModelSource).not.toContain('getPreferredResourceDisplayName(resource)');
|
||||
|
|
@ -89,30 +89,36 @@ describe('SetupCompletionPanel guardrails', () => {
|
|||
});
|
||||
|
||||
it('keeps setup completion on one primary next-step surface instead of repeated CTA sections', () => {
|
||||
expect(setupCompletionPanelSource).toContain("const [showCredentials, setShowCredentials] = createSignal(true);");
|
||||
expect(setupCompletionPanelSource).toContain('Save the admin login and API token before leaving this screen');
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'const [showCredentials, setShowCredentials] = createSignal(true);',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'Save the admin login and API token before leaving this screen',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain('Recommended next step');
|
||||
expect(setupCompletionPanelSource).toContain('Go to Dashboard');
|
||||
expect(setupCompletionModelSource).toContain("heroTitle: 'First monitored system connected'");
|
||||
expect(setupCompletionModelSource).toContain("heroTitle: 'Connect your first monitored system'");
|
||||
expect(setupCompletionModelSource).toContain(
|
||||
"heroTitle: 'Choose your first infrastructure source'",
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
"completionViewModel().primaryAction === 'dashboard'",
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'completionViewModel().showPlatformConnectionsAction',
|
||||
'completionViewModel().showAddInfrastructureAction',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain('completionViewModel().showInstallAction');
|
||||
expect(setupCompletionPanelSource).toContain('handleOpenPlatformConnections');
|
||||
expect(setupCompletionPanelSource).toContain('completionViewModel().showAgentInstallAction');
|
||||
expect(setupCompletionPanelSource).toContain('handleOpenAddInfrastructure');
|
||||
expect(setupCompletionPanelSource).not.toContain('hasConnectedAgents');
|
||||
expect(setupCompletionPanelSource).not.toContain('connectedAgents().length');
|
||||
expect(setupCompletionPanelSource).not.toContain(
|
||||
'You can return here later from Connections & Inventory if you skip install for now.',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'The canonical install flow now lives in Infrastructure.',
|
||||
'Add infrastructure now owns the first source decision.',
|
||||
);
|
||||
expect(setupCompletionPanelSource).toContain(
|
||||
'then return to Infrastructure when you want to continue with the next system path.',
|
||||
'then return to Add infrastructure when you want to connect the next API or Agent source.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -77,13 +77,14 @@ describe('SetupCompletionPanel', () => {
|
|||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('frames setup completion around the canonical infrastructure install workspace', async () => {
|
||||
it('frames setup completion around the canonical add-infrastructure picker', async () => {
|
||||
render(() => <SetupCompletionPanel state={baseState} onComplete={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText('Connect your first monitored system')).toBeInTheDocument();
|
||||
expect(screen.getByText('Choose your first infrastructure source')).toBeInTheDocument();
|
||||
expect(screen.getByText('What happens next')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('Open Infrastructure Install').length).toBeGreaterThan(0);
|
||||
expect(screen.getByRole('button', { name: 'Open Platform connections' })).toBeInTheDocument();
|
||||
expect(screen.getAllByText('Open Add infrastructure').length).toBeGreaterThan(0);
|
||||
expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Install Pulse Agent' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Credentials you must save now')).toBeInTheDocument();
|
||||
expect(screen.getByText('Shown during setup')).toBeInTheDocument();
|
||||
expect(screen.getByText('admin')).toBeInTheDocument();
|
||||
|
|
@ -92,17 +93,17 @@ describe('SetupCompletionPanel', () => {
|
|||
expect(screen.getByText('First system first')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Infrastructure Install owns the token, connection URL, TLS/CA settings, and platform-specific commands.',
|
||||
'Platform APIs own inventory and health. Pulse Agent owns host telemetry, local services, Docker, and Kubernetes discovery.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Use the canonical install workspace where Pulse prepares the first-host install token from setup and keeps Platform connections beside it when the first target is API-backed.',
|
||||
'Review the supported source types in one place before choosing a platform API, Pulse Agent, or both.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'API-backed platforms like Proxmox, TrueNAS, and VMware use Platform connections instead of a dedicated install profile in Infrastructure Install.',
|
||||
'VMware, TrueNAS, Proxmox, PBS, and PMG use API-backed source flows; standalone hosts use Pulse Agent.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
|
|
@ -111,27 +112,27 @@ describe('SetupCompletionPanel', () => {
|
|||
expect(screen.queryByText('Windows (PowerShell as Administrator)')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hands install into the canonical infrastructure workspace', async () => {
|
||||
it('hands the primary action into the canonical source picker', async () => {
|
||||
const onComplete = vi.fn();
|
||||
|
||||
render(() => <SetupCompletionPanel state={baseState} onComplete={onComplete} />);
|
||||
|
||||
fireEvent.click(screen.getAllByRole('button', { name: 'Open Infrastructure Install' })[0]);
|
||||
|
||||
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure?add=agent');
|
||||
});
|
||||
|
||||
it('hands API-backed starts into the canonical platform connections workspace', async () => {
|
||||
const onComplete = vi.fn();
|
||||
|
||||
render(() => <SetupCompletionPanel state={baseState} onComplete={onComplete} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open Platform connections' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add infrastructure' }));
|
||||
|
||||
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure?add=pick');
|
||||
});
|
||||
|
||||
it('downloads credentials that point operators to the install workspace instead of inline commands', async () => {
|
||||
it('keeps a direct Pulse Agent handoff for operators who already know the first source', async () => {
|
||||
const onComplete = vi.fn();
|
||||
|
||||
render(() => <SetupCompletionPanel state={baseState} onComplete={onComplete} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Install Pulse Agent' }));
|
||||
|
||||
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure?add=agent');
|
||||
});
|
||||
|
||||
it('downloads credentials that point operators to the unified source picker instead of inline commands', async () => {
|
||||
const anchorClickMock = vi.fn();
|
||||
const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName) => {
|
||||
const element = document.createElementNS('http://www.w3.org/1999/xhtml', tagName);
|
||||
|
|
@ -156,8 +157,8 @@ describe('SetupCompletionPanel', () => {
|
|||
expect(content).toContain('Web Login:');
|
||||
expect(content).toContain('Admin API Token:');
|
||||
expect(content).toContain('Infrastructure:');
|
||||
expect(content).toContain('https://pulse.example.com/settings/infrastructure?add=agent');
|
||||
expect(content).toContain('Use Add connection to connect');
|
||||
expect(content).toContain('https://pulse.example.com/settings/infrastructure?add=pick');
|
||||
expect(content).toContain('Use Add infrastructure to choose');
|
||||
expect(content).not.toContain('Example Install Command');
|
||||
expect(content).not.toContain('Example Windows Install Command');
|
||||
|
||||
|
|
@ -201,7 +202,7 @@ describe('SetupCompletionPanel', () => {
|
|||
expect(screen.getByText('First monitored system connected')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Infrastructure Install when you want to add more host-installed systems.',
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Add infrastructure when you want another Pulse Agent or platform API source.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
|
|
@ -209,28 +210,30 @@ describe('SetupCompletionPanel', () => {
|
|||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Infrastructure Install stays available any time you want to add more host-installed systems.',
|
||||
'Add infrastructure stays available for more Pulse Agent systems or platform API inventory when a platform manages the estate.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getAllByRole('button', { name: 'Go to Dashboard' }).length).toBeGreaterThan(0);
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: 'Open Infrastructure Install' }).length,
|
||||
).toBeGreaterThan(0);
|
||||
expect(screen.queryByRole('button', { name: 'Open Platform connections' })).not.toBeInTheDocument();
|
||||
expect(screen.getAllByRole('button', { name: 'Add infrastructure' }).length).toBeGreaterThan(0);
|
||||
expect(screen.queryByRole('button', { name: 'Install Pulse Agent' })).not.toBeInTheDocument();
|
||||
expect(trackAgentFirstConnectedMock).toHaveBeenCalledWith(
|
||||
'setup_wizard_complete',
|
||||
'first_agent',
|
||||
);
|
||||
|
||||
const nextStepHeading = screen.getByRole('heading', { name: 'Open your first dashboard view' });
|
||||
const nextStepCard = nextStepHeading.closest('div.bg-surface.rounded-md.border.border-border.p-6.text-left.mb-6');
|
||||
const nextStepCard = nextStepHeading.closest(
|
||||
'div.bg-surface.rounded-md.border.border-border.p-6.text-left.mb-6',
|
||||
);
|
||||
expect(nextStepCard).not.toBeNull();
|
||||
|
||||
fireEvent.click(within(nextStepCard as HTMLElement).getByRole('button', { name: 'Go to Dashboard' }));
|
||||
fireEvent.click(
|
||||
within(nextStepCard as HTMLElement).getByRole('button', { name: 'Go to Dashboard' }),
|
||||
);
|
||||
expect(onComplete).toHaveBeenCalledWith('/');
|
||||
});
|
||||
|
||||
it('keeps platform connections available for API-backed starts after the first system connects', async () => {
|
||||
it('keeps add infrastructure available for API-backed starts after the first system connects', async () => {
|
||||
const onComplete = vi.fn();
|
||||
apiFetchJSONMock.mockResolvedValue({
|
||||
resources: [
|
||||
|
|
@ -262,26 +265,28 @@ describe('SetupCompletionPanel', () => {
|
|||
expect(screen.getByText('First monitored system connected')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Platform connections when you want to add more API-backed systems.',
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Add infrastructure when you want another platform API or Pulse Agent source.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Platform connections stays available any time you want to add more API-backed systems, and Infrastructure Install is ready when the next system should run the unified agent.',
|
||||
'Add infrastructure stays available for more API-backed systems or Pulse Agent telemetry when a system needs node-local coverage.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Go to Dashboard' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open Platform connections' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open Infrastructure Install' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Install Pulse Agent' })).not.toBeInTheDocument();
|
||||
expect(trackAgentFirstConnectedMock).not.toHaveBeenCalled();
|
||||
|
||||
const nextStepHeading = screen.getByRole('heading', { name: 'Open your first dashboard view' });
|
||||
const nextStepCard = nextStepHeading.closest('div.bg-surface.rounded-md.border.border-border.p-6.text-left.mb-6');
|
||||
const nextStepCard = nextStepHeading.closest(
|
||||
'div.bg-surface.rounded-md.border.border-border.p-6.text-left.mb-6',
|
||||
);
|
||||
expect(nextStepCard).not.toBeNull();
|
||||
|
||||
fireEvent.click(
|
||||
within(nextStepCard as HTMLElement).getByRole('button', {
|
||||
name: 'Open Platform connections',
|
||||
name: 'Add infrastructure',
|
||||
}),
|
||||
);
|
||||
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure?add=pick');
|
||||
|
|
@ -318,16 +323,16 @@ describe('SetupCompletionPanel', () => {
|
|||
expect(screen.getByText('VMware vSphere')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Platform connections when you want to add more API-backed systems.',
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Add infrastructure when you want another platform API or Pulse Agent source.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open Platform connections' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open Platform connections' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add infrastructure' }));
|
||||
expect(onComplete).toHaveBeenCalledWith('/settings/infrastructure?add=pick');
|
||||
});
|
||||
|
||||
it('keeps both continuation paths visible when install-managed and API-backed systems are already present', async () => {
|
||||
it('keeps add infrastructure visible when agent and API-backed systems are already present', async () => {
|
||||
apiFetchJSONMock.mockResolvedValue({
|
||||
resources: [
|
||||
{
|
||||
|
|
@ -367,16 +372,16 @@ describe('SetupCompletionPanel', () => {
|
|||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Platform connections or Infrastructure Install when you want to add more systems.',
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview, then return to Add infrastructure when you want another platform API or Agent source.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Platform connections and Infrastructure Install both stay available any time you want to expand from this first system.',
|
||||
'Add infrastructure stays available any time you want to expand from this first system with another API source, Agent source, or both.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open Platform connections' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open Infrastructure Install' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Install Pulse Agent' })).not.toBeInTheDocument();
|
||||
expect(trackAgentFirstConnectedMock).toHaveBeenCalledWith(
|
||||
'setup_wizard_complete',
|
||||
'first_agent',
|
||||
|
|
@ -438,9 +443,7 @@ describe('SetupCompletionPanel', () => {
|
|||
});
|
||||
|
||||
expect(screen.queryByText('Monitor from Anywhere')).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /start free trial/i }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /start free trial/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /set up relay/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,9 +56,9 @@ describe('SetupCompletionPreview', () => {
|
|||
|
||||
expect(apiFetchJSONMock).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(screen.getAllByRole('button', { name: 'Open Infrastructure Install' })[0]);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add infrastructure' }));
|
||||
|
||||
expect(navigateMock).toHaveBeenCalledWith('/settings/infrastructure?add=agent');
|
||||
expect(navigateMock).toHaveBeenCalledWith('/settings/infrastructure?add=pick');
|
||||
});
|
||||
|
||||
it('renders the VMware-connected preview scenario without polling runtime state', () => {
|
||||
|
|
@ -69,6 +69,6 @@ describe('SetupCompletionPreview', () => {
|
|||
expect(apiFetchJSONMock).not.toHaveBeenCalled();
|
||||
expect(screen.getByText('First monitored system connected')).toBeInTheDocument();
|
||||
expect(screen.getByText('VMware vSphere')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open Platform connections' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -65,7 +65,12 @@ describe('WelcomeStep', () => {
|
|||
|
||||
expect(screen.getByText('Unlock this Pulse server')).toBeInTheDocument();
|
||||
expect(screen.getByText('Create the admin account')).toBeInTheDocument();
|
||||
expect(screen.getByText('Install the first host')).toBeInTheDocument();
|
||||
expect(screen.getByText('Choose the first source')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Connect a platform API, install Pulse Agent, or use both for full coverage.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('What this token does')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import {
|
|||
getPreferredResourceHostname,
|
||||
} from '@/utils/resourceIdentity';
|
||||
|
||||
export type SetupCompletionConnectionPath = 'install' | 'platforms';
|
||||
export type SetupCompletionPrimaryAction = 'dashboard' | 'install';
|
||||
export type SetupCompletionConnectionPath = 'agent' | 'api';
|
||||
export type SetupCompletionPrimaryAction = 'dashboard' | 'sources';
|
||||
|
||||
export interface ConnectedSetupSystem {
|
||||
id: string;
|
||||
|
|
@ -29,16 +29,16 @@ export interface SetupCompletionViewModel {
|
|||
connectedSummaryLabel: string;
|
||||
credentialsContinuationText: string;
|
||||
hasConnectedSystems: boolean;
|
||||
hasInstallConnectedSystems: boolean;
|
||||
hasPlatformConnectedSystems: boolean;
|
||||
hasAgentConnectedSystems: boolean;
|
||||
hasApiConnectedSystems: boolean;
|
||||
heroDescription: string;
|
||||
heroTitle: string;
|
||||
nextStepDetail: string;
|
||||
nextStepSummary: string;
|
||||
nextStepTitle: string;
|
||||
primaryAction: SetupCompletionPrimaryAction;
|
||||
showInstallAction: boolean;
|
||||
showPlatformConnectionsAction: boolean;
|
||||
showAddInfrastructureAction: boolean;
|
||||
showAgentInstallAction: boolean;
|
||||
}
|
||||
|
||||
const asRecord = (value: unknown): Record<string, unknown> | undefined =>
|
||||
|
|
@ -65,14 +65,14 @@ const getSetupCompletionPlatformLabel = (resource: Resource): string | null => {
|
|||
return displayTokens[displayTokens.length - 1] || manifestPlatform.uiLabel;
|
||||
};
|
||||
|
||||
const isPlatformConnectedSetupResource = (resource: Resource): boolean =>
|
||||
const isApiConnectedSetupResource = (resource: Resource): boolean =>
|
||||
sourcePlatformSupportsOnboardingPath(
|
||||
getSetupCompletionPlatformKey(resource),
|
||||
'platform-connections',
|
||||
);
|
||||
|
||||
const isInstallConnectedSetupResource = (resource: Resource): boolean =>
|
||||
isAgentFacetInfrastructureResource(resource) && !isPlatformConnectedSetupResource(resource);
|
||||
const isAgentConnectedSetupResource = (resource: Resource): boolean =>
|
||||
isAgentFacetInfrastructureResource(resource) && !isApiConnectedSetupResource(resource);
|
||||
|
||||
const getConnectedSetupSystemTypeLabel = (resource: Resource): string => {
|
||||
return getSetupCompletionPlatformLabel(resource) || 'Agent';
|
||||
|
|
@ -88,20 +88,17 @@ const getConnectedSetupSystemHost = (resource: Resource): string => {
|
|||
const vmware = asRecord(platformData?.vmware);
|
||||
|
||||
return (
|
||||
asString(proxmox?.instance) ||
|
||||
asString(truenas?.hostname) ||
|
||||
asString(vmware?.hostname) ||
|
||||
''
|
||||
asString(proxmox?.instance) || asString(truenas?.hostname) || asString(vmware?.hostname) || ''
|
||||
);
|
||||
};
|
||||
|
||||
const toConnectedSetupSystem = (resource: Resource): ConnectedSetupSystem | null => {
|
||||
if (!isSetupCompletionInfrastructureResource(resource)) return null;
|
||||
|
||||
const connectionPath = isPlatformConnectedSetupResource(resource)
|
||||
? 'platforms'
|
||||
: isInstallConnectedSetupResource(resource)
|
||||
? 'install'
|
||||
const connectionPath = isApiConnectedSetupResource(resource)
|
||||
? 'api'
|
||||
: isAgentConnectedSetupResource(resource)
|
||||
? 'agent'
|
||||
: null;
|
||||
if (!connectionPath) return null;
|
||||
|
||||
|
|
@ -130,8 +127,8 @@ export function buildSetupCompletionConnectedSystems(
|
|||
continue;
|
||||
}
|
||||
|
||||
if (existing.connectionPath === 'install' && nextSystem.connectionPath === 'platforms') {
|
||||
existing.connectionPath = 'platforms';
|
||||
if (existing.connectionPath === 'agent' && nextSystem.connectionPath === 'api') {
|
||||
existing.connectionPath = 'api';
|
||||
existing.typeLabel = nextSystem.typeLabel;
|
||||
}
|
||||
if (!existing.host && nextSystem.host) {
|
||||
|
|
@ -148,57 +145,49 @@ export function buildSetupCompletionConnectedSystems(
|
|||
}
|
||||
|
||||
const buildConnectedHeroDescription = (
|
||||
hasInstallConnectedSystems: boolean,
|
||||
hasPlatformConnectedSystems: boolean,
|
||||
hasAgentConnectedSystems: boolean,
|
||||
hasApiConnectedSystems: boolean,
|
||||
): string => {
|
||||
const prefix =
|
||||
'Your admin account is ready and Pulse is already receiving telemetry. Open the dashboard to verify the first overview';
|
||||
|
||||
if (hasInstallConnectedSystems && hasPlatformConnectedSystems) {
|
||||
return `${prefix}, then return to Platform connections or Infrastructure Install when you want to add more systems.`;
|
||||
if (hasAgentConnectedSystems && hasApiConnectedSystems) {
|
||||
return `${prefix}, then return to Add infrastructure when you want another platform API or Agent source.`;
|
||||
}
|
||||
if (hasPlatformConnectedSystems) {
|
||||
return `${prefix}, then return to Platform connections when you want to add more API-backed systems.`;
|
||||
if (hasApiConnectedSystems) {
|
||||
return `${prefix}, then return to Add infrastructure when you want another platform API or Pulse Agent source.`;
|
||||
}
|
||||
return `${prefix}, then return to Infrastructure Install when you want to add more host-installed systems.`;
|
||||
return `${prefix}, then return to Add infrastructure when you want another Pulse Agent or platform API source.`;
|
||||
};
|
||||
|
||||
const buildCredentialsContinuationText = (
|
||||
hasInstallConnectedSystems: boolean,
|
||||
hasPlatformConnectedSystems: boolean,
|
||||
_hasAgentConnectedSystems: boolean,
|
||||
_hasApiConnectedSystems: boolean,
|
||||
): string => {
|
||||
if (hasInstallConnectedSystems && hasPlatformConnectedSystems) {
|
||||
return 'the dashboard, Platform connections, or Infrastructure Install.';
|
||||
}
|
||||
if (hasPlatformConnectedSystems) {
|
||||
return 'the dashboard, Platform connections, or Infrastructure Install.';
|
||||
}
|
||||
return 'the dashboard or Infrastructure Install.';
|
||||
return 'the dashboard or Add infrastructure.';
|
||||
};
|
||||
|
||||
const buildConnectedNextStepDetail = (
|
||||
hasInstallConnectedSystems: boolean,
|
||||
hasPlatformConnectedSystems: boolean,
|
||||
hasAgentConnectedSystems: boolean,
|
||||
hasApiConnectedSystems: boolean,
|
||||
): string => {
|
||||
if (hasInstallConnectedSystems && hasPlatformConnectedSystems) {
|
||||
return 'Platform connections and Infrastructure Install both stay available any time you want to expand from this first system.';
|
||||
if (hasAgentConnectedSystems && hasApiConnectedSystems) {
|
||||
return 'Add infrastructure stays available any time you want to expand from this first system with another API source, Agent source, or both.';
|
||||
}
|
||||
if (hasPlatformConnectedSystems) {
|
||||
return 'Platform connections stays available any time you want to add more API-backed systems, and Infrastructure Install is ready when the next system should run the unified agent.';
|
||||
if (hasApiConnectedSystems) {
|
||||
return 'Add infrastructure stays available for more API-backed systems or Pulse Agent telemetry when a system needs node-local coverage.';
|
||||
}
|
||||
return 'Infrastructure Install stays available any time you want to add more host-installed systems.';
|
||||
return 'Add infrastructure stays available for more Pulse Agent systems or platform API inventory when a platform manages the estate.';
|
||||
};
|
||||
|
||||
export function buildSetupCompletionViewModel(
|
||||
connectedSystems: readonly ConnectedSetupSystem[],
|
||||
): SetupCompletionViewModel {
|
||||
const hasConnectedSystems = connectedSystems.length > 0;
|
||||
const hasInstallConnectedSystems = connectedSystems.some(
|
||||
(system) => system.connectionPath === 'install',
|
||||
);
|
||||
const hasPlatformConnectedSystems = connectedSystems.some(
|
||||
(system) => system.connectionPath === 'platforms',
|
||||
const hasAgentConnectedSystems = connectedSystems.some(
|
||||
(system) => system.connectionPath === 'agent',
|
||||
);
|
||||
const hasApiConnectedSystems = connectedSystems.some((system) => system.connectionPath === 'api');
|
||||
|
||||
const connectedSystemNoun = connectedSystems.length === 1 ? 'system' : 'systems';
|
||||
const nextStepSummary =
|
||||
|
|
@ -209,45 +198,42 @@ export function buildSetupCompletionViewModel(
|
|||
if (!hasConnectedSystems) {
|
||||
return {
|
||||
connectedSummaryLabel: 'Connected (0 systems)',
|
||||
credentialsContinuationText: 'Infrastructure Install or Platform connections.',
|
||||
credentialsContinuationText: 'Add infrastructure.',
|
||||
hasConnectedSystems,
|
||||
hasInstallConnectedSystems,
|
||||
hasPlatformConnectedSystems,
|
||||
hasAgentConnectedSystems,
|
||||
hasApiConnectedSystems,
|
||||
heroDescription:
|
||||
'Your admin account is ready. Next, choose the first infrastructure path: open Infrastructure Install for a host that should run the unified agent, or open Platform connections for API-backed platforms such as Proxmox, TrueNAS, and VMware.',
|
||||
heroTitle: 'Connect your first monitored system',
|
||||
'Your admin account is ready. Next, choose how the first system should enter the unified infrastructure model: platform API inventory, Pulse Agent telemetry, or both.',
|
||||
heroTitle: 'Choose your first infrastructure source',
|
||||
nextStepDetail:
|
||||
'If the first system is API-backed, use Platform connections instead of starting with host install.',
|
||||
nextStepSummary: 'Open Infrastructure Install to bring your first monitored system into Pulse.',
|
||||
nextStepTitle: 'Choose your first infrastructure path',
|
||||
primaryAction: 'install',
|
||||
showInstallAction: false,
|
||||
showPlatformConnectionsAction: true,
|
||||
'Start with a platform API when a platform manages the estate. Install Pulse Agent when the system itself should report node-local telemetry.',
|
||||
nextStepSummary: 'Open Add infrastructure to choose a platform API, Pulse Agent, or both.',
|
||||
nextStepTitle: 'Choose the first source strategy',
|
||||
primaryAction: 'sources',
|
||||
showAddInfrastructureAction: false,
|
||||
showAgentInstallAction: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
connectedSummaryLabel: `Connected (${connectedSystems.length} ${connectedSystemNoun})`,
|
||||
credentialsContinuationText: buildCredentialsContinuationText(
|
||||
hasInstallConnectedSystems,
|
||||
hasPlatformConnectedSystems,
|
||||
hasAgentConnectedSystems,
|
||||
hasApiConnectedSystems,
|
||||
),
|
||||
hasConnectedSystems,
|
||||
hasInstallConnectedSystems,
|
||||
hasPlatformConnectedSystems,
|
||||
hasAgentConnectedSystems,
|
||||
hasApiConnectedSystems,
|
||||
heroDescription: buildConnectedHeroDescription(
|
||||
hasInstallConnectedSystems,
|
||||
hasPlatformConnectedSystems,
|
||||
hasAgentConnectedSystems,
|
||||
hasApiConnectedSystems,
|
||||
),
|
||||
heroTitle: 'First monitored system connected',
|
||||
nextStepDetail: buildConnectedNextStepDetail(
|
||||
hasInstallConnectedSystems,
|
||||
hasPlatformConnectedSystems,
|
||||
),
|
||||
nextStepDetail: buildConnectedNextStepDetail(hasAgentConnectedSystems, hasApiConnectedSystems),
|
||||
nextStepSummary,
|
||||
nextStepTitle: 'Open your first dashboard view',
|
||||
primaryAction: 'dashboard',
|
||||
showInstallAction: hasConnectedSystems,
|
||||
showPlatformConnectionsAction: hasPlatformConnectedSystems,
|
||||
showAddInfrastructureAction: hasConnectedSystems,
|
||||
showAgentInstallAction: false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ export const WelcomeStep: Component<WelcomeStepProps> = (props) => {
|
|||
</p>
|
||||
<p class="mt-4 text-sm text-muted max-w-xl mx-auto animate-fade-in delay-300">
|
||||
You are about to do three things: unlock setup on this Pulse server, create your admin
|
||||
account, and install the first system you want Pulse to monitor.
|
||||
account, and choose the first infrastructure source Pulse should monitor.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -203,9 +203,9 @@ export const WelcomeStep: Component<WelcomeStepProps> = (props) => {
|
|||
<div class="text-[11px] font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-300">
|
||||
Step 3
|
||||
</div>
|
||||
<div class="mt-1 text-sm font-semibold text-base-content">Install the first host</div>
|
||||
<div class="mt-1 text-sm font-semibold text-base-content">Choose the first source</div>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
Open Infrastructure Install and connect the first system you want Pulse to monitor.
|
||||
Connect a platform API, install Pulse Agent, or use both for full coverage.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -218,9 +218,9 @@ export const WelcomeStep: Component<WelcomeStepProps> = (props) => {
|
|||
Unlock Setup
|
||||
</h3>
|
||||
<p class="text-sm text-muted mb-6">
|
||||
Run the following command on the Pulse server to retrieve the one-time bootstrap
|
||||
token that unlocks this wizard. Do not paste the raw `.bootstrap_token` file
|
||||
contents directly.
|
||||
Run the following command on the Pulse server to retrieve the one-time bootstrap token
|
||||
that unlocks this wizard. Do not paste the raw `.bootstrap_token` file contents
|
||||
directly.
|
||||
</p>
|
||||
|
||||
<div class="mb-4 rounded-md border border-blue-200 bg-blue-50 px-4 py-3 dark:border-blue-800 dark:bg-blue-950/40">
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ describe('WhatsNewModal', () => {
|
|||
expect(whatsNewModalSource).not.toContain('createSignal');
|
||||
expect(whatsNewModalSource).not.toContain('WHATS_NEW_NAV_V2_SHOWN');
|
||||
expect(whatsNewModalSource).not.toContain('Migration guide');
|
||||
expect(whatsNewModalSource).not.toContain('https://github.com/rcourtman/Pulse/blob/main/docs/PRIVACY.md');
|
||||
expect(whatsNewModalSource).not.toContain(
|
||||
'https://github.com/rcourtman/Pulse/blob/main/docs/PRIVACY.md',
|
||||
);
|
||||
expect(whatsNewModalSource).not.toContain('bg-gradient');
|
||||
expect(whatsNewModalSource).not.toContain('backdrop-blur-sm');
|
||||
|
||||
|
|
@ -60,8 +62,12 @@ describe('WhatsNewModal', () => {
|
|||
expect(whatsNewModalModelSource).toContain('WHATS_NEW_KICKER_LABEL');
|
||||
expect(whatsNewModalModelSource).toContain('WHATS_NEW_PROGRESS_PREFIX');
|
||||
expect(whatsNewModalModelSource).toContain("WHATS_NEW_PRIMARY_ACTION_LABEL = 'Done'");
|
||||
expect(whatsNewModalModelSource).not.toContain('https://github.com/rcourtman/Pulse/blob/main/docs/README.md');
|
||||
expect(whatsNewModalModelSource).not.toContain('https://github.com/rcourtman/Pulse/blob/main/docs/PRIVACY.md');
|
||||
expect(whatsNewModalModelSource).not.toContain(
|
||||
'https://github.com/rcourtman/Pulse/blob/main/docs/README.md',
|
||||
);
|
||||
expect(whatsNewModalModelSource).not.toContain(
|
||||
'https://github.com/rcourtman/Pulse/blob/main/docs/PRIVACY.md',
|
||||
);
|
||||
expect(whatsNewModalModelSource).toContain('WHATS_NEW_DOCS_LABEL');
|
||||
expect(whatsNewModalModelSource).toContain("title: 'Dashboard'");
|
||||
});
|
||||
|
|
@ -73,7 +79,9 @@ describe('WhatsNewModal', () => {
|
|||
expect(dialog).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('Step 1 of 5')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('Nav guide')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText(/Start here for health, alerts, capacity/i)).toBeInTheDocument();
|
||||
expect(
|
||||
within(dialog).getByText(/Start here for the live estate overview/i),
|
||||
).toBeInTheDocument();
|
||||
expect(within(dialog).queryByText('Where Things Moved')).not.toBeInTheDocument();
|
||||
expect(within(dialog).getByRole('link', { name: 'Navigation guide' })).toBeInTheDocument();
|
||||
expect(within(dialog).getByRole('link', { name: 'Telemetry details' })).toBeInTheDocument();
|
||||
|
|
@ -129,26 +137,30 @@ describe('WhatsNewModal', () => {
|
|||
it('advances through the guided tour and finishes on the last step', async () => {
|
||||
render(() => <WhatsNewModal />);
|
||||
|
||||
expect(await screen.findByText(/Start here for health, alerts, capacity/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Start here for the live estate overview/i)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(await screen.findByText(/Use this for nodes, hosts, clusters/i)).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText(/Use this to add and manage infrastructure sources/i),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(await screen.findByText(/Use this for VMs, containers, and pods/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Use this for VMs, containers, pods/i)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(await screen.findByText(/Use this for datastores, pools, disks, and capacity/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Use this for pools, datastores, disks/i)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(await screen.findByText(/Use this for backups, snapshots, and replication/i)).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText(/Use this for backup coverage, snapshots, replication/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('lets the user jump to a tour stop directly from the stop map', async () => {
|
||||
render(() => <WhatsNewModal />);
|
||||
|
||||
expect(await screen.findByText(/Start here for health, alerts, capacity/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Start here for the live estate overview/i)).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Workloads/i }));
|
||||
|
||||
expect(await screen.findByText(/Use this for VMs, containers, and pods/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Use this for VMs, containers, pods/i)).toBeInTheDocument();
|
||||
expect(screen.getByText('Step 3 of 5')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
@ -166,6 +178,8 @@ describe('WhatsNewModal', () => {
|
|||
|
||||
const dialog = await screen.findByRole('dialog', { name: 'Pulse navigation guide' });
|
||||
expect(within(dialog).getByText('Step 5 of 5')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText(/Use this for backups, snapshots, and replication/i)).toBeInTheDocument();
|
||||
expect(
|
||||
within(dialog).getByText(/Use this for backup coverage, snapshots, replication/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,35 +14,37 @@ export const WHATS_NEW_PRIVACY_URL = PRIVACY_DOC_URL;
|
|||
export const WHATS_NEW_FEATURE_CARDS: WhatsNewFeatureCard[] = [
|
||||
{
|
||||
accent: 'border-indigo-200 bg-indigo-50 dark:border-indigo-800 dark:bg-indigo-900',
|
||||
description: 'Start here for health, alerts, capacity, and recent activity.',
|
||||
description:
|
||||
'Start here for the live estate overview: health, alerts, capacity, and recent activity.',
|
||||
icon: 'dashboard',
|
||||
target: 'dashboard',
|
||||
title: 'Dashboard',
|
||||
},
|
||||
{
|
||||
accent: 'border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900',
|
||||
description: 'Use this for nodes, hosts, clusters, and other platform roots.',
|
||||
description:
|
||||
'Use this to add and manage infrastructure sources: platform API inventory, Pulse Agent telemetry, and discovered candidates.',
|
||||
icon: 'infrastructure',
|
||||
target: 'infrastructure',
|
||||
title: 'Infrastructure',
|
||||
},
|
||||
{
|
||||
accent: 'border-rose-200 bg-rose-50 dark:border-rose-800 dark:bg-rose-900',
|
||||
description: 'Use this for VMs, containers, and pods.',
|
||||
description: 'Use this for VMs, containers, pods, and other running workloads.',
|
||||
icon: 'workloads',
|
||||
target: 'workloads',
|
||||
title: 'Workloads',
|
||||
},
|
||||
{
|
||||
accent: 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-900',
|
||||
description: 'Use this for datastores, pools, disks, and capacity.',
|
||||
description: 'Use this for pools, datastores, disks, datasets, and capacity.',
|
||||
icon: 'storage',
|
||||
target: 'storage',
|
||||
title: 'Storage',
|
||||
},
|
||||
{
|
||||
accent: 'border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-900',
|
||||
description: 'Use this for backups, snapshots, and replication.',
|
||||
description: 'Use this for backup coverage, snapshots, replication, and restore readiness.',
|
||||
icon: 'recovery',
|
||||
target: 'recovery',
|
||||
title: 'Recovery',
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ export function InfrastructurePageSurface() {
|
|||
actions={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(buildInfrastructureOnboardingPath('agent'))}
|
||||
onClick={() => navigate(buildInfrastructureOnboardingPath('pick'))}
|
||||
class="inline-flex items-center gap-2 rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content shadow-sm hover:bg-slate-50"
|
||||
>
|
||||
<SettingsIcon class="h-3.5 w-3.5" />
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ export default function Dashboard() {
|
|||
<p class="mt-2 text-sm text-muted">{dashboardNoResourcesState().description}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(buildInfrastructureOnboardingPath('agent'))}
|
||||
onClick={() => navigate(buildInfrastructureOnboardingPath('pick'))}
|
||||
class="mt-4 inline-flex items-center rounded-md border border-transparent bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700"
|
||||
>
|
||||
{dashboardNoResourcesState().actionLabel}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default function RuntimeHome() {
|
|||
if (summaryFailed()) {
|
||||
return DASHBOARD_PATH;
|
||||
}
|
||||
return summaryHasResources() ? DASHBOARD_PATH : buildInfrastructureOnboardingPath('agent');
|
||||
return summaryHasResources() ? DASHBOARD_PATH : buildInfrastructureOnboardingPath('pick');
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
|
|
|
|||
|
|
@ -204,13 +204,13 @@ describe('Dashboard page module contract', () => {
|
|||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'The dashboard appears after Pulse receives its first monitored system. Add a Pulse Agent or platform API source from Infrastructure setup, then this page becomes the live estate overview.',
|
||||
'The dashboard appears after Pulse receives its first monitored system. Add an infrastructure source with API inventory, Agent telemetry, or both, then this page becomes the live estate overview.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add infrastructure source' }));
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=agent');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=pick');
|
||||
});
|
||||
|
||||
it('renders the governed storage and recovery dashboard panels', () => {
|
||||
|
|
|
|||
|
|
@ -40,27 +40,27 @@ describe('Infrastructure empty state', () => {
|
|||
navigateSpy.mockReset();
|
||||
});
|
||||
|
||||
it('shows empty state with direct install guidance when no resources exist', async () => {
|
||||
it('shows empty state with source-strategy guidance when no resources exist', async () => {
|
||||
const { getByText } = render(() => <Infrastructure />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('No infrastructure resources yet')).toBeInTheDocument();
|
||||
expect(getByText('No infrastructure sources yet')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const button = getByText('Open Infrastructure Install');
|
||||
const button = getByText('Add infrastructure source');
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button.closest('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to the install workspace when the empty-state button is clicked', async () => {
|
||||
it('navigates to the source picker when the empty-state button is clicked', async () => {
|
||||
const { getByText } = render(() => <Infrastructure />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Open Infrastructure Install')).toBeInTheDocument();
|
||||
expect(getByText('Add infrastructure source')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(getByText('Open Infrastructure Install'));
|
||||
fireEvent.click(getByText('Add infrastructure source'));
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=agent');
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=pick');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ describe('RuntimeHome', () => {
|
|||
expect(getDashboardSummaryMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('routes hosted empty workspaces into infrastructure install', async () => {
|
||||
it('routes hosted empty workspaces into the infrastructure source picker', async () => {
|
||||
getDashboardSummaryMock.mockResolvedValue({
|
||||
health: { totalResources: 0 },
|
||||
});
|
||||
|
|
@ -63,7 +63,7 @@ describe('RuntimeHome', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(getDashboardSummaryMock).toHaveBeenCalledTimes(1);
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=agent', {
|
||||
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=pick', {
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import {
|
|||
describe('dashboardEmptyStatePresentation', () => {
|
||||
it('returns the infrastructure onboarding empty state', () => {
|
||||
expect(getDashboardInfrastructureEmptyState()).toEqual({
|
||||
title: 'No infrastructure hosts connected',
|
||||
title: 'No infrastructure sources connected',
|
||||
description:
|
||||
'To start using Pulse, first add your infrastructure in Settings → Infrastructure → Install on a host. If you want an API-backed platform such as Proxmox or TrueNAS instead, use Settings → Infrastructure → Platform connections.',
|
||||
actionLabel: 'Open infrastructure setup',
|
||||
'Start in Settings → Infrastructure by choosing a source strategy. Connect a platform API for inventory and health, install Pulse Agent for host telemetry, or use both when you want full coverage.',
|
||||
actionLabel: 'Add infrastructure source',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ describe('dashboardEmptyStatePresentation', () => {
|
|||
expect(getDashboardNoResourcesState()).toEqual({
|
||||
title: 'Connect your first infrastructure source',
|
||||
description:
|
||||
'The dashboard appears after Pulse receives its first monitored system. Add a Pulse Agent or platform API source from Infrastructure setup, then this page becomes the live estate overview.',
|
||||
'The dashboard appears after Pulse receives its first monitored system. Add an infrastructure source with API inventory, Agent telemetry, or both, then this page becomes the live estate overview.',
|
||||
actionLabel: 'Add infrastructure source',
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import {
|
|||
describe('infrastructureEmptyStatePresentation', () => {
|
||||
it('returns the infrastructure onboarding empty state', () => {
|
||||
expect(getInfrastructureEmptyState()).toEqual({
|
||||
title: 'No infrastructure resources yet',
|
||||
title: 'No infrastructure sources yet',
|
||||
description:
|
||||
'Start by opening Settings → Infrastructure → Install on a host and adding the first system you want Pulse to monitor. If you prefer an API-backed platform such as Proxmox or TrueNAS instead, use Platform connections.',
|
||||
actionLabel: 'Open Infrastructure Install',
|
||||
'Start in Settings → Infrastructure by choosing a source strategy. Connect a platform API for inventory and health, install Pulse Agent for host telemetry, or use both when you want full coverage.',
|
||||
actionLabel: 'Add infrastructure source',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -161,11 +161,12 @@ describe('infrastructureOnboardingPresentation', () => {
|
|||
});
|
||||
|
||||
expect(getInfrastructureEmptyStateSummary()).toBe(
|
||||
'Add infrastructure systems to start monitoring your environment.',
|
||||
'Choose an infrastructure source to start monitoring your environment.',
|
||||
);
|
||||
expect(getInfrastructureEmptyStateDetail()).toContain(
|
||||
'Supported source types include VMware vCenter',
|
||||
);
|
||||
expect(getInfrastructureEmptyStateDetail()).toContain('Available system types: VMware vCenter');
|
||||
expect(getInfrastructureEmptyStateDetail()).toContain('standalone hosts through Pulse Agent');
|
||||
expect(getInfrastructureEmptyStateDetail()).toContain('Docker and Kubernetes are discovered');
|
||||
expect(getInfrastructureEmptyStateDetail()).toContain('VMware vCenter is also available now.');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export function getDashboardInfrastructureEmptyState() {
|
||||
return {
|
||||
title: 'No infrastructure hosts connected',
|
||||
title: 'No infrastructure sources connected',
|
||||
description:
|
||||
'To start using Pulse, first add your infrastructure in Settings → Infrastructure → Install on a host. If you want an API-backed platform such as Proxmox or TrueNAS instead, use Settings → Infrastructure → Platform connections.',
|
||||
actionLabel: 'Open infrastructure setup',
|
||||
'Start in Settings → Infrastructure by choosing a source strategy. Connect a platform API for inventory and health, install Pulse Agent for host telemetry, or use both when you want full coverage.',
|
||||
actionLabel: 'Add infrastructure source',
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ export function getDashboardNoResourcesState() {
|
|||
return {
|
||||
title: 'Connect your first infrastructure source',
|
||||
description:
|
||||
'The dashboard appears after Pulse receives its first monitored system. Add a Pulse Agent or platform API source from Infrastructure setup, then this page becomes the live estate overview.',
|
||||
'The dashboard appears after Pulse receives its first monitored system. Add an infrastructure source with API inventory, Agent telemetry, or both, then this page becomes the live estate overview.',
|
||||
actionLabel: 'Add infrastructure source',
|
||||
} as const;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export function getInfrastructureEmptyState() {
|
||||
return {
|
||||
title: 'No infrastructure resources yet',
|
||||
title: 'No infrastructure sources yet',
|
||||
description:
|
||||
'Start by opening Settings → Infrastructure → Install on a host and adding the first system you want Pulse to monitor. If you prefer an API-backed platform such as Proxmox or TrueNAS instead, use Platform connections.',
|
||||
actionLabel: 'Open Infrastructure Install',
|
||||
'Start in Settings → Infrastructure by choosing a source strategy. Connect a platform API for inventory and health, install Pulse Agent for host telemetry, or use both when you want full coverage.',
|
||||
actionLabel: 'Add infrastructure source',
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ export const getInfrastructureSupportSummaryBadges = (): {
|
|||
});
|
||||
|
||||
export const getInfrastructureEmptyStateSummary = (): string =>
|
||||
'Add infrastructure systems to start monitoring your environment.';
|
||||
'Choose an infrastructure source to start monitoring your environment.';
|
||||
|
||||
export const getInfrastructureEmptyStateDetail = (): string =>
|
||||
'Available system types: VMware vCenter, TrueNAS SCALE, Proxmox VE, Proxmox Backup Server, Proxmox Mail Gateway, and standalone hosts through Pulse Agent. Docker and Kubernetes are discovered from supported agent hosts. VMware vCenter is also available now.';
|
||||
'Supported source types include VMware vCenter, TrueNAS SCALE, Proxmox VE, Proxmox Backup Server, Proxmox Mail Gateway, and standalone hosts through Pulse Agent. Docker and Kubernetes are discovered from supported agent hosts.';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue