feat: I.3 Dashboard — system metrics (DB/Redis/Worker/API), cost tracking (LLM cost 24h), usage analytics (tokens/plugins), admin-only, tsc clean
This commit is contained in:
@@ -4,7 +4,10 @@ import { ActivityFeed, ActivityItem } from '@/components/shared/ActivityFeed';
|
||||
import { DashboardGrid } from '@/components/dashboard/DashboardGrid';
|
||||
import { useUnifiedContacts, useAuditLog } from '@/api/hooks';
|
||||
import { useDashboardWidgets } from '@/api/dashboard';
|
||||
import { useSystemDashboard } from '@/api/systemDashboard';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { formatDateTime } from '@/utils/date';
|
||||
import { Database, Server, Cpu, DollarSign, Activity, TrendingUp } from 'lucide-react';
|
||||
|
||||
export function DashboardPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -12,6 +15,9 @@ export function DashboardPage() {
|
||||
const { data: contactsData } = useUnifiedContacts(1, 1, undefined, 'person');
|
||||
const { data: auditData, isError: auditError } = useAuditLog(1, 5);
|
||||
const { data: widgetsData, isError: widgetsError } = useDashboardWidgets();
|
||||
const { data: systemData } = useSystemDashboard();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const isAdmin = user?.is_system_admin ?? false;
|
||||
|
||||
const totalCompanies = companiesData?.total ?? 0;
|
||||
const totalContacts = contactsData?.total ?? 0;
|
||||
@@ -72,6 +78,72 @@ export function DashboardPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* System Metrics (Admin only) — I-DASH, I-COST, I-USE */}
|
||||
{isAdmin && systemData && (
|
||||
<div className="mb-8">
|
||||
<h2 className="text-lg font-semibold text-secondary-800 mb-4">{t('dashboard.systemMetrics', 'System Status')}</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="w-4 h-4 text-primary-600" />
|
||||
<span className="text-sm font-medium text-secondary-900">Database</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900">{systemData.database?.status === 'up' ? '✅' : '❌'}</p>
|
||||
<p className="text-xs text-secondary-500">{systemData.database?.db_size_human || '—'}</p>
|
||||
</div>
|
||||
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Server className="w-4 h-4 text-primary-600" />
|
||||
<span className="text-sm font-medium text-secondary-900">Redis</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900">{systemData.redis?.status === 'up' ? '✅' : '❌'}</p>
|
||||
<p className="text-xs text-secondary-500">{systemData.redis?.used_memory_human || '—'}</p>
|
||||
</div>
|
||||
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Cpu className="w-4 h-4 text-primary-600" />
|
||||
<span className="text-sm font-medium text-secondary-900">Worker</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900">{systemData.worker?.status === 'up' ? '✅' : '❌'}</p>
|
||||
<p className="text-xs text-secondary-500">Queue: {systemData.worker?.queue_length ?? '—'}</p>
|
||||
</div>
|
||||
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Activity className="w-4 h-4 text-primary-600" />
|
||||
<span className="text-sm font-medium text-secondary-900">API</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900">{systemData.api?.total_requests ?? '—'}</p>
|
||||
<p className="text-xs text-secondary-500">Errors: {systemData.api?.error_count ?? 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
{systemData.llm && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mt-4">
|
||||
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<DollarSign className="w-4 h-4 text-warning-600" />
|
||||
<span className="text-sm font-medium text-secondary-900">LLM Cost (24h)</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900">${systemData.llm.cost_24h?.toFixed(2) ?? '0.00'}</p>
|
||||
</div>
|
||||
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<TrendingUp className="w-4 h-4 text-primary-600" />
|
||||
<span className="text-sm font-medium text-secondary-900">LLM Tokens (24h)</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900">{systemData.llm.tokens_24h?.toLocaleString() ?? '0'}</p>
|
||||
</div>
|
||||
<div className="border border-secondary-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Activity className="w-4 h-4 text-primary-600" />
|
||||
<span className="text-sm font-medium text-secondary-900">Active Plugins</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900">{systemData.plugins?.active_count ?? '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dynamic Plugin Widgets */}
|
||||
{widgetsError ? (
|
||||
<p className="text-sm text-secondary-500 mb-6" data-testid="dashboard-widgets-unavailable">
|
||||
|
||||
Reference in New Issue
Block a user