feat(M4): System-Rueckbau — Dashboard-Inhalte als MiniApps, Core = reiner Host (#362)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- system_miniapps.py: audit_activity (audit:read, settings max_items) + system_metrics (settings:read) als Core-Apps in Registry - base.py-Fix: native Manifest-MiniApps reichen component durch (M1-Luecke) - contacts-Manifest: contacts_stats (ContactsStatsWidget, contacts:read, show_companies/show_persons) - Seed-Fix: nur renderbare Apps (component) landen im Dashboard-Layout - Frontend: ContactsStatsWidget, AuditActivityWidget, SystemMetricsWidget; MiniAppHost-Registry +3 - Dashboard.tsx = reiner Host (26 Z.); Page-Tests auf Pure-Host umgeschrieben - Tests: M4 7/7 (TDD rot->gruen), Backend-Regression 46/46, Vitest 22/22, tsc clean, build OK
This commit is contained in:
@@ -1,157 +1,24 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StatCard } from '@/components/shared/StatCard';
|
||||
import { ActivityFeed, ActivityItem } from '@/components/shared/ActivityFeed';
|
||||
import { DashboardBuilder } from '@/components/dashboard/DashboardBuilder';
|
||||
import { useUnifiedContacts, useAuditLog } from '@/api/hooks';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Dashboard page — pure host (Phase M4).
|
||||
*
|
||||
* All content (stat cards, activity feed, system metrics, plugin widgets)
|
||||
* now lives in personal MiniApp instances managed by the builder. The
|
||||
* former hard-coded blocks were migrated to widgets in M4:
|
||||
* - StatCards -> ContactsStatsWidget (contacts plugin, contacts:read)
|
||||
* - ActivityFeed -> AuditActivityWidget (system, audit:read)
|
||||
* - System Metrics -> SystemMetricsWidget (system, settings:read)
|
||||
*/
|
||||
|
||||
export function DashboardPage() {
|
||||
const { t } = useTranslation();
|
||||
const { data: companiesData } = useUnifiedContacts(1, 1, undefined, 'company');
|
||||
const { data: contactsData } = useUnifiedContacts(1, 1, undefined, 'person');
|
||||
const { data: auditData, isError: auditError } = useAuditLog(1, 5);
|
||||
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;
|
||||
|
||||
const auditEntries = auditData?.items ?? [];
|
||||
const activeThisWeek = auditEntries.filter((e) => {
|
||||
if (!e.timestamp) return false;
|
||||
const d = new Date(e.timestamp);
|
||||
const weekAgo = new Date();
|
||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||
return d >= weekAgo;
|
||||
}).length;
|
||||
|
||||
const newThisMonth = auditEntries.filter((e) => {
|
||||
if (!e.timestamp) return false;
|
||||
const d = new Date(e.timestamp);
|
||||
const monthAgo = new Date();
|
||||
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
||||
return d >= monthAgo;
|
||||
}).length;
|
||||
|
||||
const activities: ActivityItem[] = auditError
|
||||
? []
|
||||
: auditEntries.map((entry) => ({
|
||||
id: `${entry.timestamp}-${entry.user}-${entry.action}`,
|
||||
user: entry.user || 'System',
|
||||
action: entry.action || '',
|
||||
time: entry.timestamp ? (formatDateTime(entry.timestamp) || '') : '',
|
||||
avatarUrl: null,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-7xl mx-auto" data-testid="dashboard-page">
|
||||
<h1 className="text-2xl font-bold text-secondary-900 mb-6">{t('dashboard.title')}</h1>
|
||||
|
||||
{/* Personal dashboard builder (Phase M3) — replaces the old plugin widget grid */}
|
||||
<div className="mb-10">
|
||||
<DashboardBuilder />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<StatCard
|
||||
label={t('dashboard.totalCompanies')}
|
||||
value={totalCompanies}
|
||||
testId="stat-companies"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('dashboard.totalContacts')}
|
||||
value={totalContacts}
|
||||
testId="stat-contacts"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('dashboard.activeThisWeek')}
|
||||
value={activeThisWeek}
|
||||
testId="stat-active-week"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('dashboard.newThisMonth')}
|
||||
value={newThisMonth}
|
||||
testId="stat-new-month"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* System Metrics (Admin only) — I-DASH, I-COST, I-USE (M4: becomes system MiniApp) */}
|
||||
{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.last_24h_cost?.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.last_24h_tokens?.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_plugins?.length ?? '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{auditError ? (
|
||||
<p className="text-sm text-secondary-500" data-testid="activity-unavailable">
|
||||
{t('dashboard.activityUnavailable')}
|
||||
</p>
|
||||
) : (
|
||||
<ActivityFeed activities={activities} title={t('dashboard.recentActivity')} />
|
||||
)}
|
||||
<DashboardBuilder />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user