码桶

发现社区成员的开源项目

Dashboard.jsx6.5 KB
import React, { useState, useEffect, useRef } from 'react';
import { Globe, Layers, User, Clock, Activity } from 'lucide-react';
import { ApiClient, ApiError } from '../utils/api.js';
import { useAuth } from '../contexts/AuthContext.jsx';
import { useTheme } from '../contexts/ThemeContext.jsx';

const Dashboard = ({ zones, dnsRecordCounts }) => {
    const { auth } = useAuth();
    const { t } = useTheme();
    const [auditLog, setAuditLog] = useState([]);
    const [auditLoading, setAuditLoading] = useState(false);
    const authRef = useRef(auth);
    authRef.current = auth;

    const apiRef = useRef(null);
    if (!apiRef.current) {
        apiRef.current = new ApiClient(() => authRef.current);
    }

    const zoneCount = zones.length;
    const accountCount = (() => {
        if (auth.mode === 'server') {
            const sessions = auth.sessions || [];
            let count = 0;
            for (const s of sessions) {
                count += (s.accounts || []).length || 1;
            }
            return count;
        }
        // Client/local mode: count local tokens
        const localTokens = JSON.parse(localStorage.getItem('local_cf_tokens') || '{}');
        return Math.max(Object.keys(localTokens).length, 1);
    })();

    const lastLogin = new Date().toLocaleString();

    // Fetch recent audit log entries for admin users (using ApiClient)
    useEffect(() => {
        if (auth.mode === 'server' && auth.role === 'admin') {
            setAuditLoading(true);
            apiRef.current.get('/api/admin/audit-log?limit=5')
                .then(data => {
                    setAuditLog(data.entries || data.logs || []);
                })
                .catch((err) => {
                    if (err instanceof ApiError) {
                        console.error(`Audit log fetch failed (${err.status}):`, err.message);
                    }
                    setAuditLog([]);
                })
                .finally(() => setAuditLoading(false));
        }
    }, [auth.mode, auth.role, auth.token]);

    const formatTime = (timestamp) => {
        if (!timestamp) return '';
        const date = new Date(timestamp);
        const now = new Date();
        const diff = now - date;
        if (diff < 60000) return t('dashboardJustNow');
        if (diff < 3600000) return `${Math.floor(diff / 60000)}m`;
        if (diff < 86400000) return `${Math.floor(diff / 3600000)}h`;
        return date.toLocaleDateString();
    };

    return (
        <div className="dashboard-stats fade-in">
            <div className="dashboard-grid">
                <div className="glass-card dashboard-card">
                    <div className="dashboard-card-icon" style={{ background: 'rgba(243, 128, 32, 0.1)' }}>
                        <Globe size={20} color="var(--primary)" />
                    </div>
                    <div className="dashboard-card-content">
                        <span className="dashboard-card-value">{zoneCount}</span>
                        <span className="dashboard-card-label">{t('dashboardZones')}</span>
                    </div>
                </div>

                <div className="glass-card dashboard-card">
                    <div className="dashboard-card-icon" style={{ background: 'var(--badge-blue-bg)' }}>
                        <Layers size={20} color="var(--badge-blue-text)" />
                    </div>
                    <div className="dashboard-card-content">
                        <span className="dashboard-card-value">{(() => { const total = zones.reduce((sum, z) => sum + (dnsRecordCounts?.[z.id] || 0), 0); return total > 0 ? total : '-'; })()}</span>
                        <span className="dashboard-card-label">{t('dashboardRecords')}</span>
                    </div>
                </div>

                <div className="glass-card dashboard-card">
                    <div className="dashboard-card-icon" style={{ background: 'var(--badge-green-bg)' }}>
                        <User size={20} color="var(--badge-green-text)" />
                    </div>
                    <div className="dashboard-card-content">
                        <span className="dashboard-card-value">{accountCount}</span>
                        <span className="dashboard-card-label">{t('dashboardAccounts')}</span>
                    </div>
                </div>

                <div className="glass-card dashboard-card">
                    <div className="dashboard-card-icon" style={{ background: 'var(--badge-orange-bg)' }}>
                        <Clock size={20} color="var(--badge-orange-text)" />
                    </div>
                    <div className="dashboard-card-content">
                        <span className="dashboard-card-value" style={{ fontSize: '0.85rem' }}>{lastLogin}</span>
                        <span className="dashboard-card-label">{t('dashboardLastLogin')}</span>
                    </div>
                </div>
            </div>

            {auth.mode === 'server' && auth.role === 'admin' && (
                <div className="glass-card dashboard-activity">
                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.75rem' }}>
                        <Activity size={16} color="var(--primary)" />
                        <h4 style={{ fontSize: '0.85rem', fontWeight: 600, margin: 0 }}>{t('dashboardRecentActivity')}</h4>
                    </div>
                    {auditLoading ? (
                        <p style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>...</p>
                    ) : auditLog.length === 0 ? (
                        <p style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{t('dashboardNoActivity')}</p>
                    ) : (
                        <div className="dashboard-activity-list">
                            {auditLog.slice(0, 5).map((entry, idx) => (
                                <div key={idx} className="dashboard-activity-item">
                                    <span className="dashboard-activity-time">{formatTime(entry.time || entry.timestamp)}</span>
                                    <span className="dashboard-activity-user">{entry.user || entry.username || '-'}</span>
                                    <span className="dashboard-activity-action">{entry.action || entry.type || '-'}</span>
                                    <span className="dashboard-activity-detail">{entry.detail || entry.details || ''}</span>
                                </div>
                            ))}
                        </div>
                    )}
                </div>
            )}
        </div>
    );
};

export default Dashboard;