mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
infra: align agent-led add landing with Proxmox auto-detect
This commit is contained in:
parent
d8d94c48e1
commit
4bd023fe1c
11 changed files with 169 additions and 117 deletions
|
|
@ -1006,6 +1006,17 @@ presenting `Platform connections` as the explicit API-backed alternative
|
|||
instead of leaving first-session install guidance implicit in generic
|
||||
settings-shell prose or retreating to one provider's name as the primary
|
||||
alternative.
|
||||
That same first-run infrastructure handoff now also owns the inline
|
||||
add-infrastructure landing in `ConnectionEditor.tsx`: when the target can run
|
||||
the Unified Agent, the landing must lead with the recommended host-install
|
||||
card above the platform-API probe and picker instead of teaching probe-first
|
||||
as the default story, and returning `Back to catalog` must reset probe input
|
||||
and result state rather than reopening the landing with stale no-match or
|
||||
detected-product state already rendered.
|
||||
Render-order proof for that landing belongs to DOM-backed settings tests, not
|
||||
raw source-string position checks, so lifecycle ownership continues to guard
|
||||
the operator-visible order after reasonable component extraction or copy
|
||||
refactors.
|
||||
When that infrastructure workspace needs to redirect operators to the Pulse Pro
|
||||
surface for billing, monitored-system limits, or license status, it must
|
||||
consume the settings-owned referral copy from
|
||||
|
|
|
|||
|
|
@ -1420,6 +1420,14 @@ API-backed lifecycle state. The retired
|
|||
`InfrastructureOperationsController.tsx` shell and
|
||||
`useInfrastructureReportingState.tsx` reporting path must not be reintroduced
|
||||
as parallel transport owners.
|
||||
That same shared infrastructure-settings boundary also owns install-profile
|
||||
semantics surfaced by
|
||||
`frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx`:
|
||||
the recommended auto profile may describe Proxmox auto-detect only as the
|
||||
canonical unpinned runtime mode that lets the agent register every detected
|
||||
local PVE or PBS service. Frontend copy on that shared model must not imply a
|
||||
hidden single-type selection or invent a profile flag that the installer and
|
||||
auto-register contract do not actually persist.
|
||||
That shared `frontend-modern/src/api/agentProfiles.ts` boundary must also stay
|
||||
under explicit proof routing on both sides instead of remaining a generic
|
||||
frontend-client match on the API-contract side: assignment, delete, unassign,
|
||||
|
|
|
|||
|
|
@ -759,6 +759,12 @@ and it must fail closed unless the response comes back on the canonical
|
|||
installer response handling must also use the returned canonical
|
||||
`nodeId`/`nodeName` identity instead of continuing to report the caller's local
|
||||
hostname after Pulse stores a disambiguated node record.
|
||||
When Proxmox is only auto-detected rather than explicitly profile-pinned, that
|
||||
same installer-owned boundary must enable Proxmox without persisting a forced
|
||||
`--proxmox-type` service argument. Auto mode must stay unpinned so the runtime
|
||||
can detect and register every supported local Proxmox service it finds; only
|
||||
an operator-selected install profile may lock the persisted runtime to one
|
||||
specific `pve` or `pbs` type.
|
||||
That same installer-owned bootstrap step against `/api/setup-script-url` must
|
||||
also validate the returned canonical `type`, normalized `host`, and live
|
||||
`expires` metadata before using the one-time setup token, so install-time
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export const ConnectionEditor: Component<ConnectionEditorProps> = (props) => {
|
|||
};
|
||||
|
||||
const reopenProbe = () => {
|
||||
state.reset();
|
||||
setSelectedCandidate(null);
|
||||
setSelectedType(null);
|
||||
};
|
||||
|
|
@ -116,15 +117,18 @@ export const ConnectionEditor: Component<ConnectionEditorProps> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted">
|
||||
<span class="font-medium text-base-content">On a Proxmox host, this is the
|
||||
fastest path.</span> The agent creates the PVE / PBS API token itself and
|
||||
auto-registers the node — no address or credentials to paste.
|
||||
<span class="font-medium text-base-content">
|
||||
On a Proxmox host, this is the fastest path.
|
||||
</span>{' '}
|
||||
The installer auto-detects Proxmox on the machine, creates the needed API
|
||||
token(s), and auto-registers any detected PVE / PBS services — no address or
|
||||
credentials to paste.
|
||||
</div>
|
||||
<div class="text-xs text-muted">
|
||||
It also reports CPU temperature, disk SMART, systemd services, and network
|
||||
metrics from the host, and auto-detects Docker and Kubernetes on that
|
||||
machine. Required for bare-metal Linux / Unraid / FreeBSD targets that have
|
||||
no platform API to connect.
|
||||
metrics from the host, and auto-detects Docker and Kubernetes on that machine.
|
||||
Required for bare-metal Linux / Unraid / FreeBSD targets that have no platform
|
||||
API to connect.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -143,8 +147,8 @@ export const ConnectionEditor: Component<ConnectionEditorProps> = (props) => {
|
|||
<span class="h-px flex-1 bg-border" aria-hidden="true" />
|
||||
</div>
|
||||
<p class="text-xs text-muted">
|
||||
For VMware, TrueNAS, PMG, or a remote Proxmox you can't install the agent on.
|
||||
Paste the address and Pulse will auto-detect the product, or pick it below.
|
||||
For VMware, TrueNAS, PMG, or a remote Proxmox you can't install the agent on. Paste
|
||||
the address and Pulse will auto-detect the product, or pick it below.
|
||||
</p>
|
||||
|
||||
<AddressProbeStep
|
||||
|
|
@ -191,7 +195,7 @@ export const ConnectionEditor: Component<ConnectionEditorProps> = (props) => {
|
|||
<span class="font-semibold text-base-content">
|
||||
{activeType() === 'agent'
|
||||
? 'Install Pulse Agent'
|
||||
: CONNECTION_TYPE_LABELS[activeType()!] ?? activeType()}
|
||||
: (CONNECTION_TYPE_LABELS[activeType()!] ?? activeType())}
|
||||
</span>
|
||||
<Show when={selectedCandidate()}>
|
||||
<span class="ml-2 text-xs text-muted">{selectedCandidate()!.host}</span>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ vi.mock('@/api/connections', async () => {
|
|||
|
||||
const mockedProbe = vi.mocked(ConnectionsAPI.probe);
|
||||
|
||||
function expectNodeBefore(a: Node, b: Node) {
|
||||
expect(a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
}
|
||||
|
||||
describe('ConnectionEditor', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
@ -32,16 +36,9 @@ describe('ConnectionEditor', () => {
|
|||
|
||||
const renderSlot = vi.fn(({ type }) => <div data-testid="slot">slot:{type}</div>);
|
||||
|
||||
render(() => (
|
||||
<ConnectionEditor
|
||||
renderCredentialSlot={renderSlot}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
));
|
||||
render(() => <ConnectionEditor renderCredentialSlot={renderSlot} onClose={() => {}} />);
|
||||
|
||||
const input = screen.getByPlaceholderText(
|
||||
/pve01\.lan/,
|
||||
) as HTMLInputElement;
|
||||
const input = screen.getByPlaceholderText(/pve01\.lan/) as HTMLInputElement;
|
||||
fireEvent.input(input, { target: { value: 'pve.lab' } });
|
||||
|
||||
const probeButton = screen.getByRole('button', { name: /probe address/i });
|
||||
|
|
@ -67,16 +64,9 @@ describe('ConnectionEditor', () => {
|
|||
|
||||
const renderSlot = vi.fn(({ type }) => <div data-testid="slot">slot:{type}</div>);
|
||||
|
||||
render(() => (
|
||||
<ConnectionEditor
|
||||
renderCredentialSlot={renderSlot}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
));
|
||||
render(() => <ConnectionEditor renderCredentialSlot={renderSlot} onClose={() => {}} />);
|
||||
|
||||
const input = screen.getByPlaceholderText(
|
||||
/pve01\.lan/,
|
||||
) as HTMLInputElement;
|
||||
const input = screen.getByPlaceholderText(/pve01\.lan/) as HTMLInputElement;
|
||||
fireEvent.input(input, { target: { value: '192.168.1.50' } });
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /probe address/i }));
|
||||
|
|
@ -94,23 +84,23 @@ describe('ConnectionEditor', () => {
|
|||
expect(lastCall.candidate).toBeNull();
|
||||
});
|
||||
|
||||
it('surfaces the probe input and the full product catalog as peers', () => {
|
||||
render(() => (
|
||||
<ConnectionEditor
|
||||
renderCredentialSlot={() => <div />}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
));
|
||||
it('renders an agent-led catalog with the API fallback beneath it', () => {
|
||||
render(() => <ConnectionEditor renderCredentialSlot={() => <div />} onClose={() => {}} />);
|
||||
|
||||
// Catalog landing — the probe input sits above a tile grid that includes
|
||||
// every supported product as a peer, Install Pulse Agent included. There
|
||||
// is no intermediate mode picker or "Platform API" framing.
|
||||
expect(screen.getByRole('button', { name: /probe address/i })).toBeInTheDocument();
|
||||
const agentButton = screen.getByRole('button', { name: /Install Pulse Agent/i });
|
||||
const apiHeading = screen.getByText('Or connect a platform API directly');
|
||||
const probeButton = screen.getByRole('button', { name: /probe address/i });
|
||||
|
||||
// Catalog landing — lead with the agent card, then collapse the probe and
|
||||
// direct platform options into one API fallback section below it.
|
||||
expect(agentButton).toBeInTheDocument();
|
||||
expect(apiHeading).toBeInTheDocument();
|
||||
expect(probeButton).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /^Proxmox VE/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /TrueNAS SCALE/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /VMware vCenter \/ ESXi/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Install Pulse Agent/i })).toBeInTheDocument();
|
||||
expect(screen.queryByText('Platform API')).toBeNull();
|
||||
expectNodeBefore(agentButton, apiHeading);
|
||||
expectNodeBefore(apiHeading, probeButton);
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /install the unified agent on a host/i }),
|
||||
).toBeNull();
|
||||
|
|
@ -121,12 +111,7 @@ describe('ConnectionEditor', () => {
|
|||
|
||||
const renderSlot = vi.fn(({ type }) => <div data-testid="slot">slot:{type}</div>);
|
||||
|
||||
render(() => (
|
||||
<ConnectionEditor
|
||||
renderCredentialSlot={renderSlot}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
));
|
||||
render(() => <ConnectionEditor renderCredentialSlot={renderSlot} onClose={() => {}} />);
|
||||
|
||||
const input = screen.getByPlaceholderText(/pve01\.lan/) as HTMLInputElement;
|
||||
fireEvent.input(input, { target: { value: 'baremetal.lan' } });
|
||||
|
|
@ -165,4 +150,29 @@ describe('ConnectionEditor', () => {
|
|||
expect(call.type).toBe('vmware');
|
||||
expect(call.candidate).toBeNull();
|
||||
});
|
||||
|
||||
it('resets probe state when returning to the catalog from a credential slot', async () => {
|
||||
mockedProbe.mockResolvedValueOnce({ candidates: [], probedMs: 203 });
|
||||
|
||||
const renderSlot = vi.fn(({ type }) => <div data-testid="slot">slot:{type}</div>);
|
||||
|
||||
render(() => <ConnectionEditor renderCredentialSlot={renderSlot} onClose={() => {}} />);
|
||||
|
||||
const input = screen.getByPlaceholderText(/pve01\.lan/) as HTMLInputElement;
|
||||
fireEvent.input(input, { target: { value: '192.168.1.50' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: /probe address/i }));
|
||||
|
||||
await waitFor(() => expect(mockedProbe).toHaveBeenCalled());
|
||||
await screen.findByText(/no supported product detected/i);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /TrueNAS SCALE/i }));
|
||||
await waitFor(() => expect(screen.getByTestId('slot').textContent).toBe('slot:truenas'));
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /back to catalog/i }));
|
||||
|
||||
const resetInput = screen.getByPlaceholderText(/pve01\.lan/) as HTMLInputElement;
|
||||
expect(resetInput.value).toBe('');
|
||||
expect(screen.queryByText(/no supported product detected/i)).toBeNull();
|
||||
expect(screen.queryByTestId('slot')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -154,6 +154,15 @@ describe('infrastructure operations model', () => {
|
|||
expect(INSTALL_PROFILE_OPTIONS.map((option) => option.value)).not.toContain('truenas');
|
||||
});
|
||||
|
||||
it('keeps the recommended auto profile aligned with unpinned Proxmox detection', () => {
|
||||
const autoProfile = INSTALL_PROFILE_OPTIONS.find((option) => option.value === 'auto');
|
||||
|
||||
expect(autoProfile).toBeDefined();
|
||||
expect(autoProfile?.flags).toEqual([]);
|
||||
expect(autoProfile?.description).toContain('leaves the type unpinned');
|
||||
expect(autoProfile?.description).toContain('every detected PVE / PBS service');
|
||||
});
|
||||
|
||||
it('keeps the embedded installer section on the canonical host-install framing', () => {
|
||||
expect(infrastructureInstallerSectionSource).toContain(
|
||||
"title={state.isEmbedded() ? 'Install on a host' : 'Infrastructure'}",
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ vi.mock('../useConnectionsLedger', () => ({
|
|||
isAgent: connection.type === 'agent',
|
||||
connection,
|
||||
})),
|
||||
findById: (id: string) => connectionState.connections.find((connection) => connection.id === id),
|
||||
findById: (id: string) =>
|
||||
connectionState.connections.find((connection) => connection.id === id),
|
||||
reload: vi.fn(),
|
||||
loading: () => false,
|
||||
error: () => null,
|
||||
|
|
@ -226,11 +227,11 @@ describe('InfrastructureWorkspace', () => {
|
|||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add infrastructure/i }));
|
||||
|
||||
// The catalog landing surfaces the probe input and a tile grid of every
|
||||
// supported product — including Install Pulse Agent as a peer tile — with
|
||||
// no intermediate picker screen.
|
||||
expect(screen.getByRole('button', { name: /Probe address/i })).toBeInTheDocument();
|
||||
// The catalog landing leads with the agent path and keeps the direct API
|
||||
// probe/catalog visible below it in the same shared editor.
|
||||
expect(screen.getByRole('button', { name: /Install Pulse Agent/i })).toBeInTheDocument();
|
||||
expect(screen.getByText('Or connect a platform API directly')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Probe address/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /^Proxmox VE/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /TrueNAS SCALE/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /VMware vCenter \/ ESXi/i })).toBeInTheDocument();
|
||||
|
|
@ -354,8 +355,8 @@ describe('InfrastructureWorkspace', () => {
|
|||
await waitFor(() =>
|
||||
expect(screen.getByRole('button', { name: /Probe address/i })).toBeInTheDocument(),
|
||||
);
|
||||
// Catalog landing — probe input + tile grid are visible, no credential
|
||||
// slot has been entered yet.
|
||||
// Catalog landing — the shared agent-led entry is visible again, and no
|
||||
// credential slot has been entered yet.
|
||||
expect(screen.getByRole('button', { name: /Install Pulse Agent/i })).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('install-section')).toBeNull();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ describe('settings architecture guardrails', () => {
|
|||
expect(settingsNavigationHookSource).toContain(
|
||||
'navigate(buildInfrastructureOnboardingPath(infrastructureOnboardingStep), {',
|
||||
);
|
||||
expect(settingsNavigationHookSource).toContain('navigate(buildInfrastructureWorkspacePath(), {');
|
||||
expect(settingsNavigationHookSource).toContain(
|
||||
'navigate(buildInfrastructureWorkspacePath(), {',
|
||||
);
|
||||
expect(settingsNavigationHookSource).toContain('resolveCanonicalSettingsPath(path)');
|
||||
|
||||
expect(infrastructureWorkspaceModelSource).toContain(
|
||||
|
|
@ -64,8 +66,12 @@ describe('settings architecture guardrails', () => {
|
|||
expect(infrastructureWorkspaceSource).toContain('NodeCredentialSlot');
|
||||
expect(infrastructureWorkspaceSource).toContain('TrueNASCredentialSlot');
|
||||
expect(infrastructureWorkspaceSource).toContain('VMwareCredentialSlot');
|
||||
expect(infrastructureWorkspaceSource).toContain('navigate(buildInfrastructureWorkspacePath(), { replace: true });');
|
||||
expect(infrastructureWorkspaceSource).toContain('const [, setSearchParams] = useSearchParams();');
|
||||
expect(infrastructureWorkspaceSource).toContain(
|
||||
'navigate(buildInfrastructureWorkspacePath(), { replace: true });',
|
||||
);
|
||||
expect(infrastructureWorkspaceSource).toContain(
|
||||
'const [, setSearchParams] = useSearchParams();',
|
||||
);
|
||||
expect(infrastructureWorkspaceSource).toContain(
|
||||
'setSearchParams({ [INFRASTRUCTURE_ADD_QUERY_PARAM]: null }, { replace: true });',
|
||||
);
|
||||
|
|
@ -75,8 +81,10 @@ describe('settings architecture guardrails', () => {
|
|||
expect(infrastructureWorkspaceSource).not.toContain('layout="drawer-right"');
|
||||
});
|
||||
|
||||
it('keeps probe-first connection setup and inline node credentials on the shared editor model', () => {
|
||||
expect(connectionEditorSource).toContain("import { AddressProbeStep } from './AddressProbeStep';");
|
||||
it('keeps the agent-led add landing and inline node credentials on the shared editor model', () => {
|
||||
expect(connectionEditorSource).toContain(
|
||||
"import { AddressProbeStep } from './AddressProbeStep';",
|
||||
);
|
||||
// Platform integrations render as peer tiles; the agent lives in its own
|
||||
// section below so it can explain what host-level telemetry adds instead
|
||||
// of being mistaken for one more peer of Proxmox / VMware / TrueNAS.
|
||||
|
|
@ -84,21 +92,13 @@ describe('settings architecture guardrails', () => {
|
|||
expect(connectionEditorSource).not.toContain("'agent'] =");
|
||||
expect(connectionEditorSource).toContain('<AddressProbeStep');
|
||||
expect(connectionEditorSource).toContain('Or connect a platform API directly');
|
||||
// The agent card is the *primary* path on the Add landing — it sits above
|
||||
// the probe input AND the platform grid so a user doesn't read the probe
|
||||
// as the lead action. The probe + platforms collapse into one "connect
|
||||
// via API instead" fallback block below the agent. Guarding ordering
|
||||
// here prevents regression to "agent as an alternative to the probe."
|
||||
const agentPrimaryIdx = connectionEditorSource.indexOf('On a Proxmox host, this is the');
|
||||
const apiSectionIdx = connectionEditorSource.indexOf('Or connect a platform API directly');
|
||||
const probeIdx = connectionEditorSource.indexOf('<AddressProbeStep');
|
||||
expect(agentPrimaryIdx).toBeGreaterThan(-1);
|
||||
expect(apiSectionIdx).toBeGreaterThan(-1);
|
||||
expect(probeIdx).toBeGreaterThan(-1);
|
||||
expect(agentPrimaryIdx).toBeLessThan(apiSectionIdx);
|
||||
expect(apiSectionIdx).toBeLessThan(probeIdx);
|
||||
// Keep the landing source aligned with the canonical agent-first story, but
|
||||
// leave actual DOM ordering to the render test rather than raw source
|
||||
// string positions.
|
||||
expect(connectionEditorSource).not.toContain('Or install the agent on the host');
|
||||
expect(connectionEditorSource).toContain('auto-registers the node');
|
||||
expect(connectionEditorSource).toContain('On a Proxmox host, this is the');
|
||||
expect(connectionEditorSource).toContain('auto-registers any detected PVE / PBS services');
|
||||
expect(connectionEditorSource).not.toContain('auto-registers the node');
|
||||
expect(connectionEditorSource).toContain('Recommended');
|
||||
// The agent install path is a first-class ledger-header action, not a
|
||||
// subtext offramp inside the editor — make sure it doesn't drift back.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
import type { JSX } from 'solid-js';
|
||||
import type {
|
||||
ConnectedInfrastructureItem,
|
||||
ConnectedInfrastructureSurface,
|
||||
} from '@/types/api';
|
||||
import {
|
||||
getAgentCapabilityLabel,
|
||||
type AgentCapability,
|
||||
} from '@/utils/agentCapabilityPresentation';
|
||||
import {
|
||||
type PlatformConnectionsView,
|
||||
} from './platformConnectionsModel';
|
||||
import type { ConnectedInfrastructureItem, ConnectedInfrastructureSurface } from '@/types/api';
|
||||
import { getAgentCapabilityLabel, type AgentCapability } from '@/utils/agentCapabilityPresentation';
|
||||
import { type PlatformConnectionsView } from './platformConnectionsModel';
|
||||
import { buildInfrastructureWorkspacePath } from './infrastructureWorkspaceModel';
|
||||
|
||||
export const TOKEN_PLACEHOLDER = '<api-token>';
|
||||
|
|
@ -18,12 +10,7 @@ export const UNIFIED_AGENT_TELEMETRY_SURFACE = 'settings_unified_agents';
|
|||
export type AgentPlatform = 'linux' | 'macos' | 'freebsd' | 'windows';
|
||||
export type UnifiedAgentStatus = 'active' | 'removed';
|
||||
export type ScopeCategory = 'default' | 'profile' | 'ai-managed' | 'na';
|
||||
export type InstallProfile =
|
||||
| 'auto'
|
||||
| 'docker'
|
||||
| 'kubernetes'
|
||||
| 'proxmox-pve'
|
||||
| 'proxmox-pbs';
|
||||
export type InstallProfile = 'auto' | 'docker' | 'kubernetes' | 'proxmox-pve' | 'proxmox-pbs';
|
||||
|
||||
export type SetupHandoffState = {
|
||||
username: string;
|
||||
|
|
@ -309,7 +296,7 @@ export const INSTALL_PROFILE_OPTIONS: InstallProfileOption[] = [
|
|||
value: 'auto',
|
||||
label: 'Auto-detect (recommended)',
|
||||
description:
|
||||
'Let the installer detect Docker, Kubernetes, Proxmox, and agent capabilities automatically.',
|
||||
'Let the installer detect Docker, Kubernetes, and Proxmox automatically. On Proxmox hosts, auto mode leaves the type unpinned so the agent can register every detected PVE / PBS service.',
|
||||
flags: [],
|
||||
},
|
||||
{
|
||||
|
|
@ -426,7 +413,9 @@ export const buildCommandsByPlatform = (
|
|||
},
|
||||
});
|
||||
|
||||
const agentCapabilityFromSurfaceKind = (kind: ConnectedInfrastructureSurface['kind']): AgentCapability => {
|
||||
const agentCapabilityFromSurfaceKind = (
|
||||
kind: ConnectedInfrastructureSurface['kind'],
|
||||
): AgentCapability => {
|
||||
switch (kind) {
|
||||
case 'agent':
|
||||
case 'docker':
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@
|
|||
#
|
||||
# Auto-Detection:
|
||||
# The installer automatically detects Docker, Kubernetes, and Proxmox on the
|
||||
# target machine and enables monitoring for detected platforms. Use --disable-*
|
||||
# flags to skip specific platforms, or --enable-* to force enable even if not
|
||||
# detected.
|
||||
# target machine and enables monitoring for detected platforms. Proxmox auto
|
||||
# mode keeps the runtime unpinned so the agent can register every detected
|
||||
# PVE / PBS service on that host. Use --disable-* flags to skip specific
|
||||
# platforms, or --enable-* to force enable even if not detected.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -774,19 +775,6 @@ detect_proxmox() {
|
|||
return 1
|
||||
}
|
||||
|
||||
detect_proxmox_type() {
|
||||
if [[ -d "/etc/proxmox-backup" ]] || command -v proxmox-backup-manager &>/dev/null; then
|
||||
echo "pbs"
|
||||
return 0
|
||||
fi
|
||||
if [[ -d "/etc/pve" ]] || command -v pveversion &>/dev/null; then
|
||||
echo "pve"
|
||||
return 0
|
||||
fi
|
||||
echo ""
|
||||
return 1
|
||||
}
|
||||
|
||||
pulse_url_uses_plain_http() {
|
||||
local url_lower
|
||||
url_lower=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')
|
||||
|
|
@ -1106,22 +1094,18 @@ if [[ "$PROXMOX_EXPLICIT" != "true" ]]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
if [[ "$ENABLE_PROXMOX" == "true" && -z "$PROXMOX_TYPE" ]]; then
|
||||
auto_type="$(detect_proxmox_type || true)"
|
||||
if [[ -n "$auto_type" ]]; then
|
||||
PROXMOX_TYPE="$auto_type"
|
||||
log_info "Proxmox mode detected: ${PROXMOX_TYPE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary of what will be monitored
|
||||
log_info "Monitoring configuration:"
|
||||
log_info " Agent metrics: $ENABLE_HOST"
|
||||
log_info " Docker/Podman: $ENABLE_DOCKER"
|
||||
log_info " Kubernetes: $ENABLE_KUBERNETES"
|
||||
log_info " Proxmox: $ENABLE_PROXMOX"
|
||||
if [[ "$ENABLE_PROXMOX" == "true" && -n "$PROXMOX_TYPE" ]]; then
|
||||
log_info " Proxmox type: $PROXMOX_TYPE"
|
||||
if [[ "$ENABLE_PROXMOX" == "true" ]]; then
|
||||
if [[ -n "$PROXMOX_TYPE" ]]; then
|
||||
log_info " Proxmox type: $PROXMOX_TYPE"
|
||||
else
|
||||
log_info " Proxmox type: auto-detect all installed services"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Uninstall Logic ---
|
||||
|
|
|
|||
|
|
@ -38,6 +38,36 @@ func TestInstallSHAllowsMissingTokenForOptionalAuth(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInstallSHAutoDetectProxmoxKeepsRuntimeTypeUnpinned(t *testing.T) {
|
||||
content, err := os.ReadFile(repoFile("scripts", "install.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read install.sh: %v", err)
|
||||
}
|
||||
|
||||
script := string(content)
|
||||
required := []string{
|
||||
`if detect_proxmox; then`,
|
||||
`log_info "Proxmox detected - enabling Proxmox integration"`,
|
||||
`log_info " Proxmox type: auto-detect all installed services"`,
|
||||
}
|
||||
for _, needle := range required {
|
||||
if !strings.Contains(script, needle) {
|
||||
t.Fatalf("install.sh missing unpinned Proxmox auto-detect contract: %s", needle)
|
||||
}
|
||||
}
|
||||
|
||||
forbidden := []string{
|
||||
`detect_proxmox_type() {`,
|
||||
`auto_type="$(detect_proxmox_type || true)"`,
|
||||
`PROXMOX_TYPE="$auto_type"`,
|
||||
}
|
||||
for _, needle := range forbidden {
|
||||
if strings.Contains(script, needle) {
|
||||
t.Fatalf("install.sh preserved stale single-type Proxmox auto-detect contract: %s", needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConnectionEnvRecovery verifies the canonical helper logic that parses
|
||||
// connection.env without using shell source (to prevent injection).
|
||||
func TestConnectionEnvRecovery(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue