添加日志清理功能
This commit is contained in:
@@ -392,3 +392,61 @@ func (h *APIHandler) GetRequestLogStatsHandler(c *gin.Context) {
|
|||||||
"backend_model_stats": backendModelStats,
|
"backend_model_stats": backendModelStats,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearRequestLogsHandler 清空请求日志
|
||||||
|
func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) {
|
||||||
|
// 获取查询参数
|
||||||
|
olderThan := c.Query("older_than") // 清空多少天前的日志
|
||||||
|
|
||||||
|
var startTime time.Time
|
||||||
|
if olderThan != "" {
|
||||||
|
// 解析天数
|
||||||
|
var days int
|
||||||
|
if _, err := fmt.Sscanf(olderThan, "%d", &days); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid older_than parameter, must be a number representing days"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if days < 0 {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "older_than must be 0 or greater"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是0天,要删除所有日志,不需要时间限制
|
||||||
|
if days == 0 {
|
||||||
|
// 执行删除所有日志的操作
|
||||||
|
result := h.DB.Where("1 = 1").Delete(&models.RequestLog{})
|
||||||
|
if result.Error != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回删除的记录数
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "All logs cleared successfully",
|
||||||
|
"deleted_count": result.RowsAffected,
|
||||||
|
"older_than_days": olderThan,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = time.Now().AddDate(0, 0, -days)
|
||||||
|
} else {
|
||||||
|
// 如果没有指定天数,默认清空30天前的日志
|
||||||
|
startTime = time.Now().AddDate(0, 0, -30)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行删除操作
|
||||||
|
result := h.DB.Where("created_at < ?", startTime).Delete(&models.RequestLog{})
|
||||||
|
if result.Error != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回删除的记录数
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "Logs cleared successfully",
|
||||||
|
"deleted_count": result.RowsAffected,
|
||||||
|
"older_than_days": olderThan,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ func main() {
|
|||||||
api_.GET("/logs", handler.GetRequestLogsHandler)
|
api_.GET("/logs", handler.GetRequestLogsHandler)
|
||||||
api_.GET("/logs/stats", handler.GetRequestLogStatsHandler)
|
api_.GET("/logs/stats", handler.GetRequestLogStatsHandler)
|
||||||
api_.GET("/logs/:id", handler.GetRequestLogDetailHandler)
|
api_.GET("/logs/:id", handler.GetRequestLogDetailHandler)
|
||||||
|
api_.DELETE("/logs", handler.ClearRequestLogsHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动HTTP服务器
|
// 启动HTTP服务器
|
||||||
|
|||||||
@@ -31,4 +31,17 @@ export const getLogDetail = async (id) => {
|
|||||||
console.error('获取日志详情失败:', error);
|
console.error('获取日志详情失败:', error);
|
||||||
throw new Error(error.response?.data?.error || '获取日志详情失败');
|
throw new Error(error.response?.data?.error || '获取日志详情失败');
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清空日志
|
||||||
|
export const clearLogs = async (olderThanDays = 30) => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.delete('/api/logs', {
|
||||||
|
params: { older_than: olderThanDays }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清空日志失败:', error);
|
||||||
|
throw new Error(error.response?.data?.error || '清空日志失败');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { getRequestLogs, getRequestLogStats } from '../api';
|
import { getRequestLogs, getRequestLogStats, clearLogs } from '../api';
|
||||||
import RequestLogDetailModal from './RequestLogDetailModal';
|
import RequestLogDetailModal from './RequestLogDetailModal';
|
||||||
|
|
||||||
// 自定义防抖 Hook
|
// 自定义防抖 Hook
|
||||||
@@ -29,6 +29,9 @@ const RequestLogList = () => {
|
|||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
const [selectedLog, setSelectedLog] = useState(null);
|
const [selectedLog, setSelectedLog] = useState(null);
|
||||||
const [showDetailModal, setShowDetailModal] = useState(false);
|
const [showDetailModal, setShowDetailModal] = useState(false);
|
||||||
|
const [showClearModal, setShowClearModal] = useState(false);
|
||||||
|
const [clearingLogs, setClearingLogs] = useState(false);
|
||||||
|
const [clearDays, setClearDays] = useState(30);
|
||||||
|
|
||||||
// 筛选条件
|
// 筛选条件
|
||||||
const [filters, setFilters] = useState({
|
const [filters, setFilters] = useState({
|
||||||
@@ -91,6 +94,29 @@ const RequestLogList = () => {
|
|||||||
setShowDetailModal(true);
|
setShowDetailModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClearLogs = async () => {
|
||||||
|
try {
|
||||||
|
setClearingLogs(true);
|
||||||
|
const result = await clearLogs(clearDays);
|
||||||
|
|
||||||
|
// 显示成功消息
|
||||||
|
setError(null);
|
||||||
|
alert(`成功清空 ${result.deleted_count} 条 ${clearDays} 天前的日志记录`);
|
||||||
|
|
||||||
|
// 刷新日志列表和统计
|
||||||
|
fetchLogs();
|
||||||
|
fetchStats();
|
||||||
|
|
||||||
|
// 关闭模态框
|
||||||
|
setShowClearModal(false);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message || '清空日志失败');
|
||||||
|
console.error('Error clearing logs:', err);
|
||||||
|
} finally {
|
||||||
|
setClearingLogs(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
if (!dateString) return 'N/A';
|
if (!dateString) return 'N/A';
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
@@ -119,6 +145,17 @@ const RequestLogList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{/* 页面标题和操作按钮 */}
|
||||||
|
<div className="flex justify-between items-center max-w-7xl mx-auto px-4">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">请求日志</h1>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowClearModal(true)}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
清空日志
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 统计信息卡片 */}
|
{/* 统计信息卡片 */}
|
||||||
{stats && (
|
{stats && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
@@ -310,6 +347,64 @@ const RequestLogList = () => {
|
|||||||
isOpen={showDetailModal}
|
isOpen={showDetailModal}
|
||||||
onClose={() => setShowDetailModal(false)}
|
onClose={() => setShowDetailModal(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 清空日志确认模态框 */}
|
||||||
|
{showClearModal && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-4">清空日志确认</h2>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
清空多少天前的日志记录?
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="365"
|
||||||
|
value={clearDays}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value === '' || value === null) return;
|
||||||
|
const numValue = parseInt(value);
|
||||||
|
if (!isNaN(numValue) && numValue >= 0) {
|
||||||
|
setClearDays(numValue);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-gray-600">
|
||||||
|
{clearDays === 0 ? '所有日志' : '天前'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-sm text-gray-600">
|
||||||
|
{clearDays === 0
|
||||||
|
? '警告:此操作将永久删除所有日志记录,无法恢复。'
|
||||||
|
: `此操作将永久删除 ${clearDays} 天以前的日志记录,无法恢复。`
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-3">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowClearModal(false)}
|
||||||
|
disabled={clearingLogs}
|
||||||
|
className="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleClearLogs}
|
||||||
|
disabled={clearingLogs}
|
||||||
|
className="px-4 py-2 bg-red-600 text-white rounded-md text-sm font-medium hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{clearingLogs ? '清空中...' : '确认清空'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,13 +3,8 @@ import RequestLogList from './components/RequestLogList';
|
|||||||
|
|
||||||
const LogsPage = () => {
|
const LogsPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="min-h-screen bg-gray-50 py-8">
|
||||||
<div className="flex justify-between items-center max-w-7xl mx-auto px-4">
|
<RequestLogList />
|
||||||
<h1 className="text-2xl font-bold text-gray-900">请求日志</h1>
|
|
||||||
</div>
|
|
||||||
<div className="px-4">
|
|
||||||
<RequestLogList />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user