feat(migrations): Rename indexes and update global integration settings fields for improved clarity and functionality

feat(admin): Add API monitoring, debug console, and system health templates for enhanced admin interface

docs: Add AI system cleanup summary and audit report detailing architecture, token management, and recommendations

docs: Introduce credits and tokens system guide outlining configuration, data flow, and monitoring strategies
This commit is contained in:
IGNY8 VPS (Salman)
2025-12-20 12:55:05 +00:00
parent eb6cba7920
commit 3283a83b42
51 changed files with 3578 additions and 5434 deletions

View File

@@ -1,31 +0,0 @@
import { Navigate } from 'react-router-dom';
import { useAuthStore } from '../../store/authStore';
interface AwsAdminGuardProps {
children: React.ReactNode;
}
/**
* Route guard that only allows access to users of the aws-admin account
* Used for the single remaining admin dashboard page
*/
export const AwsAdminGuard: React.FC<AwsAdminGuardProps> = ({ children }) => {
const { user, loading } = useAuthStore();
if (loading) {
return (
<div className="flex items-center justify-center h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
</div>
);
}
// Check if user belongs to aws-admin account
const isAwsAdmin = user?.account?.slug === 'aws-admin';
if (!isAwsAdmin) {
return <Navigate to="/dashboard" replace />;
}
return <>{children}</>;
};

View File

