mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 22:12:23 +00:00
patrol settings: describe Watch only for plan-locked installs
The Settings > Patrol "Patrol mode" section told every install to choose a mode on the Patrol page, but plan-locked (free) installs have no mode choice there — Watch only is their only mode and the paid modes stay off the free surface. The copy was tier-blind and sent free users on a fool's errand. Make the section tier-aware via the canonical patrol autonomy availability resolver: plan-locked installs now read "This install runs Watch only. Patrol monitors your infrastructure and reports issues here." while Pro/runtime installs keep the choose-on-the-Patrol-page redirect. The Open Patrol button stays.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Component, Show } from 'solid-js';
|
||||
import { Component, createMemo, Show } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { AIChatMaintenanceSection } from '@/components/Settings/AIChatMaintenanceSection';
|
||||
import { AISettingsDialogs } from '@/components/Settings/AISettingsDialogs';
|
||||
@@ -19,6 +19,13 @@ import { LoadingSpinner } from '@/components/shared/LoadingSpinner';
|
||||
import SettingsPanel from '@/components/shared/SettingsPanel';
|
||||
import { Toggle } from '@/components/shared/Toggle';
|
||||
import { PATROL_PATH } from '@/routing/resourceLinks';
|
||||
import { getPatrolAutonomyAvailabilityPresentation } from '@/features/patrol/patrolAutonomyAvailability';
|
||||
import { getRuntimeCapabilityBlock, hasFeature, runtimeCapabilities } from '@/stores/license';
|
||||
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
|
||||
import {
|
||||
presentationPolicyHidesCommercialSurfaces,
|
||||
presentationPolicyHidesUpgradePrompts,
|
||||
} from '@/stores/sessionPresentationPolicy';
|
||||
import {
|
||||
AI_SETTINGS_PANEL_TITLE,
|
||||
getAISettingsLoadingState,
|
||||
@@ -93,6 +100,17 @@ const PatrolSettingsContent: Component<{ state: ReturnType<typeof useAISettingsS
|
||||
props,
|
||||
) => {
|
||||
const navigate = useNavigate();
|
||||
const patrolModeAvailability = createMemo(() =>
|
||||
getPatrolAutonomyAvailabilityPresentation({
|
||||
autoFixLocked: !hasFeature('ai_autofix'),
|
||||
commercialSurfacesHidden: presentationPolicyHidesCommercialSurfaces(),
|
||||
upgradePromptsHidden: presentationPolicyHidesUpgradePrompts(),
|
||||
runtimeCapabilityBlock: getRuntimeCapabilityBlock('ai_autofix'),
|
||||
runtime: runtimeCapabilities()?.runtime,
|
||||
planUpgradeDestination: getUpgradeActionDestination('ai_autofix'),
|
||||
}),
|
||||
);
|
||||
const isPlanLockedPatrol = createMemo(() => patrolModeAvailability().kind === 'plan_locked');
|
||||
const intervalOptions = [
|
||||
{ value: 0, label: 'Manual only' },
|
||||
{ value: 60, label: 'Every hour' },
|
||||
@@ -109,8 +127,13 @@ const PatrolSettingsContent: Component<{ state: ReturnType<typeof useAISettingsS
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-base-content">Patrol mode</h3>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
Choose a Patrol mode on the Patrol page. Keep schedule, triggers, and model readiness
|
||||
here.
|
||||
<Show
|
||||
when={isPlanLockedPatrol()}
|
||||
fallback="Choose a Patrol mode on the Patrol page. Keep schedule, triggers, and model readiness here."
|
||||
>
|
||||
This install runs Watch only. Patrol monitors your infrastructure and reports issues
|
||||
here.
|
||||
</Show>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -82,7 +82,9 @@ vi.mock('@/utils/logger', () => ({
|
||||
|
||||
vi.mock('@/stores/license', () => ({
|
||||
hasFeature: (...args: unknown[]) => hasFeatureMock(...args),
|
||||
getRuntimeCapabilityBlock: () => undefined,
|
||||
loadRuntimeCapabilities: (...args: unknown[]) => loadLicenseStatusMock(...args),
|
||||
runtimeCapabilities: () => ({ capabilities: [], runtime: undefined }),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/licenseCommercial', () => ({
|
||||
@@ -95,6 +97,7 @@ vi.mock('@/stores/licenseCommercial', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/sessionPresentationPolicy', () => ({
|
||||
presentationPolicyHidesCommercialSurfaces: () => false,
|
||||
presentationPolicyHidesUpgradePrompts: () => presentationPolicyHidesUpgradePromptsMock(),
|
||||
}));
|
||||
|
||||
@@ -295,6 +298,25 @@ describe('AISettings model loading error states', () => {
|
||||
expect(screen.queryByText('External agents')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('describes Watch only for plan-locked installs instead of telling the user to choose a mode', async () => {
|
||||
hasFeatureMock.mockImplementation((feature: string) => feature !== 'ai_autofix');
|
||||
getSettingsMock.mockResolvedValue({
|
||||
...baseSettings(),
|
||||
enabled: true,
|
||||
configured: true,
|
||||
patrol_interval_minutes: 180,
|
||||
});
|
||||
|
||||
renderComponent('patrol');
|
||||
|
||||
expect(await screen.findByText('Patrol mode')).toBeInTheDocument();
|
||||
expect(screen.getByText(/This install runs Watch only/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText(/Choose a Patrol mode on the Patrol page/i),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Open Patrol/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('saves Patrol trigger settings from Pulse Intelligence Patrol settings', async () => {
|
||||
getSettingsMock.mockResolvedValue({
|
||||
...baseSettings(),
|
||||
|
||||
Reference in New Issue
Block a user