// // Copyright (c) 2025 rustmailer.com (https://rustmailer.com) // // This file is part of the Bichon Email Archiving Project // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program.  If not, see . import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Skeleton } from '@/components/ui/skeleton'; import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar } from 'recharts'; import { Mail, HardDrive, Database, Users, Inbox, Info } from 'lucide-react'; import { formatBytes, formatNumber } from '@/lib/utils'; import { useQuery } from '@tanstack/react-query'; import { get_dashboard_stats, TimeBucket } from '@/api/system/api'; import { Main } from '@/components/layout/main'; import { FixedHeader } from '@/components/layout/fixed-header'; import { useTranslation } from 'react-i18next'; interface DailyActivity { date: string; count: number; } function convertRecentActivity(timeBuckets: TimeBucket[], locale: string): DailyActivity[] { const dateFormatter = new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric', }); return timeBuckets.map(bucket => { const date = new Date(bucket.timestamp_ms); return { date: dateFormatter.format(date), count: bucket.count, timestamp_ms: bucket.timestamp_ms, }; }); } const formatTooltipDate = (timestamp_ms: number, locale: string): string => { const date = new Date(timestamp_ms); return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', day: 'numeric', }).format(date); }; const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444']; // Skeleton Components const MetricCardSkeleton = () => ( ); const ChartSkeleton = () => (
{[...Array(6)].map((_, i) => ( ))}
); // Empty State Components const EmptyChart = ({ title }: { title: string }) => (

{title}

); const EmptyTable = ({ title }: { title: string }) => (

{title}

); export default function MailArchiveDashboard() { const { data: stats, isLoading } = useQuery({ queryKey: ['dashboard-stats'], queryFn: get_dashboard_stats, }); const { t, i18n } = useTranslation(); const currentLocale = i18n.resolvedLanguage || i18n.language || navigator.language; const totalAttachments = (stats?.with_attachment_count ?? 0) + (stats?.without_attachment_count ?? 0); const attachmentRatio = totalAttachments > 0 ? (stats?.with_attachment_count ?? 0) / totalAttachments : 0; const hasRecentActivity = stats?.recent_activity && stats.recent_activity.length > 0; const hasTopSenders = stats?.top_senders && stats.top_senders.length > 0; const hasTopEmails = stats?.top_largest_emails && stats.top_largest_emails.length > 0; const hasTopAccounts = stats?.top_accounts && stats.top_accounts.length > 0; const attachmentData = totalAttachments > 0 ? [ { name: 'With Attachments', value: attachmentRatio, fill: COLORS[1] }, { name: 'No Attachments', value: 1 - attachmentRatio, fill: '#e5e7eb' }, ] : [ { name: 'No Data', value: 1, fill: '#e5e7eb' }, ]; if (isLoading) { return (
{[...Array(6)].map((_, i) => ( ))}
); } return ( <>

{t('dashboard.title')}

{t('dashboard.mailAccounts')}
{formatNumber(stats!.account_count)}

{t('dashboard.connected')}

{t('dashboard.totalEmails')}
{formatNumber(stats!.email_count)}

{t('dashboard.syncedLocally')}

{t('dashboard.totalEmailSize')}
{formatBytes(stats!.total_size_bytes)}

{t('dashboard.logicalVolume')}

{t('dashboard.localDataFiles')}
{formatBytes(stats!.storage_usage_bytes)}

{t('dashboard.actualDiskUsage')}

{t('dashboard.indexSize')}
{formatBytes(stats!.index_usage_bytes)}

{t('dashboard.tantivyIndex')}

{t('dashboard.systemVersion')}
{stats!.system_version ? ( {stats!.system_version} ) : 'N/A'}

{stats!.commit_hash ?? 'N/A'}

{t('dashboard.dayTrend')} {t('dashboard.attachments')} {t('dashboard.topLists')} {t('dashboard.newEmails')} {t('dashboard.messageDistribution')} {hasRecentActivity ? ( formatNumber(v as number)} content={({ payload }) => { if (payload && payload.length) { const dataPoint = payload[0].payload; const fullDate = formatTooltipDate(dataPoint.timestamp_ms, currentLocale); return (

{fullDate}

{t('dashboard.emails')}: {formatNumber(dataPoint.count)}

); } return null; }} contentStyle={{ backgroundColor: 'hsl(var(--background))', border: '1px solid hsl(var(--border))', borderRadius: '8px' }} />
) : ( )}
{/* Attachments */} {t('dashboard.attachmentRatio')} {totalAttachments > 0 ? t('dashboard.attachmentRatioDesc', { percent: (attachmentRatio * 100).toFixed(1) }) : t('dashboard.noEmailsSynced')} {attachmentData.map((entry, i) => ( ))} totalAttachments > 0 ? `${((v as number) * 100).toFixed(1)}%` : '0%'} />
{totalAttachments > 0 ? ( <>
{t('dashboard.withAttachments')} ({(attachmentRatio * 100).toFixed(1)}%)
{t('dashboard.noAttachments')}
) : (
{t('common.noData')}
)}
{t('dashboard.top10Senders')} {t('dashboard.byMessageCount')} {hasTopSenders ? ( {t('dashboard.sender')} {t('dashboard.count')} {stats!.top_senders.map((s) => ( {s.key} {formatNumber(s.count)} ))}
) : ( )}
{t('dashboard.top10LargestEmails')} {t('dashboard.byMessageSize')} {hasTopEmails ? ( {t('dashboard.subject')} {t('dashboard.size')} {stats!.top_largest_emails.map((m, index) => ( {m.subject || t('dashboard.noSubject')} {formatBytes(m.size_bytes)} ))}
) : ( )}
{t('dashboard.top10Accounts')} {t('dashboard.byMessageCount')} {hasTopAccounts ? ( {t('dashboard.account')} {t('dashboard.emails')} {stats!.top_accounts.map((acc) => ( {acc.key} {formatNumber(acc.count)} ))}
) : ( )}
{/* Footer / Copyright - New Addition */}
© 2025 rustmailer.com - Bichon Email Archiving Project
); }