@@ -32,14 +32,6 @@ interface ImageQueueModalProps {
model?: string;
provider?: string;
onUpdateQueue?: (queue: ImageQueueItem[]) => void;
onLog?: (log: {
timestamp: string;
type: 'request' | 'success' | 'error' | 'step';
action: string;
data: any;
stepName?: string;
percentage?: number;
}) => void;
}
export default function ImageQueueModal({
@@ -51,7 +43,6 @@ export default function ImageQueueModal({
model,
provider,
onUpdateQueue,
onLog,
}: ImageQueueModalProps) {
const [localQueue, setLocalQueue] = useState<ImageQueueItem[]>(queue);
// Track smooth progress animation for each item
@@ -250,43 +241,6 @@ export default function ImageQueueModal({
console.log(`[ImageQueueModal] Task completed with state:`, taskState);
clearInterval(pollInterval);
// Log completion status
if (onLog) {
if (taskState === 'SUCCESS') {
const result = data.result || (data.meta && data.meta.result);
const completed = result?.completed || 0;
const failed = result?.failed || 0;
const total = result?.total_images || totalImages;
onLog({
timestamp: new Date().toISOString(),
type: failed > 0 ? 'error' : 'success',
action: 'generate_images',
stepName: 'Task Completed',
data: {
state: 'SUCCESS',
completed,
failed,
total,
results: result?.results || []
}
});
} else {
// FAILURE
onLog({
timestamp: new Date().toISOString(),
type: 'error',
action: 'generate_images',
stepName: 'Task Failed',
data: {
state: 'FAILURE',
error: data.error || data.meta?.error || 'Task failed',
meta: data.meta
}
});
}
}
// Update final state
if (taskState === 'SUCCESS' && data.result) {
console.log(`[ImageQueueModal] Updating queue from result:`, data.result);

View File

@@ -1,435 +0,0 @@
import { useState, useEffect, useRef } from 'react';
import { useAuthStore } from '../../store/authStore';
import { API_BASE_URL } from '../../services/api';
interface RequestMetrics {
request_id: string;
path: string;
method: string;
elapsed_time_ms: number;
cpu: {
user_time_ms: number;
system_time_ms: number;
total_time_ms: number;
system_percent: number;
};
memory: {
delta_bytes: number;
delta_mb: number;
final_rss_mb: number;
system_used_percent: number;
};
io: {
read_bytes: number;
read_mb: number;
write_bytes: number;
write_mb: number;
};
timestamp: number;
}
interface ResourceDebugOverlayProps {
enabled: boolean;
}
export default function ResourceDebugOverlay({ enabled }: ResourceDebugOverlayProps) {
const { user } = useAuthStore();
const [metrics, setMetrics] = useState<RequestMetrics[]>([]);
const [isVisible, setIsVisible] = useState(false);
const [pageLoadStart, setPageLoadStart] = useState<number | null>(null);
const requestIdRef = useRef<string | null>(null);
const metricsRef = useRef<RequestMetrics[]>([]);
const originalFetchRef = useRef<typeof fetch | null>(null);
const nativeFetchRef = useRef<typeof fetch | null>(null); // Store native fetch separately
// Check if user is admin/developer
const isAdminOrDeveloper = user?.role === 'admin' || user?.role === 'developer';
// Track page load start and intercept fetch requests
useEffect(() => {
if (!enabled || !isAdminOrDeveloper) {
// Restore native fetch if disabled
if (nativeFetchRef.current) {
window.fetch = nativeFetchRef.current;
nativeFetchRef.current = null;
originalFetchRef.current = null;
}
return;
}
setPageLoadStart(performance.now());
// Store native fetch and create bound version
if (!nativeFetchRef.current) {
nativeFetchRef.current = window.fetch; // Store actual native fetch
originalFetchRef.current = window.fetch.bind(window); // Create bound version for calling
}
// Intercept fetch requests to track API calls
window.fetch = async function(...args) {
const startTime = performance.now();
const [url, options = {}] = args;
// Don't intercept our own metrics fetch calls to avoid infinite loops
const urlString = typeof url === 'string' ? url : url.toString();
if (urlString.includes('/request-metrics/')) {
// Use native fetch directly for metrics calls
return nativeFetchRef.current!.apply(window, args as [RequestInfo | URL, RequestInit?]);
}
// Add debug header to enable tracking
const headers = new Headers(options.headers || {});
headers.set('X-Debug-Resource-Tracking', 'true');
// Use bound fetch to preserve context
const response = await originalFetchRef.current!(url, {
...options,
headers,
});
const endTime = performance.now();
// Get request ID from response header
const requestId = response.headers.get('X-Resource-Tracking-ID');
if (requestId) {
requestIdRef.current = requestId;
// Fetch metrics after a delay to ensure backend has stored them
// Use a slightly longer delay to avoid race conditions
setTimeout(() => fetchRequestMetrics(requestId), 300);
}
return response;
};
return () => {
// Restore native fetch on cleanup
if (nativeFetchRef.current) {
window.fetch = nativeFetchRef.current;
nativeFetchRef.current = null;
originalFetchRef.current = null;
}
};
}, [enabled, isAdminOrDeveloper]);
// Fetch metrics for a request - use fetchAPI to get proper authentication handling
const fetchRequestMetrics = async (requestId: string, retryCount = 0) => {
try {
// Use fetchAPI which handles token refresh and authentication properly
// But we need to use native fetch to avoid interception loop
const nativeFetch = nativeFetchRef.current || window.fetch;
const { token } = useAuthStore.getState();
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
// Add JWT token if available
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
// Silently handle 404s and other errors - metrics might not exist for all requests
try {
const response = await nativeFetch.call(window, `${API_BASE_URL}/v1/system/request-metrics/${requestId}/`, {
method: 'GET',
headers,
credentials: 'include', // Include session cookies for authentication
});
// Silently ignore 404s - metrics endpoint might not exist for all requests
if (response.status === 404) {
return; // Don't log or retry 404s
}
if (response.ok) {
const responseData = await response.json();
// Extract data from unified API response format: {success: true, data: {...}}
const data = responseData?.data || responseData;
// Only log in debug mode to reduce console noise
if (import.meta.env.DEV) {
console.debug('Fetched metrics for request:', requestId, data);
}
// Validate data structure before adding
if (data && typeof data === 'object' && data.request_id) {
metricsRef.current = [...metricsRef.current, data];
setMetrics([...metricsRef.current]);
}
} else if (response.status === 401) {
// Token might be expired - try to refresh and retry once
try {
await useAuthStore.getState().refreshToken();
const newToken = useAuthStore.getState().token;
if (newToken) {
const retryHeaders: HeadersInit = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${newToken}`,
};
const retryResponse = await nativeFetch.call(window, `${API_BASE_URL}/v1/system/request-metrics/${requestId}/`, {
method: 'GET',
headers: retryHeaders,
credentials: 'include',
});
if (retryResponse.ok) {
const responseData = await retryResponse.json();
// Extract data from unified API response format: {success: true, data: {...}}
const data = responseData?.data || responseData;
// Validate data structure before adding
if (data && typeof data === 'object' && data.request_id) {
metricsRef.current = [...metricsRef.current, data];
setMetrics([...metricsRef.current]);
}
return;
}
}
} catch (refreshError) {
// Refresh failed - silently ignore
}
// Silently ignore 401 errors - user might not be authenticated
} else if (response.status === 404) {
// Metrics not found - silently ignore (metrics might not exist for all requests)
return;
} else {
// Other errors - silently ignore
return;
}
} catch (error) {
// Silently ignore all fetch errors (network errors, etc.)
// Metrics are optional and not critical for functionality
return;
}
} catch (error) {
// Silently ignore all errors
return;
}
};
// Calculate page load time
const pageLoadTime = pageLoadStart ? performance.now() - pageLoadStart : null;
// Calculate totals - with null safety checks
const totals = metrics.reduce((acc, m) => {
// Safely access nested properties with defaults
const elapsed = m?.elapsed_time_ms || 0;
const cpuTotal = m?.cpu?.total_time_ms || 0;
const memoryDelta = m?.memory?.delta_mb || 0;
const ioRead = m?.io?.read_mb || 0;
const ioWrite = m?.io?.write_mb || 0;
return {
elapsed_time_ms: acc.elapsed_time_ms + elapsed,
cpu_total_ms: acc.cpu_total_ms + cpuTotal,
memory_delta_mb: acc.memory_delta_mb + memoryDelta,
io_read_mb: acc.io_read_mb + ioRead,
io_write_mb: acc.io_write_mb + ioWrite,
};
}, {
elapsed_time_ms: 0,
cpu_total_ms: 0,
memory_delta_mb: 0,
io_read_mb: 0,
io_write_mb: 0,
});
// Find the slowest request - with null safety
const slowestRequest = metrics.length > 0
? metrics.reduce((prev, current) => {
const prevTime = prev?.elapsed_time_ms || 0;
const currentTime = current?.elapsed_time_ms || 0;
return (currentTime > prevTime) ? current : prev;
})
: null;
// Find the request with highest CPU usage - with null safety
const highestCpuRequest = metrics.length > 0
? metrics.reduce((prev, current) => {
const prevCpu = prev?.cpu?.total_time_ms || 0;
const currentCpu = current?.cpu?.total_time_ms || 0;
return (currentCpu > prevCpu) ? current : prev;
})
: null;
// Find the request with highest memory usage - with null safety
const highestMemoryRequest = metrics.length > 0
? metrics.reduce((prev, current) => {
const prevMemory = prev?.memory?.delta_mb || 0;
const currentMemory = current?.memory?.delta_mb || 0;
return (currentMemory > prevMemory) ? current : prev;
})
: null;
if (!enabled || !isAdminOrDeveloper) return null;
return (
<>
{/* Toggle Button - Fixed position */}
<button
onClick={() => setIsVisible(!isVisible)}
className="fixed bottom-4 right-4 z-[99999] bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg shadow-lg text-sm font-medium flex items-center gap-2"
title="Toggle Resource Debug Overlay"
>
<span>🔍</span>
<span>Debug ({metrics.length})</span>
</button>
{/* Overlay */}
{isVisible && (
<div className="fixed bottom-20 right-4 z-[99998] bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg shadow-2xl w-[500px] max-h-[85vh] overflow-auto">
<div className="sticky top-0 bg-gray-100 dark:bg-gray-800 px-4 py-3 border-b border-gray-300 dark:border-gray-700 flex justify-between items-center">
<h3 className="font-semibold text-gray-900 dark:text-white">Resource Debug</h3>
<button
onClick={() => setIsVisible(false)}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
</button>
</div>
<div className="p-4 space-y-4">
{/* Page Load Summary */}
{pageLoadTime && (
<div className="bg-blue-50 dark:bg-blue-900/20 p-3 rounded border border-blue-200 dark:border-blue-800">
<h4 className="font-semibold text-blue-900 dark:text-blue-200 mb-2">Page Load Time</h4>
<div className="text-sm text-blue-800 dark:text-blue-300">
{pageLoadTime.toFixed(2)} ms
</div>
</div>
)}
{/* Performance Summary - Highlight Culprits */}
{metrics.length > 0 && (
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-3 rounded border border-yellow-200 dark:border-yellow-800">
<h4 className="font-semibold text-yellow-900 dark:text-yellow-200 mb-2"> Performance Culprits</h4>
<div className="text-xs space-y-2 text-yellow-800 dark:text-yellow-300">
{slowestRequest && (
<div>
<span className="font-semibold">Slowest Request:</span> {slowestRequest.method || 'N/A'} {slowestRequest.path || 'N/A'}
<br />
<span className="ml-4">Time: {(slowestRequest.elapsed_time_ms || 0).toFixed(2)} ms</span>
</div>
)}
{highestCpuRequest && highestCpuRequest.cpu && (highestCpuRequest.cpu.total_time_ms || 0) > 100 && (
<div>
<span className="font-semibold">Highest CPU:</span> {highestCpuRequest.method || 'N/A'} {highestCpuRequest.path || 'N/A'}
<br />
<span className="ml-4">CPU: {(highestCpuRequest.cpu.total_time_ms || 0).toFixed(2)} ms (System: {(highestCpuRequest.cpu.system_percent || 0).toFixed(1)}%)</span>
</div>
)}
{highestMemoryRequest && highestMemoryRequest.memory && (highestMemoryRequest.memory.delta_mb || 0) > 1 && (
<div>
<span className="font-semibold">Highest Memory:</span> {highestMemoryRequest.method || 'N/A'} {highestMemoryRequest.path || 'N/A'}
<br />
<span className="ml-4">Memory: {(highestMemoryRequest.memory.delta_mb || 0) > 0 ? '+' : ''}{(highestMemoryRequest.memory.delta_mb || 0).toFixed(2)} MB</span>
</div>
)}
</div>
</div>
)}
{/* Totals */}
{metrics.length > 0 && (
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded border border-gray-200 dark:border-gray-700">
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Request Totals</h4>
<div className="text-xs space-y-1 text-gray-700 dark:text-gray-300">
<div>Total Requests: {metrics.length}</div>
<div>Total Time: {totals.elapsed_time_ms.toFixed(2)} ms</div>
<div>Total CPU Time: {totals.cpu_total_ms.toFixed(2)} ms</div>
<div>Total Memory Delta: {totals.memory_delta_mb > 0 ? '+' : ''}{totals.memory_delta_mb.toFixed(2)} MB</div>
<div>Total I/O Read: {totals.io_read_mb.toFixed(2)} MB</div>
<div>Total I/O Write: {totals.io_write_mb.toFixed(2)} MB</div>
</div>
</div>
)}
{/* Individual Requests - Detailed View */}
<div className="space-y-2">
<h4 className="font-semibold text-gray-900 dark:text-white">All Requests (Detailed)</h4>
{metrics.length === 0 ? (
<div className="text-sm text-gray-500 dark:text-gray-400">
No requests tracked yet. Navigate to trigger API calls.
<br />
<span className="text-xs">Make sure debug toggle is enabled in header.</span>
</div>
) : (
metrics.map((m, idx) => {
// Safely access properties with defaults
const elapsedTime = m?.elapsed_time_ms || 0;
const cpuTotal = m?.cpu?.total_time_ms || 0;
const memoryDelta = m?.memory?.delta_mb || 0;
const isSlow = elapsedTime > 1000;
const isHighCpu = cpuTotal > 100;
const isHighMemory = memoryDelta > 1;
return (
<div
key={idx}
className={`p-3 rounded border text-xs ${
isSlow || isHighCpu || isHighMemory
? 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800'
: 'bg-gray-50 dark:bg-gray-800 border-gray-200 dark:border-gray-700'
}`}
>
<div className="font-semibold text-gray-900 dark:text-white mb-2 flex items-center gap-2">
<span>{m.method}</span>
<span className="text-gray-600 dark:text-gray-400 truncate">{m.path}</span>
{(isSlow || isHighCpu || isHighMemory) && (
<span className="text-red-600 dark:text-red-400 text-xs"></span>
)}
</div>
<div className="space-y-1 text-gray-700 dark:text-gray-300">
<div className={isSlow ? 'font-semibold text-red-700 dark:text-red-300' : ''}>
Time: {elapsedTime.toFixed(2)} ms
</div>
{m?.cpu && (
<div className={isHighCpu ? 'font-semibold text-red-700 dark:text-red-300' : ''}>
🔥 CPU: {cpuTotal.toFixed(2)} ms
<span className="text-gray-500"> (User: {(m.cpu.user_time_ms || 0).toFixed(2)}ms, System: {(m.cpu.system_time_ms || 0).toFixed(2)}ms)</span>
<br />
<span className="ml-4 text-gray-500">System CPU: {(m.cpu.system_percent || 0).toFixed(1)}%</span>
</div>
)}
{m?.memory && (
<div className={isHighMemory ? 'font-semibold text-red-700 dark:text-red-300' : ''}>
💾 Memory: {memoryDelta > 0 ? '+' : ''}{memoryDelta.toFixed(2)} MB
<span className="text-gray-500"> (Final RSS: {(m.memory.final_rss_mb || 0).toFixed(2)} MB)</span>
<br />
<span className="ml-4 text-gray-500">System Memory: {(m.memory.system_used_percent || 0).toFixed(1)}%</span>
</div>
)}
{m?.io?.read_mb > 0 && (
<div>
📖 I/O Read: {m.io.read_mb.toFixed(2)} MB ({(m.io.read_bytes || 0).toLocaleString()} bytes)
</div>
)}
{m?.io?.write_mb > 0 && (
<div>
📝 I/O Write: {m.io.write_mb.toFixed(2)} MB ({(m.io.write_bytes || 0).toLocaleString()} bytes)
</div>
)}
</div>
</div>
);
})
)}
</div>
{/* Clear Button */}
{metrics.length > 0 && (
<button
onClick={() => {
setMetrics([]);
metricsRef.current = [];
setPageLoadStart(performance.now());
}}
className="w-full bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-900 dark:text-white px-3 py-2 rounded text-sm"
>
Clear Metrics
</button>
)}
</div>
</div>
)}
</>
);
}

View File

@@ -1,42 +0,0 @@
import { useState, useEffect } from 'react';
import { useAuthStore } from '../../store/authStore';
export default function ResourceDebugToggle() {
const { user } = useAuthStore();
const [enabled, setEnabled] = useState(false);
const isAdminOrDeveloper = user?.role === 'admin' || user?.role === 'developer';
// Load saved state from localStorage
useEffect(() => {
if (isAdminOrDeveloper) {
const saved = localStorage.getItem('debug_resource_tracking_enabled');
setEnabled(saved === 'true');
}
}, [isAdminOrDeveloper]);
const toggle = () => {
const newValue = !enabled;
setEnabled(newValue);
localStorage.setItem('debug_resource_tracking_enabled', String(newValue));
// Dispatch event for overlay component
window.dispatchEvent(new CustomEvent('debug-resource-tracking-toggle', { detail: newValue }));
};
if (!isAdminOrDeveloper) return null;
return (
<button
onClick={toggle}
className={`flex items-center justify-center w-10 h-10 rounded-lg transition-colors ${
enabled
? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400'
: 'text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800'
}`}
title={enabled ? 'Disable Resource Debug' : 'Enable Resource Debug'}
>
🔍
</button>
);
}