feat: 日志列表

This commit is contained in:
tbphp
2025-07-12 11:22:13 +08:00
parent 25d65b0a94
commit a340deaf1b
5 changed files with 52 additions and 118 deletions

View File

@@ -1,14 +1,14 @@
import type { Group, LogFilter, LogsResponse } from "@/types/models";
import type { ApiResponse, Group, LogFilter, LogsResponse } from "@/types/models";
import http from "@/utils/http";
export const logApi = {
// 获取日志列表
getLogs: (params: LogFilter): Promise<LogsResponse> => {
getLogs: (params: LogFilter): Promise<ApiResponse<LogsResponse>> => {
return http.get("/logs", { params });
},
// 获取分组列表(用于筛选)
getGroups: (): Promise<Group[]> => {
getGroups: (): Promise<ApiResponse<Group[]>> => {
return http.get("/groups");
},
};

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { keysApi } from "@/api/keys";
import type { APIKey, Group, KeyStatus } from "@/types/models";
import { getGroupDisplayName } from "@/utils/display";
import { getGroupDisplayName, maskKey } from "@/utils/display";
import {
AddCircleOutline,
AlertCircleOutline,
@@ -142,13 +142,6 @@ async function loadKeys() {
}
}
function maskKey(key: string): string {
if (key.length <= 8) {
return key;
}
return `${key.substring(0, 4)}...${key.substring(key.length - 4)}`;
}
function copyKey(key: KeyRow) {
navigator.clipboard
.writeText(key.key_value)

View File

@@ -1,3 +1,10 @@
// 通用 API 响应结构
export interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 密钥状态
export type KeyStatus = "active" | "invalid" | undefined;
@@ -69,32 +76,47 @@ export interface TaskInfo {
};
}
// Based on backend response
export interface RequestLog {
id: string;
timestamp: string;
group_id: number;
key_id: number;
is_success: boolean;
source_ip: string;
status_code: number;
request_path: string;
request_body_snippet: string;
duration_ms: number;
error_message: string;
user_agent: string;
retries: number;
group_name?: string;
key_value?: string;
}
export interface Pagination {
page: number;
page_size: number;
total_items: number;
total_pages: number;
}
export interface LogsResponse {
total: number;
page: number;
size: number;
data: RequestLog[];
items: RequestLog[];
pagination: Pagination;
}
export interface LogFilter {
page: number;
size: number;
group_id?: number;
start_time?: string;
end_time?: string;
status_code?: number;
page?: number;
page_size?: number;
group_name?: string;
key_value?: string;
is_success?: boolean | null;
status_code?: number | null;
source_ip?: string;
error_contains?: string;
start_time?: string | null;
end_time?: string | null;
}
export interface DashboardStats {

View File

@@ -37,3 +37,15 @@ export function formatDisplayName(name: string): string {
export function getGroupDisplayName(group: Group): string {
return group.display_name || formatDisplayName(group.name);
}
/**
* Masks a long key string for display.
* @param key The key string.
* @returns The masked key.
*/
export function maskKey(key: string): string {
if (!key || key.length <= 8) {
return key || "";
}
return `${key.substring(0, 4)}...${key.substring(key.length - 4)}`;
}

View File

@@ -1,102 +1,9 @@
<script setup lang="ts">
import { NCard, NH3, NSpace, NTag, NText } from "naive-ui";
// 这里可以添加日志管理相关的逻辑
import LogTable from "@/components/logs/LogTable.vue";
</script>
<template>
<div class="logs-container">
<n-space vertical size="large">
<!-- 占位符内容 -->
<div class="content-placeholder">
<n-card :bordered="false" class="placeholder-card">
<n-space vertical align="center" size="large">
<div class="placeholder-icon">📋</div>
<n-h3 class="placeholder-title">日志管理功能</n-h3>
<n-text depth="2" class="placeholder-description">
此功能正在开发中将提供完整的系统日志查看和管理功能包括实时日志历史记录和日志分析
</n-text>
<n-space wrap size="medium" class="placeholder-features">
<n-tag type="info" size="medium">📝 实时日志流</n-tag>
<n-tag type="info" size="medium">🔍 日志搜索过滤</n-tag>
<n-tag type="info" size="medium">📈 错误统计分析</n-tag>
<n-tag type="info" size="medium">💾 日志导出功能</n-tag>
</n-space>
</n-space>
</n-card>
</div>
</n-space>
<div>
<log-table />
</div>
</template>
<style scoped>
.page-header-card {
background: rgba(255, 255, 255, 0.98);
border-radius: var(--border-radius-lg);
border: 1px solid rgba(255, 255, 255, 0.3);
animation: fadeInUp 0.2s ease-out;
}
.page-title {
font-size: 2.25rem;
font-weight: 700;
margin: 0;
letter-spacing: -0.5px;
}
.page-subtitle {
font-size: 1.1rem;
font-weight: 500;
}
.content-placeholder {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
animation: fadeInUp 0.2s ease-out 0.1s both;
}
.placeholder-card {
text-align: center;
max-width: 500px;
padding: 48px 32px;
background: rgba(255, 255, 255, 0.98);
border-radius: var(--border-radius-lg);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.placeholder-icon {
font-size: 4rem;
display: block;
}
.placeholder-title {
font-size: 1.5rem;
font-weight: 600;
margin: 0;
}
.placeholder-description {
font-size: 1rem;
line-height: 1.6;
text-align: center;
max-width: 400px;
}
.placeholder-features {
justify-content: center;
margin-top: 8px;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>