feat(ui): show anomalies in AI Intelligence Summary table
Adds real-time anomaly detection results to the AI Overview Table: - Anomalies appear at TOP of list (before predictions) since they're real-time - Severity-based color coding (critical=red, high=orange, medium=amber, low=blue) - Shows resource name, metric, and deviation ratio (e.g., 'CPU at 2.5x baseline') - Subtitle shows current vs baseline values - Timestamp shows 'Now' since anomalies are current state This integrates the FREE anomaly detection feature directly alongside the Pro patrol insights, providing immediate value to all users.
This commit is contained in:
parent
420e4960dc
commit
0730f857a4
1 changed files with 41 additions and 3 deletions
|
|
@ -1,12 +1,12 @@
|
||||||
import { Component, createEffect, createSignal, For, Show } from 'solid-js';
|
import { Component, createEffect, createSignal, For, Show } from 'solid-js';
|
||||||
import { AIAPI } from '@/api/ai';
|
import { AIAPI } from '@/api/ai';
|
||||||
import type { FailurePrediction, InfrastructureChange, RemediationRecord, RemediationStats, ResourceCorrelation } from '@/types/aiIntelligence';
|
import type { FailurePrediction, InfrastructureChange, RemediationRecord, RemediationStats, ResourceCorrelation, AnomalyReport } from '@/types/aiIntelligence';
|
||||||
|
|
||||||
const DEFAULT_UPGRADE_URL = 'https://pulsemonitor.app/pro';
|
const DEFAULT_UPGRADE_URL = 'https://pulsemonitor.app/pro';
|
||||||
|
|
||||||
interface InsightRow {
|
interface InsightRow {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'prediction' | 'impact' | 'memory';
|
type: 'prediction' | 'impact' | 'memory' | 'anomaly';
|
||||||
typeBadge: string;
|
typeBadge: string;
|
||||||
typeBadgeClass: string;
|
typeBadgeClass: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -27,6 +27,7 @@ export const AIOverviewTable: Component<{ showWhenEmpty?: boolean }> = (props) =
|
||||||
const [remediations, setRemediations] = createSignal<RemediationRecord[]>([]);
|
const [remediations, setRemediations] = createSignal<RemediationRecord[]>([]);
|
||||||
const [remediationStats, setRemediationStats] = createSignal<RemediationStats | null>(null);
|
const [remediationStats, setRemediationStats] = createSignal<RemediationStats | null>(null);
|
||||||
const [changes, setChanges] = createSignal<InfrastructureChange[]>([]);
|
const [changes, setChanges] = createSignal<InfrastructureChange[]>([]);
|
||||||
|
const [anomalies, setAnomalies] = createSignal<AnomalyReport[]>([]);
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
const [error, setError] = createSignal('');
|
const [error, setError] = createSignal('');
|
||||||
|
|
||||||
|
|
@ -46,13 +47,17 @@ export const AIOverviewTable: Component<{ showWhenEmpty?: boolean }> = (props) =
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
const [predResp, corrResp, remResp, changesResp] = await Promise.all([
|
const [predResp, corrResp, remResp, changesResp, anomalyResp] = await Promise.all([
|
||||||
AIAPI.getPredictions(),
|
AIAPI.getPredictions(),
|
||||||
AIAPI.getCorrelations(),
|
AIAPI.getCorrelations(),
|
||||||
AIAPI.getRemediations({ hours: 168, limit: 6 }),
|
AIAPI.getRemediations({ hours: 168, limit: 6 }),
|
||||||
AIAPI.getRecentChanges(24),
|
AIAPI.getRecentChanges(24),
|
||||||
|
AIAPI.getAnomalies(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Handle anomalies (FREE - no license required)
|
||||||
|
setAnomalies(anomalyResp.anomalies || []);
|
||||||
|
|
||||||
// Handle insights lock
|
// Handle insights lock
|
||||||
const insightsLockedState = Boolean(predResp.license_required || corrResp.license_required);
|
const insightsLockedState = Boolean(predResp.license_required || corrResp.license_required);
|
||||||
setInsightsLocked(insightsLockedState);
|
setInsightsLocked(insightsLockedState);
|
||||||
|
|
@ -128,6 +133,39 @@ export const AIOverviewTable: Component<{ showWhenEmpty?: boolean }> = (props) =
|
||||||
const unifiedRows = (): InsightRow[] => {
|
const unifiedRows = (): InsightRow[] => {
|
||||||
const rows: InsightRow[] = [];
|
const rows: InsightRow[] = [];
|
||||||
|
|
||||||
|
// Anomalies (FREE - show at top since they're real-time)
|
||||||
|
for (const anomaly of anomalies()) {
|
||||||
|
const severityClasses: Record<string, string> = {
|
||||||
|
critical: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
|
||||||
|
high: 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300',
|
||||||
|
medium: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
|
||||||
|
low: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
|
||||||
|
};
|
||||||
|
const badgeColors: Record<string, string> = {
|
||||||
|
critical: 'text-red-600 dark:text-red-400',
|
||||||
|
high: 'text-orange-600 dark:text-orange-400',
|
||||||
|
medium: 'text-amber-600 dark:text-amber-400',
|
||||||
|
low: 'text-blue-600 dark:text-blue-400',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format the deviation ratio
|
||||||
|
const ratio = anomaly.baseline_mean > 0
|
||||||
|
? (anomaly.current_value / anomaly.baseline_mean).toFixed(1)
|
||||||
|
: 'N/A';
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
id: `anomaly-${anomaly.resource_id}-${anomaly.metric}`,
|
||||||
|
type: 'anomaly',
|
||||||
|
typeBadge: `${anomaly.severity.charAt(0).toUpperCase() + anomaly.severity.slice(1)} Anomaly`,
|
||||||
|
typeBadgeClass: severityClasses[anomaly.severity] || severityClasses.low,
|
||||||
|
title: `${anomaly.resource_name || anomaly.resource_id}: ${anomaly.metric.toUpperCase()} at ${ratio}x baseline`,
|
||||||
|
subtitle: anomaly.description || `Current: ${anomaly.current_value.toFixed(1)}%, Baseline: ${anomaly.baseline_mean.toFixed(1)}%`,
|
||||||
|
timestamp: 'Now',
|
||||||
|
locked: false,
|
||||||
|
badgeClass: badgeColors[anomaly.severity] || badgeColors.low,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Predictions
|
// Predictions
|
||||||
for (const pred of predictions()) {
|
for (const pred of predictions()) {
|
||||||
const colorClass = pred.is_overdue || pred.days_until < 0
|
const colorClass = pred.is_overdue || pred.days_until < 0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue