import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { request as invoke } from '../../utils/request'; import { Activity, ShieldAlert, Users, Globe } from 'lucide-react'; import { formatCompactNumber } from '../../utils/format'; interface IpRanking { client_ip: string; request_count: number; last_seen: number; is_blocked: boolean; } interface IpStatsResponse { total_requests: number; unique_ips: number; blocked_requests: number; top_ips: IpRanking[]; } interface IpTokenStats { client_ip: string; total_tokens: number; input_tokens: number; output_tokens: number; request_count: number; username?: string; } interface Props { refreshKey?: number; } export const IpStatistics: React.FC = ({ refreshKey }) => { const { t } = useTranslation(); const [stats, setStats] = useState(null); const [tokenStats, setTokenStats] = useState([]); const [loading, setLoading] = useState(false); const [timeRange, setTimeRange] = useState(24); const loadStats = async () => { setLoading(true); try { const [statsData, tokenData] = await Promise.all([ invoke('get_ip_stats'), invoke('get_ip_token_stats', { limit: 20, hours: timeRange }) ]); setStats(statsData); setTokenStats(tokenData || []); } catch (e) { console.error('Failed to load stats', e); } finally { setLoading(false); } }; useEffect(() => { loadStats(); }, [timeRange, refreshKey]); const getTimeRangeLabel = () => { switch (timeRange) { case 1: return t('security.stats.hour'); case 24: return t('security.stats.day'); case 168: return t('security.stats.week'); case 720: return t('security.stats.month'); default: return `${timeRange} h`; } }; if (loading && !stats) { return
; } if (!stats) { return
{t('security.stats.no_data')}
; } const maxReqCount = Math.max(...tokenStats.map(ip => ip.request_count), 1); return (
{/* Overview Cards */}
{t('security.stats.total_requests')}
{formatCompactNumber(stats.total_requests)}
{t('security.stats.total_requests_desc')}
{t('security.stats.unique_ips')}
{formatCompactNumber(stats.unique_ips)}
{t('security.stats.unique_ips_desc')}
{t('security.stats.blocked_requests')}
{formatCompactNumber(stats.blocked_requests)}
{t('security.stats.blocked_requests_desc')}
{/* Combined IP Stats */}

{t('security.stats.ip_activity_token_usage')} ({getTimeRangeLabel()})

{tokenStats.map((ip, index) => { // Determine color based on usage magnitude let colorClass = "text-green-500"; if (ip.total_tokens > 1000000) colorClass = "text-red-500 font-bold"; else if (ip.total_tokens > 100000) colorClass = "text-yellow-500 font-bold"; else if (ip.total_tokens > 10000) colorClass = "text-blue-500"; const percentage = Math.min(100, Math.max(0, (ip.request_count / maxReqCount) * 100)) || 0; return ( ); })} {tokenStats.length === 0 && ( )}
{t('security.stats.rank')} {t('security.stats.ip_address')} {t('security.logs.username')} {t('security.stats.activity_reqs')} {t('security.stats.total_token')} {t('security.stats.prompt')} {t('security.stats.completion')}
#{index + 1} {ip.client_ip} {ip.username || '-'}
{formatCompactNumber(ip.request_count)} reqs {Math.round(percentage)}%
{formatCompactNumber(ip.total_tokens)} {formatCompactNumber(ip.input_tokens)} {formatCompactNumber(ip.output_tokens)}
{t('security.stats.no_data')}
); };