mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
chore: UI improvements and branding updates
Frontend Updates: - Add AI Intelligence page routing in App.tsx - Add PulsePatrolLogo brand component - Update Settings with Pulse Assistant branding - Update FeaturesStep wizard text - Minor fixes across Dashboard, Backups, Storage, Kubernetes components Backend: - Update license feature handling - Improve model converters Tooling: - Add pulse-check diagnostic script
This commit is contained in:
@@ -46,6 +46,7 @@ import SettingsIcon from 'lucide-solid/icons/settings';
|
||||
import NetworkIcon from 'lucide-solid/icons/network';
|
||||
import Maximize2Icon from 'lucide-solid/icons/maximize-2';
|
||||
import Minimize2Icon from 'lucide-solid/icons/minimize-2';
|
||||
import { PulsePatrolLogo } from '@/components/Brand/PulsePatrolLogo';
|
||||
import { TokenRevealDialog } from './components/TokenRevealDialog';
|
||||
import { useAlertsActivation } from './stores/alertsActivation';
|
||||
import { UpdateProgressModal } from './components/UpdateProgressModal';
|
||||
@@ -53,6 +54,7 @@ import type { UpdateStatus } from './api/updates';
|
||||
import { AIChat } from './components/AI/Chat';
|
||||
import { AIStatusIndicator } from './components/AI/AIStatusIndicator';
|
||||
import { aiChatStore } from './stores/aiChat';
|
||||
import { getPatrolStatus } from './api/patrol';
|
||||
import { useResourcesAsLegacy } from './hooks/useResources';
|
||||
import { updateSystemSettingsFromResponse, markSystemSettingsLoadedWithDefaults } from './stores/systemSettings';
|
||||
import { initKioskMode, isKioskMode, setKioskMode, subscribeToKioskMode } from './utils/url';
|
||||
@@ -83,6 +85,9 @@ const HostsOverview = lazy(() =>
|
||||
default: module.HostsOverview,
|
||||
})),
|
||||
);
|
||||
const AIIntelligencePage = lazy(() =>
|
||||
import('./pages/AIIntelligence').then((module) => ({ default: module.AIIntelligence })),
|
||||
);
|
||||
|
||||
|
||||
// Enhanced store type with proper typing
|
||||
@@ -962,6 +967,7 @@ function App() {
|
||||
|
||||
<Route path="/servers" component={() => <Navigate href="/hosts" />} />
|
||||
<Route path="/alerts/*" component={AlertsPage} />
|
||||
<Route path="/ai/*" component={AIIntelligencePage} />
|
||||
<Route path="/settings/*" component={SettingsRoute} />
|
||||
</Router>
|
||||
);
|
||||
@@ -1037,6 +1043,17 @@ function AppLayout(props: {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
// Track patrol license status for Pro badge
|
||||
const [patrolLicenseRequired, setPatrolLicenseRequired] = createSignal(false);
|
||||
onMount(async () => {
|
||||
try {
|
||||
const status = await getPatrolStatus();
|
||||
setPatrolLicenseRequired(status.license_required ?? false);
|
||||
} catch {
|
||||
// Ignore errors - default to not showing badge
|
||||
}
|
||||
});
|
||||
|
||||
const readSeenPlatforms = (): Record<string, boolean> => {
|
||||
if (typeof window === 'undefined') return {};
|
||||
try {
|
||||
@@ -1101,6 +1118,7 @@ function AppLayout(props: {
|
||||
if (path.startsWith('/hosts')) return 'hosts';
|
||||
if (path.startsWith('/servers')) return 'hosts'; // Legacy redirect
|
||||
if (path.startsWith('/alerts')) return 'alerts';
|
||||
if (path.startsWith('/ai')) return 'ai';
|
||||
if (path.startsWith('/settings')) return 'settings';
|
||||
return 'proxmox';
|
||||
};
|
||||
@@ -1223,11 +1241,11 @@ function AppLayout(props: {
|
||||
scopes.includes('*') || scopes.includes('settings:read');
|
||||
|
||||
const tabs: Array<{
|
||||
id: 'alerts' | 'settings';
|
||||
id: 'alerts' | 'ai' | 'settings';
|
||||
label: string;
|
||||
route: string;
|
||||
tooltip: string;
|
||||
badge: 'update' | null;
|
||||
badge: 'update' | 'pro' | null;
|
||||
count: number | undefined;
|
||||
breakdown: { warning: number; critical: number } | undefined;
|
||||
icon: JSX.Element;
|
||||
@@ -1242,6 +1260,16 @@ function AppLayout(props: {
|
||||
breakdown,
|
||||
icon: <BellIcon class="w-4 h-4 shrink-0" />,
|
||||
},
|
||||
{
|
||||
id: 'ai',
|
||||
label: 'Patrol',
|
||||
route: '/ai',
|
||||
tooltip: 'Pulse Patrol monitoring and analysis',
|
||||
badge: patrolLicenseRequired() ? 'pro' : null,
|
||||
count: undefined,
|
||||
breakdown: undefined,
|
||||
icon: <PulsePatrolLogo class="w-4 h-4 shrink-0" />,
|
||||
},
|
||||
];
|
||||
|
||||
// Only show settings tab if user has access
|
||||
@@ -1484,6 +1512,11 @@ function AppLayout(props: {
|
||||
<span aria-hidden="true" class="block h-2 w-2 rounded-full bg-red-500 animate-pulse"></span>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={tab.badge === 'pro'}>
|
||||
<span class="ml-1.5 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900/50 rounded">
|
||||
Pro
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -231,7 +231,7 @@ export class AgentProfilesAPI {
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
if (response.status === 503) {
|
||||
throw new Error('AI service is not available. Please check AI settings.');
|
||||
throw new Error('Pulse Assistant service is not available. Please check Pulse Assistant settings.');
|
||||
}
|
||||
throw new Error(text || `Failed to get suggestion: ${response.status}`);
|
||||
}
|
||||
|
||||
@@ -1163,7 +1163,7 @@ const UnifiedBackups: Component = () => {
|
||||
</svg>
|
||||
}
|
||||
title="No backup sources configured"
|
||||
description="Add a Proxmox VE or PBS node in the Settings tab to start monitoring backups."
|
||||
description="Install the Pulse agent for extra capabilities (temperature monitoring and Pulse Patrol automation), or add a node via API token in Settings → Proxmox."
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
|
||||
29
frontend-modern/src/components/Brand/PulsePatrolLogo.tsx
Normal file
29
frontend-modern/src/components/Brand/PulsePatrolLogo.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { Component } from 'solid-js';
|
||||
|
||||
interface PulsePatrolLogoProps {
|
||||
class?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const PulsePatrolLogo: Component<PulsePatrolLogoProps> = (props) => {
|
||||
const title = () => props.title ?? 'Pulse Patrol';
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label={title()}
|
||||
class={props.class}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<title>{title()}</title>
|
||||
{/* Shield with check */}
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
<path d="m9 12 2 2 4-4" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1006,7 +1006,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
</svg>
|
||||
}
|
||||
title="No Proxmox VE nodes configured"
|
||||
description="Add a Proxmox VE node in the Settings tab to start monitoring your infrastructure."
|
||||
description="Install the Pulse agent for extra capabilities (temperature monitoring and Pulse Patrol automation), or add a node via API token in Settings → Proxmox."
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -135,7 +135,7 @@ Start with the most critical problems first.`;
|
||||
hover:shadow-lg hover:shadow-purple-500/30
|
||||
transition-all duration-150 active:scale-95
|
||||
ring-1 ring-purple-400/50"
|
||||
title={`Ask AI to help investigate and resolve ${props.problemGuests.length} problem${props.problemGuests.length !== 1 ? 's' : ''}`}
|
||||
title={`Ask Pulse Assistant to help investigate and resolve ${props.problemGuests.length} problem${props.problemGuests.length !== 1 ? 's' : ''}`}
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path
|
||||
@@ -144,7 +144,7 @@ Start with the most critical problems first.`;
|
||||
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Investigate {props.problemGuests.length} with AI</span>
|
||||
<span>Investigate {props.problemGuests.length} with Pulse Assistant</span>
|
||||
<svg class="w-3 h-3 opacity-70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||
</svg>
|
||||
|
||||
@@ -279,11 +279,11 @@ export const KubernetesClusters: Component<KubernetesClustersProps> = (props) =>
|
||||
return;
|
||||
}
|
||||
if (!aiConfigured()) {
|
||||
setAnalysisError('AI is not configured. Configure it in Settings -> AI.');
|
||||
setAnalysisError('Pulse Assistant is not configured. Configure it in Settings → Pulse Assistant.');
|
||||
return;
|
||||
}
|
||||
if (!kubernetesAiEnabled()) {
|
||||
setAnalysisError('Pulse Pro is required for Kubernetes AI analysis.');
|
||||
setAnalysisError('Pulse Pro is required for Kubernetes analysis.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -560,12 +560,12 @@ export const KubernetesClusters: Component<KubernetesClustersProps> = (props) =>
|
||||
<div class="flex-1 min-w-[300px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-sm font-bold text-gray-900 dark:text-gray-100 flex items-center gap-2">
|
||||
Kubernetes AI Analysis
|
||||
Kubernetes Analysis
|
||||
{/* Badge removed - feature soft-locked instead */}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1 leading-relaxed">
|
||||
Generate deep health insights and actionable remediation for your clusters using Pulse's advanced AI engine.
|
||||
Generate deep health insights and actionable remediation for your clusters using Pulse's advanced analysis engine.
|
||||
</div>
|
||||
</div>
|
||||
<Show when={!licenseLoading() && !kubernetesAiEnabled()}>
|
||||
@@ -584,7 +584,7 @@ export const KubernetesClusters: Component<KubernetesClustersProps> = (props) =>
|
||||
<Show when={licenseLoading() || aiLoading()}>
|
||||
<div class="flex items-center gap-3 p-4 bg-blue-50/50 dark:bg-blue-900/20 rounded-xl border border-blue-100 dark:border-blue-800 animate-pulse">
|
||||
<div class="h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||
<span class="text-xs font-medium text-blue-700 dark:text-blue-300">Synchronizing AI & License...</span>
|
||||
<span class="text-xs font-medium text-blue-700 dark:text-blue-300">Synchronizing Pulse Assistant & License...</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -599,7 +599,7 @@ export const KubernetesClusters: Component<KubernetesClustersProps> = (props) =>
|
||||
</div>
|
||||
<h4 class="text-lg font-bold text-gray-900 dark:text-white mb-2">Power up your Kubernetes Fleet</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6 leading-relaxed">
|
||||
Pulse Pro brings advanced AI-driven diagnostics to your Kubernetes clusters. Identify bottlenecks, security risks, and configuration drift in seconds.
|
||||
Pulse Pro brings advanced diagnostics to your Kubernetes clusters. Identify bottlenecks, security risks, and configuration drift in seconds.
|
||||
</p>
|
||||
<a
|
||||
href={upgradeUrl()}
|
||||
@@ -607,7 +607,7 @@ export const KubernetesClusters: Component<KubernetesClustersProps> = (props) =>
|
||||
rel="noreferrer"
|
||||
class="inline-flex items-center gap-2.5 px-6 py-2.5 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700 transform hover:scale-105 active:scale-95 transition-all shadow-lg font-bold text-sm"
|
||||
>
|
||||
Unlock Kubernetes AI
|
||||
Unlock Kubernetes Insights
|
||||
<ExternalLink class="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -646,7 +646,7 @@ export const KubernetesClusters: Component<KubernetesClustersProps> = (props) =>
|
||||
|
||||
<Show when={!aiLoading() && !aiConfigured()}>
|
||||
<div class="text-xs text-amber-600 dark:text-amber-400">
|
||||
AI is not configured. Configure it in Settings → AI.
|
||||
Pulse Assistant is not configured. Configure it in Settings → Pulse Assistant.
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
|
||||
@@ -404,7 +404,7 @@ export const AISettings: Component = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[AISettings] Failed to load settings:', error);
|
||||
notificationStore.error('Failed to load AI settings');
|
||||
notificationStore.error('Failed to load Pulse Assistant settings');
|
||||
setSettings(null);
|
||||
resetForm(null);
|
||||
} finally {
|
||||
@@ -587,12 +587,12 @@ export const AISettings: Component = () => {
|
||||
const updated = await AIAPI.updateSettings(payload);
|
||||
setSettings(updated);
|
||||
resetForm(updated);
|
||||
notificationStore.success('AI settings saved');
|
||||
notificationStore.success('Pulse Assistant settings saved');
|
||||
// Notify other components (like AIChat) that settings changed so they can refresh models
|
||||
aiChatStore.notifySettingsChanged();
|
||||
} catch (error) {
|
||||
logger.error('[AISettings] Failed to save settings:', error);
|
||||
const message = error instanceof Error ? error.message : 'Failed to save AI settings';
|
||||
const message = error instanceof Error ? error.message : 'Failed to save Pulse Assistant settings';
|
||||
notificationStore.error(message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
@@ -650,7 +650,7 @@ export const AISettings: Component = () => {
|
||||
|
||||
let confirmMessage = `Clear ${PROVIDER_DISPLAY_NAMES[provider] || provider} credentials?`;
|
||||
if (isLastProvider) {
|
||||
confirmMessage = `⚠️ This is your only configured provider! Clearing it will disable AI until you configure another provider. Continue?`;
|
||||
confirmMessage = `⚠️ This is your only configured provider! Clearing it will disable Pulse Assistant until you configure another provider. Continue?`;
|
||||
} else if (modelUsesProvider) {
|
||||
confirmMessage = `Your current model uses ${PROVIDER_DISPLAY_NAMES[provider] || provider}. Clearing this will require selecting a different model. Continue?`;
|
||||
} else {
|
||||
@@ -726,7 +726,7 @@ export const AISettings: Component = () => {
|
||||
</div>
|
||||
<SectionHeader
|
||||
title="Pulse Assistant"
|
||||
description="Configure AI-powered infrastructure analysis"
|
||||
description="Configure Pulse Assistant and Patrol analysis"
|
||||
size="sm"
|
||||
class="flex-1"
|
||||
/>
|
||||
@@ -757,7 +757,7 @@ export const AISettings: Component = () => {
|
||||
// Revert on failure
|
||||
setForm('enabled', !newValue);
|
||||
logger.error('[AISettings] Failed to toggle AI:', error);
|
||||
const message = error instanceof Error ? error.message : 'Failed to update AI setting';
|
||||
const message = error instanceof Error ? error.message : 'Failed to update Pulse Assistant setting';
|
||||
notificationStore.error(message);
|
||||
}
|
||||
}}
|
||||
@@ -778,7 +778,7 @@ export const AISettings: Component = () => {
|
||||
<Show when={loading()}>
|
||||
<div class="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span class="h-4 w-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
||||
Loading AI settings...
|
||||
Loading Pulse Assistant settings...
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -966,17 +966,17 @@ export const AISettings: Component = () => {
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
{/* AI Provider Configuration - Configure API keys for all providers */}
|
||||
{/* Provider Configuration - Configure API keys for all providers */}
|
||||
<div class={`${formField} p-5 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/40`}>
|
||||
<div class="mb-3">
|
||||
<h4 class="font-medium text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
AI Provider Configuration
|
||||
Provider Configuration
|
||||
</h4>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
Configure API keys for each AI provider you want to use. Models from all configured providers will appear in the model selectors.
|
||||
Configure API keys for each provider you want to use. Models from all configured providers will appear in the model selectors.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1469,7 +1469,7 @@ export const AISettings: Component = () => {
|
||||
</Show>
|
||||
<Show when={!autoFixLocked() && !form.patrolAutoFix && !autoFixAcknowledged()}>
|
||||
<p class="text-[10px] text-amber-600 dark:text-amber-400 mt-1">
|
||||
⚠️ AI will execute fixes without approval. Enable with caution.
|
||||
⚠️ Pulse Patrol will execute fixes without approval. Enable with caution.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={!autoFixLocked() && form.patrolAutoFix}>
|
||||
@@ -1477,7 +1477,7 @@ export const AISettings: Component = () => {
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
Auto-Fix is ON. AI will attempt automatic remediation.
|
||||
Auto-Fix is ON. Pulse Patrol will attempt automatic remediation.
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -1524,7 +1524,7 @@ export const AISettings: Component = () => {
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
{/* AI Cost Controls - Compact */}
|
||||
{/* Usage Cost Controls - Compact */}
|
||||
<div class="flex items-center gap-3 p-3 rounded-lg border border-emerald-200 dark:border-emerald-800 bg-emerald-50 dark:bg-emerald-900/20">
|
||||
<svg class="w-4 h-4 text-emerald-600 dark:text-emerald-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
@@ -1610,19 +1610,19 @@ export const AISettings: Component = () => {
|
||||
class="flex-1 px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700"
|
||||
disabled={saving()}
|
||||
>
|
||||
<option value="read_only">Read Only - AI can only observe</option>
|
||||
<option value="controlled">Controlled - AI executes with your approval</option>
|
||||
<option value="autonomous">Autonomous - AI executes without approval (Pro)</option>
|
||||
<option value="read_only">Read Only - Pulse Assistant can only observe</option>
|
||||
<option value="controlled">Controlled - Pulse Assistant executes with your approval</option>
|
||||
<option value="autonomous">Autonomous - Pulse Assistant executes without approval (Pro)</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="text-[10px] text-gray-500 dark:text-gray-400 ml-[7.5rem]">
|
||||
{form.controlLevel === 'read_only' && '🔒 AI can only query and observe - no commands or control actions'}
|
||||
{form.controlLevel === 'controlled' && '✅ AI can execute commands and control VMs/containers with your approval'}
|
||||
{form.controlLevel === 'autonomous' && '⚠️ AI executes all commands and control actions without asking'}
|
||||
{form.controlLevel === 'read_only' && '🔒 Pulse Assistant can only query and observe - no commands or control actions'}
|
||||
{form.controlLevel === 'controlled' && '✅ Pulse Assistant can execute commands and control VMs/containers with your approval'}
|
||||
{form.controlLevel === 'autonomous' && '⚠️ Pulse Assistant executes all commands and control actions without asking'}
|
||||
</p>
|
||||
<Show when={form.controlLevel === 'autonomous'}>
|
||||
<div class="p-2 bg-amber-100/50 dark:bg-amber-900/30 rounded border border-amber-200 dark:border-amber-800 text-[10px] text-amber-800 dark:text-amber-200">
|
||||
<strong>Legal Disclaimer:</strong> AI models can hallucinate. You are responsible for any damage caused by autonomous actions. See <a href="https://github.com/rcourtman/Pulse/blob/main/TERMS.md" target="_blank" class="underline">Terms of Service</a>.
|
||||
<strong>Legal Disclaimer:</strong> Model-driven systems can hallucinate. You are responsible for any damage caused by autonomous actions. See <a href="https://github.com/rcourtman/Pulse/blob/main/TERMS.md" target="_blank" class="underline">Terms of Service</a>.
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={form.controlLevel === 'autonomous' && autoFixLocked()}>
|
||||
@@ -1653,7 +1653,7 @@ export const AISettings: Component = () => {
|
||||
disabled={saving()}
|
||||
/>
|
||||
<p class="text-[10px] text-gray-500 dark:text-gray-400 mt-1">
|
||||
Comma-separated VMIDs or names that AI cannot control
|
||||
Comma-separated VMIDs or names that Pulse Assistant cannot control
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1686,7 +1686,7 @@ export const AISettings: Component = () => {
|
||||
<Show when={showChatMaintenance()}>
|
||||
<div class="px-3 py-3 bg-white dark:bg-gray-800/50 border-t border-gray-200 dark:border-gray-700 space-y-3">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
Use this panel to summarize, inspect, or revert a specific chat session. It does not change your default AI settings.
|
||||
Use this panel to summarize, inspect, or revert a specific chat session. It does not change your default Pulse Assistant settings.
|
||||
</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs font-medium text-gray-600 dark:text-gray-400">Session</label>
|
||||
@@ -1790,7 +1790,7 @@ export const AISettings: Component = () => {
|
||||
<span class="text-xs font-medium">
|
||||
{settings()?.configured
|
||||
? `Ready • ${settings()?.configured_providers?.length || 0} provider${(settings()?.configured_providers?.length || 0) !== 1 ? 's' : ''} • ${availableModels().length} models`
|
||||
: 'Configure at least one AI provider above to enable AI features'}
|
||||
: 'Configure at least one provider above to enable Pulse Assistant features'}
|
||||
</span>
|
||||
<Show when={settings()?.configured && settings()?.model}>
|
||||
<span class="text-xs opacity-75 ml-2">
|
||||
@@ -2089,7 +2089,7 @@ export const AISettings: Component = () => {
|
||||
disabled={setupSaving() || (setupProvider() !== 'ollama' && !setupApiKey().trim()) || (setupProvider() === 'ollama' && !setupOllamaUrl().trim())}
|
||||
>
|
||||
{setupSaving() && <span class="h-4 w-4 border-2 border-white border-t-transparent rounded-full animate-spin" />}
|
||||
Enable AI
|
||||
Enable Pulse Assistant
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -684,7 +684,7 @@ export const DiagnosticsPanel: Component = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Pulse Assistant</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Pulse AI Service</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Pulse Assistant Service</p>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<StatusBadge
|
||||
|
||||
@@ -724,7 +724,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
||||
<li>Creates monitoring user and API token automatically</li>
|
||||
<li>Registers the node with Pulse</li>
|
||||
<li>Enables temperature monitoring (no SSH required)</li>
|
||||
<li>Enables AI features for managing VMs/containers</li>
|
||||
<li>Enables Pulse Patrol automation for managing VMs/containers</li>
|
||||
</ul>
|
||||
<p class="text-blue-800 dark:text-blue-200 font-medium">
|
||||
Run this command on your Proxmox VE node:
|
||||
@@ -797,7 +797,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
||||
<Show when={formData().setupMode === 'auto'}>
|
||||
<div class="rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 mb-3 dark:border-amber-700 dark:bg-amber-900/20">
|
||||
<p class="text-xs text-amber-800 dark:text-amber-200">
|
||||
<strong>Limited functionality:</strong> API-only mode does not include temperature monitoring or AI features.
|
||||
<strong>Limited functionality:</strong> API-only mode does not include temperature monitoring or Pulse Patrol automation.
|
||||
For full functionality, use the Agent Install tab instead.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1307,7 +1307,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
||||
<ul class="text-xs text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
|
||||
<li>One-command setup (creates API user and token automatically)</li>
|
||||
<li>Built-in temperature monitoring (no SSH required)</li>
|
||||
<li>Pulse features (execute commands via Pulse AI)</li>
|
||||
<li>Pulse features (execute commands via Pulse Assistant)</li>
|
||||
<li>Automatic reconnection on network issues</li>
|
||||
</ul>
|
||||
<p class="text-blue-800 dark:text-blue-200 text-xs mt-3">
|
||||
|
||||
@@ -19,8 +19,8 @@ const TIER_LABELS: Record<string, string> = {
|
||||
const FEATURE_LABELS: Record<string, string> = {
|
||||
ai_patrol: 'Pulse Patrol',
|
||||
ai_alerts: 'Pulse Alert Analysis',
|
||||
ai_autofix: 'AI Auto-Fix',
|
||||
kubernetes_ai: 'Kubernetes AI',
|
||||
ai_autofix: 'Patrol Auto-Fix',
|
||||
kubernetes_ai: 'Kubernetes Insights',
|
||||
update_alerts: 'Update Alerts',
|
||||
multi_user: 'Multi-user / RBAC',
|
||||
white_label: 'White-label Branding',
|
||||
@@ -153,7 +153,7 @@ export const ProLicensePanel: Component = () => {
|
||||
<div class="space-y-6">
|
||||
<SettingsPanel
|
||||
title="Pulse Pro License"
|
||||
description="Activate your Pulse Pro license to unlock AI automation features."
|
||||
description="Activate your Pulse Pro license to unlock Pulse Patrol automation features."
|
||||
action={
|
||||
<button
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors disabled:opacity-60"
|
||||
|
||||
@@ -303,7 +303,7 @@ const SETTINGS_HEADER_META: Record<SettingsTab, { title: string; description: st
|
||||
},
|
||||
'system-ai': {
|
||||
title: 'Pulse Assistant',
|
||||
description: 'Configure AI-powered infrastructure analysis and remediation suggestions.',
|
||||
description: 'Configure Pulse Assistant and Patrol analysis and remediation suggestions.',
|
||||
},
|
||||
'system-pro': {
|
||||
title: 'Pulse Pro',
|
||||
@@ -2440,14 +2440,17 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||
</svg>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-blue-800 dark:text-blue-200">
|
||||
<strong>Recommended:</strong> Install the Pulse agent on your Proxmox nodes for automatic setup, temperature monitoring, and AI features.
|
||||
<strong>Recommended:</strong> Install the Pulse agent on your Proxmox nodes for extra capabilities like temperature monitoring and Pulse Patrol automation (it also auto-creates the API token and links the node).
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-blue-700 dark:text-blue-300">
|
||||
Prefer not to run an agent on PVE? Use the manual API token setup below.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate('/settings/agents')}
|
||||
class="mt-2 text-sm font-medium text-blue-700 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200 underline"
|
||||
>
|
||||
Go to Agents tab →
|
||||
Install agent →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -247,10 +247,10 @@ export const SuggestProfileModal: Component<SuggestProfileModalProps> = (props)
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
AI Profile Suggestion
|
||||
Pulse Assistant Profile Suggestion
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
Describe what you need, and AI will draft a profile
|
||||
Describe what you need, and Pulse Assistant will draft a profile
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component, createSignal, Show, For, onMount, createEffect, createMemo } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { useWebSocket } from '@/App';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { ProxmoxIcon } from '@/components/icons/ProxmoxIcon';
|
||||
import { formatRelativeTime, formatAbsoluteTime } from '@/utils/format';
|
||||
import { MonitoringAPI } from '@/api/monitoring';
|
||||
import { AgentProfilesAPI, type AgentProfile, type AgentProfileAssignment } from '@/api/agentProfiles';
|
||||
@@ -144,6 +146,7 @@ const buildCommandsByPlatform = (url: string): Record<
|
||||
|
||||
export const UnifiedAgents: Component = () => {
|
||||
const { state } = useWebSocket();
|
||||
const navigate = useNavigate();
|
||||
|
||||
let hasLoggedSecurityStatusError = false;
|
||||
|
||||
@@ -476,7 +479,7 @@ export const UnifiedAgents: Component = () => {
|
||||
profile.description?.toLowerCase().includes('pulse ai') ||
|
||||
name.toLowerCase().startsWith('ai scope');
|
||||
return isAIManaged
|
||||
? { label: 'AI-managed', detail: name, category: 'ai-managed' as const }
|
||||
? { label: 'Patrol-managed', detail: name, category: 'ai-managed' as const }
|
||||
: { label: name, detail: 'Assigned profile', category: 'profile' as const };
|
||||
};
|
||||
|
||||
@@ -756,7 +759,7 @@ export const UnifiedAgents: Component = () => {
|
||||
|
||||
try {
|
||||
await MonitoringAPI.updateHostAgentConfig(hostId, { commandsEnabled: enabled });
|
||||
notificationStore.success(`AI command execution ${enabled ? 'enabled' : 'disabled'}. Syncing with agent...`);
|
||||
notificationStore.success(`Pulse command execution ${enabled ? 'enabled' : 'disabled'}. Syncing with agent...`);
|
||||
} catch (err) {
|
||||
// On error, clear the pending state so toggle reverts
|
||||
setPendingCommandConfig(prev => {
|
||||
@@ -816,6 +819,24 @@ export const UnifiedAgents: Component = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-900 dark:border-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-100">
|
||||
<div class="flex items-start gap-3">
|
||||
<ProxmoxIcon class="w-5 h-5 text-orange-500 mt-0.5 shrink-0" />
|
||||
<div class="flex-1">
|
||||
<p class="text-sm">
|
||||
Proxmox nodes can be added here with the unified agent for extra capabilities like temperature monitoring and Pulse Patrol automation (auto-creates the API token and links the node).
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate('/settings/proxmox')}
|
||||
class="mt-2 text-xs font-medium text-emerald-800 hover:text-emerald-900 dark:text-emerald-200 dark:hover:text-emerald-100 underline"
|
||||
>
|
||||
Prefer API-only? Use manual setup →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5">
|
||||
<Show when={requiresToken()}>
|
||||
<div class="space-y-3">
|
||||
@@ -962,11 +983,11 @@ export const UnifiedAgents: Component = () => {
|
||||
onChange={(e) => setEnableCommands(e.currentTarget.checked)}
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
/>
|
||||
Enable Pulse command execution (for AI auto-fix)
|
||||
Enable Pulse command execution (for Patrol auto-fix)
|
||||
</label>
|
||||
<Show when={enableCommands()}>
|
||||
<div class="rounded-lg border border-blue-200 bg-blue-50 px-4 py-2 text-sm text-blue-800 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-200">
|
||||
<span class="font-medium">Pulse commands enabled</span> — The agent will accept diagnostic and fix commands from Pulse AI features.
|
||||
<span class="font-medium">Pulse commands enabled</span> — The agent will accept diagnostic and fix commands from Pulse Patrol features.
|
||||
</div>
|
||||
</Show>
|
||||
<div class="rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-2 text-sm text-emerald-900 dark:border-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-100">
|
||||
@@ -1249,7 +1270,7 @@ export const UnifiedAgents: Component = () => {
|
||||
<option value="all">All scopes</option>
|
||||
<option value="default">Default</option>
|
||||
<option value="profile">Profile assigned</option>
|
||||
<option value="ai-managed">AI-managed</option>
|
||||
<option value="ai-managed">Patrol-managed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="min-w-[220px] flex-1 space-y-1">
|
||||
@@ -1446,8 +1467,8 @@ export const UnifiedAgents: Component = () => {
|
||||
title={isPending
|
||||
? 'Syncing with agent...'
|
||||
: effectiveEnabled
|
||||
? 'AI command execution enabled'
|
||||
: 'AI command execution disabled'
|
||||
? 'Pulse command execution enabled'
|
||||
: 'Pulse command execution disabled'
|
||||
}
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -18,9 +18,9 @@ export const FeaturesStep: Component<FeaturesStepProps> = (props) => {
|
||||
const features = [
|
||||
{
|
||||
id: 'ai',
|
||||
name: 'Pulse AI',
|
||||
name: 'Pulse Assistant',
|
||||
icon: '🤖',
|
||||
desc: 'Intelligent monitoring assistant with auto-fix capabilities',
|
||||
desc: 'Guided troubleshooting with Patrol automation and auto-fix capabilities',
|
||||
enabled: aiEnabled,
|
||||
setEnabled: setAiEnabled,
|
||||
badge: 'New in 5.0',
|
||||
@@ -97,12 +97,12 @@ export const FeaturesStep: Component<FeaturesStepProps> = (props) => {
|
||||
</button>
|
||||
))}
|
||||
|
||||
{/* AI info box */}
|
||||
{/* Assistant info box */}
|
||||
<div class="bg-gradient-to-r from-purple-500/10 to-blue-500/10 border border-purple-400/20 rounded-xl p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="text-2xl">✨</div>
|
||||
<div>
|
||||
<p class="text-white font-medium">Pulse AI Features</p>
|
||||
<p class="text-white font-medium">Pulse Assistant & Patrol Features</p>
|
||||
<p class="text-white/60 text-sm mt-1">
|
||||
• Chat assistant for infrastructure questions<br />
|
||||
• Patrol mode for proactive monitoring<br />
|
||||
@@ -110,7 +110,7 @@ export const FeaturesStep: Component<FeaturesStepProps> = (props) => {
|
||||
• Predictive failure detection
|
||||
</p>
|
||||
<p class="text-white/40 text-xs mt-2">
|
||||
Requires API key configuration in Settings → AI after setup
|
||||
Requires API key configuration in Settings → Pulse Assistant after setup
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -754,7 +754,7 @@ const Storage: Component = () => {
|
||||
</svg>
|
||||
}
|
||||
title="No storage configured"
|
||||
description="Add a Proxmox VE or PBS node in the Settings tab to start monitoring storage."
|
||||
description="Install the Pulse agent for extra capabilities (temperature monitoring and Pulse Patrol automation), or add a node via API token in Settings → Proxmox."
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface UnifiedFinding {
|
||||
resourceName: string;
|
||||
resourceType: string;
|
||||
alertId?: string;
|
||||
alertType?: string;
|
||||
isThreshold?: boolean;
|
||||
category: string;
|
||||
severity: 'critical' | 'warning' | 'info' | 'watch';
|
||||
|
||||
@@ -167,13 +167,13 @@ func GetTierDisplayName(tier Tier) string {
|
||||
func GetFeatureDisplayName(feature string) string {
|
||||
switch feature {
|
||||
case FeatureAIPatrol:
|
||||
return "AI Patrol (Background Health Checks)"
|
||||
return "Pulse Patrol (Background Health Checks)"
|
||||
case FeatureAIAlerts:
|
||||
return "AI Alert Analysis"
|
||||
return "Alert Analysis"
|
||||
case FeatureAIAutoFix:
|
||||
return "AI Auto-Fix"
|
||||
return "Pulse Patrol Auto-Fix"
|
||||
case FeatureKubernetesAI:
|
||||
return "Kubernetes AI Analysis"
|
||||
return "Kubernetes Analysis"
|
||||
case FeatureUpdateAlerts:
|
||||
return "Update Alerts (Container/Package Updates)"
|
||||
case FeatureRBAC:
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestLicenseHasFeature(t *testing.T) {
|
||||
|
||||
// Should have tier features
|
||||
if !license.HasFeature(FeatureAIPatrol) {
|
||||
t.Error("Pro license should have AI Patrol")
|
||||
t.Error("Pro license should have Pulse Patrol")
|
||||
}
|
||||
|
||||
// Should have explicit features
|
||||
@@ -211,7 +211,7 @@ func TestServiceFeatureGating(t *testing.T) {
|
||||
|
||||
// Should now have Pro features
|
||||
if !service.HasFeature(FeatureAIPatrol) {
|
||||
t.Error("Should have AI Patrol with Pro license")
|
||||
t.Error("Should have Pulse Patrol with Pro license")
|
||||
}
|
||||
if !service.IsValid() {
|
||||
t.Error("Should be valid with active license")
|
||||
@@ -356,8 +356,8 @@ func TestGetTierDisplayName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFeatureDisplayName(t *testing.T) {
|
||||
if GetFeatureDisplayName(FeatureAIPatrol) != "AI Patrol (Background Health Checks)" {
|
||||
t.Error("Wrong display name for AI Patrol")
|
||||
if GetFeatureDisplayName(FeatureAIPatrol) != "Pulse Patrol (Background Health Checks)" {
|
||||
t.Error("Wrong display name for Pulse Patrol")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -948,7 +949,7 @@ type ResourceConvertInput struct {
|
||||
LastSeenUnix int64
|
||||
Alerts []ResourceAlertInput
|
||||
Identity *ResourceIdentityInput
|
||||
PlatformData map[string]any
|
||||
PlatformData json.RawMessage
|
||||
}
|
||||
|
||||
// ResourceMetricInput represents a metric value for resource conversion.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package models
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// Frontend-friendly type aliases with proper JSON tags
|
||||
// These extend the base types with additional computed fields
|
||||
|
||||
@@ -612,7 +614,7 @@ type ResourceFrontend struct {
|
||||
Identity *ResourceIdentityFrontend `json:"identity,omitempty"`
|
||||
|
||||
// Platform-specific data (JSON blob)
|
||||
PlatformData map[string]any `json:"platformData,omitempty"`
|
||||
PlatformData json.RawMessage `json:"platformData,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceMetricFrontend represents a metric value for the frontend.
|
||||
|
||||
BIN
pulse-check
Executable file
BIN
pulse-check
Executable file
Binary file not shown.
Reference in New Issue
Block a user