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:
ryan
2026-09-03 09:16:22 +08:00
1065 changed files with 271858 additions and 9600 deletions
@@ -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 -------------------------------------------------------