onOpenChange(false)}
+ />
+ )}
+
+
+
+
+ {getBackAction() && (
+
+ )}
+
+ {/* 面包屑导航 */}
+
+
+
+ {selectedFile && (
+ <>
+
+
+
+
+ >
+ )}
+
+
+
+
+
+ {isLoading ? (
+
+ ) : selectedFile ? (
+ <>
+ {groupByReqId && groupedLogs && !selectedReqId ? (
+ // 显示日志组列表
+
+
+
{t('log_viewer.request_groups')}
+
+ {t('log_viewer.total_requests')}: {groupedLogs.summary.totalRequests} |
+ {t('log_viewer.total_logs')}: {groupedLogs.summary.totalLogs}
+
+
+
+ {groupedLogs.summary.requests.map((request) => (
+
selectReqId(request.reqId)}
+ >
+
+
+
+ {request.reqId}
+
+
+ {request.logCount} {t('log_viewer.logs')}
+
+
+
+
{t('log_viewer.first_log')}: {formatDate(request.firstLog)}
+
{t('log_viewer.last_log')}: {formatDate(request.lastLog)}
+
+
+ ))}
+
+
+ ) : (
+ // 显示日志内容
+
+ )}
+ >
+ ) : (
+
+
{t('log_viewer.select_file')}
+ {logFiles.length === 0 ? (
+
+
+
{t('log_viewer.no_log_files_available')}
+
+ ) : (
+
+ {logFiles.map((file) => (
+
selectFile(file)}
+ >
+
+
+
{formatFileSize(file.size)}
+
{formatDate(file.lastModified)}
+
+
+ ))}
+
+ )}
+
+ )}
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts
index 70f3314..9090741 100644
--- a/ui/src/lib/api.ts
+++ b/ui/src/lib/api.ts
@@ -1,5 +1,21 @@
import type { Config, Provider, Transformer } from '@/types';
+// 日志聚合响应类型
+interface GroupedLogsResponse {
+ grouped: boolean;
+ groups: { [reqId: string]: Array<{ timestamp: string; level: string; message: string; source?: string; reqId?: string }> };
+ summary: {
+ totalRequests: number;
+ totalLogs: number;
+ requests: Array<{
+ reqId: string;
+ logCount: number;
+ firstLog: string;
+ lastLog: string;
+ }>;
+ };
+}
+
// API Client Class for handling requests with baseUrl and apikey authentication
class ApiClient {
private baseUrl: string;
@@ -204,6 +220,21 @@ class ApiClient {
async performUpdate(): Promise<{ success: boolean; message: string }> {
return this.post<{ success: boolean; message: string }>('/api/update/perform', {});
}
+
+ // Get log files list
+ async getLogFiles(): Promise
> {
+ return this.get>('/logs/files');
+ }
+
+ // Get logs from specific file
+ async getLogs(filePath: string): Promise> {
+ return this.get>(`/logs?file=${encodeURIComponent(filePath)}`);
+ }
+
+ // Clear logs from specific file
+ async clearLogs(filePath: string): Promise {
+ return this.delete(`/logs?file=${encodeURIComponent(filePath)}`);
+ }
}
// Create a default instance of the API client
diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json
index ca3316a..201cb56 100644
--- a/ui/src/locales/en.json
+++ b/ui/src/locales/en.json
@@ -193,5 +193,36 @@
"template_download_success": "Template downloaded successfully",
"template_download_success_desc": "Configuration template has been downloaded to your device",
"template_download_failed": "Failed to download template"
+ },
+ "log_viewer": {
+ "title": "Log Viewer",
+ "close": "Close",
+ "download": "Download",
+ "clear": "Clear",
+ "auto_refresh_on": "Auto Refresh On",
+ "auto_refresh_off": "Auto Refresh Off",
+ "load_failed": "Failed to load logs",
+ "no_logs_available": "No logs available",
+ "logs_cleared": "Logs cleared successfully",
+ "clear_failed": "Failed to clear logs",
+ "logs_downloaded": "Logs downloaded successfully",
+ "back_to_files": "Back to Files",
+ "select_file": "Select a log file to view",
+ "no_log_files_available": "No log files available",
+ "load_files_failed": "Failed to load log files",
+ "group_by_req_id": "Group by Request ID",
+ "grouped_on": "Grouped",
+ "request_groups": "Request Groups",
+ "total_requests": "Total Requests",
+ "total_logs": "Total Logs",
+ "request": "Request",
+ "logs": "logs",
+ "first_log": "First Log",
+ "last_log": "Last Log",
+ "back_to_all_logs": "Back to All Logs",
+ "worker_error": "Worker error",
+ "worker_init_failed": "Failed to initialize worker",
+ "grouping_not_supported": "Log grouping not supported by server",
+ "back": "Back"
}
}
diff --git a/ui/src/locales/zh.json b/ui/src/locales/zh.json
index e28346c..0f994bb 100644
--- a/ui/src/locales/zh.json
+++ b/ui/src/locales/zh.json
@@ -193,5 +193,36 @@
"template_download_success": "模板下载成功",
"template_download_success_desc": "配置模板已下载到您的设备",
"template_download_failed": "模板下载失败"
+ },
+ "log_viewer": {
+ "title": "日志查看器",
+ "close": "关闭",
+ "download": "下载",
+ "clear": "清除",
+ "auto_refresh_on": "自动刷新开启",
+ "auto_refresh_off": "自动刷新关闭",
+ "load_failed": "加载日志失败",
+ "no_logs_available": "暂无日志",
+ "logs_cleared": "日志清除成功",
+ "clear_failed": "清除日志失败",
+ "logs_downloaded": "日志下载成功",
+ "back_to_files": "返回文件列表",
+ "select_file": "选择要查看的日志文件",
+ "no_log_files_available": "暂无日志文件",
+ "load_files_failed": "加载日志文件失败",
+ "group_by_req_id": "按请求ID分组",
+ "grouped_on": "已分组",
+ "request_groups": "请求组",
+ "total_requests": "总请求数",
+ "total_logs": "总日志数",
+ "request": "请求",
+ "logs": "条日志",
+ "first_log": "首条日志",
+ "last_log": "末条日志",
+ "back_to_all_logs": "返回所有日志",
+ "worker_error": "Worker错误",
+ "worker_init_failed": "Worker初始化失败",
+ "grouping_not_supported": "服务器不支持日志分组",
+ "back": "返回"
}
}
diff --git a/ui/tsconfig.tsbuildinfo b/ui/tsconfig.tsbuildinfo
index 862370a..78adc38 100644
--- a/ui/tsconfig.tsbuildinfo
+++ b/ui/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/jsoneditor.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/statuslineconfigdialog.tsx","./src/components/statuslineimportexport.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/color-picker.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/utils.ts","./src/utils/statusline.ts"],"version":"5.8.3"}
\ No newline at end of file
+{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/jsoneditor.tsx","./src/components/logviewer.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/statuslineconfigdialog.tsx","./src/components/statuslineimportexport.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/color-picker.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/utils.ts","./src/utils/statusline.ts"],"version":"5.8.3"}
\ No newline at end of file