mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
chore: remove unused frontend code
Remove unused utility files: - utils/textWidth.ts - not imported anywhere - utils/extractErrorMessage.ts - not imported anywhere Remove unused exports: - utils/canvasRenderQueue.ts: getRenderQueueStats() - hooks/useBreakpoint.ts: getVisibilityClass(), getPriorityMinWidth() - api/ai.ts: getForecast(), getForecastOverview(), related types - api/patrol.ts: categoryLabels, autonomyLevelLabels (kept used exports)
This commit is contained in:
@@ -16,59 +16,6 @@ import type {
|
||||
LearningStatusResponse,
|
||||
} from '@/types/aiIntelligence';
|
||||
|
||||
const toNumber = (value: unknown, fallback = 0) => {
|
||||
if (typeof value === 'number' && !Number.isNaN(value)) return value;
|
||||
if (typeof value === 'string') {
|
||||
const num = Number(value);
|
||||
if (!Number.isNaN(num)) return num;
|
||||
}
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const normalizeForecastResponse = (data: any): ForecastResponse => {
|
||||
const raw = data?.forecast ?? data;
|
||||
if (!raw) {
|
||||
throw new Error('No forecast data available');
|
||||
}
|
||||
|
||||
const trendDirection = typeof raw.trend === 'string' ? raw.trend : raw.trend?.direction;
|
||||
const trend =
|
||||
trendDirection === 'increasing' || trendDirection === 'decreasing' || trendDirection === 'stable'
|
||||
? trendDirection
|
||||
: 'stable';
|
||||
|
||||
const currentValue = toNumber(raw.current_value ?? raw.currentValue, 0);
|
||||
const predictedValue = toNumber(raw.predicted_value ?? raw.predictedValue, currentValue);
|
||||
const startTime = Date.now();
|
||||
const endTime = raw.predicted_at ? new Date(raw.predicted_at).getTime() : startTime;
|
||||
const points = 12;
|
||||
const stepMs = points > 1 ? (endTime - startTime) / (points - 1) : 0;
|
||||
const confidence = Math.min(Math.max(toNumber(raw.confidence, 0), 0), 1);
|
||||
const baseSpread = Math.max(Math.abs(predictedValue - currentValue) * (1 - confidence + 0.15), Math.abs(currentValue) * 0.05);
|
||||
|
||||
const predictions: ForecastPrediction[] = [];
|
||||
for (let i = 0; i < points; i += 1) {
|
||||
const ratio = points > 1 ? i / (points - 1) : 1;
|
||||
const value = currentValue + (predictedValue - currentValue) * ratio;
|
||||
const spread = baseSpread * (0.6 + ratio * 0.8);
|
||||
predictions.push({
|
||||
timestamp: new Date(startTime + stepMs * i).toISOString(),
|
||||
value,
|
||||
lower_bound: Math.max(0, value - spread),
|
||||
upper_bound: value + spread,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
resource_id: raw.resource_id ?? raw.resourceId ?? '',
|
||||
metric: raw.metric ?? '',
|
||||
predictions,
|
||||
confidence,
|
||||
trend,
|
||||
message: raw.description ?? raw.message,
|
||||
};
|
||||
};
|
||||
|
||||
export class AIAPI {
|
||||
private static baseUrl = '/api';
|
||||
|
||||
@@ -541,33 +488,6 @@ export class AIAPI {
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Phase 7: Event-Driven Intelligence API
|
||||
// ============================================
|
||||
|
||||
// Get forecast for a metric
|
||||
static async getForecast(options: { resourceId: string; metric: string; horizonHours?: number }): Promise<ForecastResponse> {
|
||||
const params = new URLSearchParams();
|
||||
params.set('resource_id', options.resourceId);
|
||||
params.set('metric', options.metric);
|
||||
if (options.horizonHours) params.set('horizon_hours', String(options.horizonHours));
|
||||
const data = await apiFetchJSON(`${this.baseUrl}/ai/forecast?${params.toString()}`);
|
||||
return normalizeForecastResponse(data);
|
||||
}
|
||||
|
||||
// Get forecast overview for all resources sorted by urgency
|
||||
static async getForecastOverview(params: {
|
||||
metric: string;
|
||||
horizonHours?: number;
|
||||
threshold?: number;
|
||||
}): Promise<ForecastOverviewResponse> {
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set('metric', params.metric);
|
||||
if (params.horizonHours) urlParams.set('horizon_hours', String(params.horizonHours));
|
||||
if (params.threshold) urlParams.set('threshold', String(params.threshold));
|
||||
return apiFetchJSON(`${this.baseUrl}/ai/forecasts/overview?${urlParams.toString()}`) as Promise<ForecastOverviewResponse>;
|
||||
}
|
||||
|
||||
// Remediation plans
|
||||
static async getRemediationPlans(): Promise<RemediationPlansResponse> {
|
||||
const data = await apiFetchJSON(`${this.baseUrl}/ai/remediation/plans`) as { plans?: RemediationPlan[]; executions?: unknown[] };
|
||||
@@ -658,42 +578,6 @@ export interface UnifiedFindingsResponse {
|
||||
active_count?: number;
|
||||
}
|
||||
|
||||
export interface ForecastResponse {
|
||||
resource_id: string;
|
||||
metric: string;
|
||||
predictions: ForecastPrediction[];
|
||||
confidence: number;
|
||||
trend: 'increasing' | 'decreasing' | 'stable';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface ForecastPrediction {
|
||||
timestamp: string;
|
||||
value: number;
|
||||
lower_bound: number;
|
||||
upper_bound: number;
|
||||
}
|
||||
|
||||
export interface ForecastOverviewItem {
|
||||
resource_id: string;
|
||||
resource_name: string;
|
||||
resource_type: 'node' | 'vm' | 'lxc';
|
||||
metric: string;
|
||||
current_value: number;
|
||||
predicted_value: number;
|
||||
time_to_threshold: number | null; // seconds, null if won't breach
|
||||
confidence: number;
|
||||
trend: 'increasing' | 'decreasing' | 'stable' | 'volatile';
|
||||
}
|
||||
|
||||
export interface ForecastOverviewResponse {
|
||||
forecasts: ForecastOverviewItem[];
|
||||
metric: string;
|
||||
threshold: number;
|
||||
horizon_hours: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface RemediationPlansResponse {
|
||||
plans: RemediationPlan[];
|
||||
}
|
||||
|
||||
@@ -166,66 +166,6 @@ export async function dismissFinding(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve and execute a proposed fix
|
||||
* @param approvalId The approval ID from the investigation
|
||||
*/
|
||||
export async function approveFix(approvalId: string): Promise<{ success: boolean; message: string; output?: string }> {
|
||||
return apiFetchJSON(`/api/ai/approvals/${approvalId}/approve`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deny/skip a proposed fix
|
||||
* @param approvalId The approval ID from the investigation
|
||||
*/
|
||||
export async function denyFix(approvalId: string): Promise<{ success: boolean; message: string }> {
|
||||
return apiFetchJSON(`/api/ai/approvals/${approvalId}/deny`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Severity color mapping for UI
|
||||
*/
|
||||
export const severityColors: Record<FindingSeverity, { bg: string; text: string; border: string }> = {
|
||||
critical: { bg: 'rgba(220, 38, 38, 0.15)', text: '#ef4444', border: 'rgba(220, 38, 38, 0.3)' },
|
||||
warning: { bg: 'rgba(234, 179, 8, 0.15)', text: '#eab308', border: 'rgba(234, 179, 8, 0.3)' },
|
||||
watch: { bg: 'rgba(59, 130, 246, 0.15)', text: '#3b82f6', border: 'rgba(59, 130, 246, 0.3)' },
|
||||
info: { bg: 'rgba(107, 114, 128, 0.15)', text: '#9ca3af', border: 'rgba(107, 114, 128, 0.3)' },
|
||||
};
|
||||
|
||||
/**
|
||||
* Category labels for UI
|
||||
*/
|
||||
export const categoryLabels: Record<FindingCategory, string> = {
|
||||
performance: 'Performance',
|
||||
capacity: 'Capacity',
|
||||
reliability: 'Reliability',
|
||||
backup: 'Backup',
|
||||
security: 'Security',
|
||||
general: 'General',
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a timestamp for display
|
||||
*/
|
||||
export function formatTimestamp(ts: string): string {
|
||||
const date = new Date(ts);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
|
||||
if (diffMins < 1) return 'just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Patrol Autonomy APIs
|
||||
// =============================================================================
|
||||
@@ -270,6 +210,52 @@ export async function reinvestigateFinding(findingId: string): Promise<{ success
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve and execute a proposed fix
|
||||
*/
|
||||
export async function approveFix(approvalId: string): Promise<{ success: boolean; message: string; output?: string }> {
|
||||
return apiFetchJSON(`/api/ai/approvals/${approvalId}/approve`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deny/skip a proposed fix
|
||||
*/
|
||||
export async function denyFix(approvalId: string): Promise<{ success: boolean; message: string }> {
|
||||
return apiFetchJSON(`/api/ai/approvals/${approvalId}/deny`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Severity color mapping for UI
|
||||
*/
|
||||
export const severityColors: Record<FindingSeverity, { bg: string; text: string; border: string }> = {
|
||||
critical: { bg: 'rgba(220, 38, 38, 0.15)', text: '#ef4444', border: 'rgba(220, 38, 38, 0.3)' },
|
||||
warning: { bg: 'rgba(234, 179, 8, 0.15)', text: '#eab308', border: 'rgba(234, 179, 8, 0.3)' },
|
||||
watch: { bg: 'rgba(59, 130, 246, 0.15)', text: '#3b82f6', border: 'rgba(59, 130, 246, 0.3)' },
|
||||
info: { bg: 'rgba(107, 114, 128, 0.15)', text: '#9ca3af', border: 'rgba(107, 114, 128, 0.3)' },
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a timestamp for display
|
||||
*/
|
||||
export function formatTimestamp(ts: string): string {
|
||||
const date = new Date(ts);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
|
||||
if (diffMins < 1) return 'just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Investigation status labels for UI
|
||||
*/
|
||||
@@ -292,21 +278,3 @@ export const investigationOutcomeLabels: Record<InvestigationOutcome, string> =
|
||||
needs_attention: 'Needs Attention',
|
||||
cannot_fix: 'Cannot Auto-Fix',
|
||||
};
|
||||
|
||||
/**
|
||||
* Autonomy level labels for UI
|
||||
*/
|
||||
export const autonomyLevelLabels: Record<PatrolAutonomyLevel, { label: string; description: string }> = {
|
||||
monitor: {
|
||||
label: 'Monitor Only',
|
||||
description: 'Detect issues and create findings. No automatic investigation.',
|
||||
},
|
||||
approval: {
|
||||
label: 'Investigate with Approval',
|
||||
description: 'Automatically investigate findings. Queue fixes for your approval.',
|
||||
},
|
||||
full: {
|
||||
label: 'Full Autonomy',
|
||||
description: 'Automatically investigate and apply non-critical fixes. Critical fixes still require approval.',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -141,21 +141,3 @@ export function useBreakpoint(): UseBreakpointReturn {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tailwind CSS class for hiding an element below a given breakpoint
|
||||
*/
|
||||
export function getVisibilityClass(priority: ColumnPriority, display: 'flex' | 'block' | 'table-cell' | 'grid' | 'inline' = 'flex'): string {
|
||||
const bp = PRIORITY_BREAKPOINTS[priority];
|
||||
if (bp === 'xs') {
|
||||
// Always visible, no hiding class needed
|
||||
return display;
|
||||
}
|
||||
return `hidden ${bp}:${display}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum width for a priority level
|
||||
*/
|
||||
export function getPriorityMinWidth(priority: ColumnPriority): number {
|
||||
return BREAKPOINTS[PRIORITY_BREAKPOINTS[priority]];
|
||||
}
|
||||
|
||||
@@ -51,19 +51,3 @@ export function scheduleSparkline(draw: () => void): () => void {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get render queue stats for debugging
|
||||
*/
|
||||
export function getRenderQueueStats() {
|
||||
return {
|
||||
pendingCount: pending.size,
|
||||
rafScheduled: rafId !== null,
|
||||
};
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// Expose for debugging in dev mode
|
||||
(window as any).__canvasRenderQueue = {
|
||||
getStats: getRenderQueueStats,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Extract a user-friendly error message from an API response.
|
||||
* Handles various response formats including JSON with error/message fields.
|
||||
*
|
||||
* @param response - The Response object from fetch
|
||||
* @param defaultMessage - Default message if extraction fails
|
||||
* @returns The extracted error message
|
||||
*/
|
||||
export async function extractErrorMessage(
|
||||
response: Response,
|
||||
defaultMessage?: string
|
||||
): Promise<string> {
|
||||
const fallback = defaultMessage || `Failed with status ${response.status}`;
|
||||
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text?.trim()) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// Try to parse as JSON first
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
// Check for common error field names
|
||||
if (typeof parsed?.error === 'string' && parsed.error.trim()) {
|
||||
return parsed.error.trim();
|
||||
}
|
||||
if (typeof parsed?.message === 'string' && parsed.message.trim()) {
|
||||
return parsed.message.trim();
|
||||
}
|
||||
} catch {
|
||||
// Not JSON, fall through to use raw text
|
||||
}
|
||||
|
||||
// Use raw text if it's non-empty
|
||||
return text.trim();
|
||||
} catch {
|
||||
// Body read failed
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Estimate text width based on character count.
|
||||
* Uses an approximation for 10px font size (~5.5-6px per character).
|
||||
*
|
||||
* @param text - The text to estimate width for
|
||||
* @param charWidth - Average character width (default: 5.5)
|
||||
* @param padding - Additional padding to add (default: 8)
|
||||
* @returns Estimated width in pixels
|
||||
*/
|
||||
export function estimateTextWidth(text: string, charWidth = 5.5, padding = 8): number {
|
||||
return text.length * charWidth + padding;
|
||||
}
|
||||
Reference in New Issue
Block a user