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:
rcourtman
2026-01-24 22:55:55 +00:00
parent 480492454f
commit 7f759e6fd2
6 changed files with 46 additions and 281 deletions

View File

@@ -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[];
}

View File

@@ -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.',
},
};

View File

@@ -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]];
}

View File

@@ -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,
};
}

View File

@@ -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;
}
}

View File

@@ -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;
}