mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 08:36:37 +08:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # AGENTS.md # backend/docs/docs.go # backend/docs/swagger.json # backend/docs/swagger.yaml # frontend/components/providers/title-updater.tsx # frontend/messages/fragments/admin.en.json # frontend/messages/fragments/admin.zh-CN.json # frontend/proxy.ts
This commit is contained in:
@@ -66,12 +66,15 @@ interface TopUserData {
|
||||
count: number;
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
count: {
|
||||
label: '请求量',
|
||||
color: 'hsl(var(--primary))',
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
function useAnalyticsChartConfig() {
|
||||
const t = useTranslations('admin.logs.analytics');
|
||||
return {
|
||||
count: {
|
||||
label: t('requestVolume'),
|
||||
color: 'hsl(var(--primary))',
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
}
|
||||
|
||||
// Format YYYY-MM-DD to MM/DD
|
||||
function formatDateLabel(dateStr: string) {
|
||||
@@ -85,6 +88,7 @@ function formatDateLabel(dateStr: string) {
|
||||
|
||||
export function AccessAnalytics() {
|
||||
const t = useTranslations('admin.logs.analytics');
|
||||
const chartConfig = useAnalyticsChartConfig();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [clickhouseDisabled, setClickhouseDisabled] = useState(false);
|
||||
@@ -111,7 +115,7 @@ export function AccessAnalytics() {
|
||||
setClickhouseDisabled(false);
|
||||
} catch (err) {
|
||||
const errorInstance =
|
||||
err instanceof Error ? err : new Error('获取数据统计失败');
|
||||
err instanceof Error ? err : new Error(t('fetchFailed'));
|
||||
const errMsg = errorInstance.message || '';
|
||||
if (errMsg.includes('ClickHouse') || errMsg.includes('未启用')) {
|
||||
setClickhouseDisabled(true);
|
||||
@@ -121,7 +125,7 @@ export function AccessAnalytics() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAnalytics();
|
||||
@@ -290,7 +294,7 @@ export function AccessAnalytics() {
|
||||
strokeWidth={2}
|
||||
fillOpacity={1}
|
||||
fill='url(#colorCount)'
|
||||
name='请求次数'
|
||||
name={t('requestCount')}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
@@ -338,8 +342,10 @@ export function AccessAnalytics() {
|
||||
<span className='font-semibold'>{item.browser}</span>
|
||||
</span>
|
||||
<span className='text-muted-foreground font-mono text-xs'>
|
||||
{item.count.toLocaleString()} 次 ({percent.toFixed(1)}
|
||||
%)
|
||||
{t('countTimes', {
|
||||
count: item.count.toLocaleString(),
|
||||
percent: percent.toFixed(1),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className='h-2 w-full rounded-full bg-muted overflow-hidden'>
|
||||
|
||||
@@ -186,12 +186,12 @@ export function AccessLogs() {
|
||||
|
||||
const copyToClipboard = (text: string, subject: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
toast.success(`${subject}已复制到剪贴板`);
|
||||
toast.success(t('copiedToClipboard', { subject }));
|
||||
};
|
||||
|
||||
// Prettify Headers JSON string
|
||||
const getPrettyHeaders = (headersRaw?: string) => {
|
||||
if (!headersRaw) return '暂无头部数据';
|
||||
if (!headersRaw) return t('noHeaderData');
|
||||
try {
|
||||
const parsed = JSON.parse(headersRaw);
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
@@ -417,7 +417,7 @@ export function AccessLogs() {
|
||||
{t('accessLogDetail')}
|
||||
</SheetTitle>
|
||||
<SheetDescription className='text-xs'>
|
||||
访问 ID: {selectedLog?.id}
|
||||
{t('accessId', { id: selectedLog?.id ?? '' })}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
@@ -505,7 +505,7 @@ export function AccessLogs() {
|
||||
onClick={() =>
|
||||
copyToClipboard(selectedLog.user_agent, 'User-Agent')
|
||||
}
|
||||
title='复制 User-Agent'
|
||||
title={t('copyUserAgent')}
|
||||
>
|
||||
<Copy className='size-3' />
|
||||
</Button>
|
||||
@@ -531,7 +531,7 @@ export function AccessLogs() {
|
||||
'Headers',
|
||||
)
|
||||
}
|
||||
title='复制 Headers'
|
||||
title={t('copyHeaders')}
|
||||
>
|
||||
<Copy className='size-3' />
|
||||
</Button>
|
||||
|
||||
@@ -174,13 +174,16 @@ export function AppLogs() {
|
||||
}, [logs, autoScroll]);
|
||||
|
||||
// ---- Data fetching ---------------------------------------------------
|
||||
const fetchLogs = useCallback(async (cursor: number = 0) => {
|
||||
try {
|
||||
return await services.adminLog.getLogs(cursor);
|
||||
} catch (err) {
|
||||
throw err instanceof Error ? err : new Error('获取日志失败');
|
||||
}
|
||||
}, []);
|
||||
const fetchLogs = useCallback(
|
||||
async (cursor: number = 0) => {
|
||||
try {
|
||||
return await services.adminLog.getLogs(cursor);
|
||||
} catch (err) {
|
||||
throw err instanceof Error ? err : new Error(t('fetchFailed'));
|
||||
}
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
const loadHistory = useCallback(
|
||||
async (cursor: number = 0) => {
|
||||
@@ -211,16 +214,16 @@ export function AppLogs() {
|
||||
setNextCursor(data.next_cursor);
|
||||
} catch (err) {
|
||||
if (isInitial) {
|
||||
setError(err instanceof Error ? err : new Error('获取日志失败'));
|
||||
setError(err instanceof Error ? err : new Error(t('fetchFailed')));
|
||||
} else {
|
||||
toast.error('加载更早日志失败');
|
||||
toast.error(t('loadOlderFailed'));
|
||||
}
|
||||
} finally {
|
||||
if (isInitial) setLoading(false);
|
||||
else setLoadingMore(false);
|
||||
}
|
||||
},
|
||||
[fetchLogs],
|
||||
[fetchLogs, t],
|
||||
);
|
||||
|
||||
// ---- WebSocket -------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user