mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 08:31:55 +08:00
优化指标处理逻辑,移除错误统计部分并更新前端展示。更新.gitignore以忽略.cursor文件。
This commit is contained in:
parent
64423b00e2
commit
de2209d177
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,3 +26,4 @@ web/dist/
|
||||
data/config.json
|
||||
data/config.json
|
||||
kaifa.md
|
||||
.cursor
|
||||
|
@ -51,13 +51,6 @@ type Metrics struct {
|
||||
Distribution map[string]int64 `json:"distribution"`
|
||||
} `json:"latency_stats"`
|
||||
|
||||
// 错误统计
|
||||
ErrorStats struct {
|
||||
ClientErrors int64 `json:"client_errors"`
|
||||
ServerErrors int64 `json:"server_errors"`
|
||||
Types map[string]int64 `json:"types"`
|
||||
} `json:"error_stats"`
|
||||
|
||||
// 带宽统计
|
||||
BandwidthHistory map[string]string `json:"bandwidth_history"`
|
||||
CurrentBandwidth string `json:"current_bandwidth"`
|
||||
@ -115,26 +108,8 @@ func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 计算客户端错误和服务器错误数量
|
||||
var clientErrors, serverErrors int64
|
||||
// 处理状态码统计数据
|
||||
statusCodeStats := models.SafeStatusCodeStats(stats["status_code_stats"])
|
||||
for code, count := range statusCodeStats {
|
||||
codeInt := utils.ParseInt(code, 0)
|
||||
if codeInt >= 400 && codeInt < 500 {
|
||||
clientErrors += count
|
||||
} else if codeInt >= 500 {
|
||||
serverErrors += count
|
||||
}
|
||||
}
|
||||
|
||||
// 创建错误类型统计
|
||||
errorTypes := make(map[string]int64)
|
||||
if clientErrors > 0 {
|
||||
errorTypes["客户端错误"] = clientErrors
|
||||
}
|
||||
if serverErrors > 0 {
|
||||
errorTypes["服务器错误"] = serverErrors
|
||||
}
|
||||
|
||||
metrics := Metrics{
|
||||
Uptime: metrics.FormatUptime(uptime),
|
||||
@ -197,11 +172,6 @@ func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
metrics.LatencyStats.Distribution["gt1s"] = 0
|
||||
}
|
||||
|
||||
// 填充错误统计数据
|
||||
metrics.ErrorStats.ClientErrors = clientErrors
|
||||
metrics.ErrorStats.ServerErrors = serverErrors
|
||||
metrics.ErrorStats.Types = errorTypes
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(metrics); err != nil {
|
||||
log.Printf("Error encoding metrics: %v", err)
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
} from "@/components/ui/dialog"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
import { Plus, Trash2, Edit, Save, Download, Upload } from "lucide-react"
|
||||
import { Plus, Trash2, Edit, Download, Upload } from "lucide-react"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@ -66,6 +66,10 @@ export default function ConfigPage() {
|
||||
|
||||
// 使用 ref 来保存滚动位置
|
||||
const scrollPositionRef = useRef(0)
|
||||
// 添加一个ref来跟踪是否是初始加载
|
||||
const isInitialLoadRef = useRef(true)
|
||||
// 添加一个防抖定时器ref
|
||||
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
// 对话框状态
|
||||
const [pathDialogOpen, setPathDialogOpen] = useState(false)
|
||||
@ -139,7 +143,8 @@ export default function ConfigPage() {
|
||||
fetchConfig()
|
||||
}, [fetchConfig])
|
||||
|
||||
const handleSave = async () => {
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!config) return
|
||||
|
||||
setSaving(true)
|
||||
@ -172,7 +177,7 @@ export default function ConfigPage() {
|
||||
|
||||
toast({
|
||||
title: "成功",
|
||||
description: "配置已保存",
|
||||
description: "配置已自动保存",
|
||||
})
|
||||
} catch (error) {
|
||||
toast({
|
||||
@ -183,8 +188,36 @@ export default function ConfigPage() {
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}, [config, router, toast])
|
||||
|
||||
// 添加自动保存的useEffect
|
||||
useEffect(() => {
|
||||
// 如果是初始加载或者配置为空,不触发保存
|
||||
if (isInitialLoadRef.current || !config) {
|
||||
isInitialLoadRef.current = false
|
||||
return
|
||||
}
|
||||
|
||||
// 清除之前的定时器
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current)
|
||||
}
|
||||
|
||||
// 设置新的定时器,延迟1秒后保存
|
||||
saveTimeoutRef.current = setTimeout(() => {
|
||||
handleSave()
|
||||
}, 1000)
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
return () => {
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [config, handleSave]) // 监听config变化
|
||||
|
||||
|
||||
|
||||
// 处理对话框打开和关闭时的滚动位置
|
||||
const handleDialogOpenChange = useCallback((open: boolean, handler: (open: boolean) => void) => {
|
||||
if (open) {
|
||||
@ -289,11 +322,6 @@ export default function ConfigPage() {
|
||||
}
|
||||
|
||||
setPathDialogOpen(false)
|
||||
|
||||
toast({
|
||||
title: "成功",
|
||||
description: `${editingPathData ? '更新' : '添加'}路径配置成功`,
|
||||
})
|
||||
}
|
||||
|
||||
const deletePath = (path: string) => {
|
||||
@ -306,10 +334,6 @@ export default function ConfigPage() {
|
||||
delete newConfig.MAP[deletingPath]
|
||||
setConfig(newConfig)
|
||||
setDeletingPath(null)
|
||||
toast({
|
||||
title: "成功",
|
||||
description: "路径映射已删除",
|
||||
})
|
||||
}
|
||||
|
||||
const updateCompression = (type: 'Gzip' | 'Brotli', field: 'Enabled' | 'Level', value: boolean | number) => {
|
||||
@ -397,11 +421,6 @@ export default function ConfigPage() {
|
||||
setExtensionMapDialogOpen(false)
|
||||
setEditingExtension(null)
|
||||
setNewExtension({ ext: "", target: "" })
|
||||
|
||||
toast({
|
||||
title: "成功",
|
||||
description: "扩展名映射已更新",
|
||||
})
|
||||
}
|
||||
|
||||
const deleteExtensionMap = (path: string, ext: string) => {
|
||||
@ -419,15 +438,8 @@ export default function ConfigPage() {
|
||||
}
|
||||
setConfig(newConfig)
|
||||
setDeletingExtension(null)
|
||||
toast({
|
||||
title: "成功",
|
||||
description: "扩展名映射已删除",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const openAddPathDialog = () => {
|
||||
setEditingPathData(null)
|
||||
setNewPathData({
|
||||
@ -571,12 +583,8 @@ export default function ConfigPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Proxy Go配置</CardTitle>
|
||||
<p className="text-sm text-muted-foreground mt-1">编辑后需要点击右上角保存配置按钮</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button onClick={exportConfig} variant="outline">
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
@ -594,11 +602,12 @@ export default function ConfigPage() {
|
||||
onChange={importConfig}
|
||||
/>
|
||||
</label>
|
||||
<Button onClick={handleSave} disabled={saving}>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
{saving ? "保存中..." : "保存配置"}
|
||||
</Button>
|
||||
{saving && (
|
||||
<div className="flex items-center text-sm text-muted-foreground">
|
||||
<span className="animate-pulse mr-2">●</span>
|
||||
正在自动保存...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
@ -37,11 +37,6 @@ interface Metrics {
|
||||
max: string
|
||||
distribution: Record<string, number>
|
||||
}
|
||||
error_stats: {
|
||||
client_errors: number
|
||||
server_errors: number
|
||||
types: Record<string, number>
|
||||
}
|
||||
bandwidth_history: Record<string, string>
|
||||
current_bandwidth: string
|
||||
total_bytes: number
|
||||
@ -325,65 +320,6 @@ export default function DashboardPage() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 错误统计卡片 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>错误统计</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-gray-500">客户端错误 (4xx)</div>
|
||||
<div className="text-2xl font-semibold text-yellow-600">
|
||||
{metrics.error_stats?.client_errors || 0}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
占总请求的 {metrics.total_requests ?
|
||||
((metrics.error_stats?.client_errors || 0) / metrics.total_requests * 100).toFixed(2) : 0}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-gray-500">服务器错误 (5xx)</div>
|
||||
<div className="text-2xl font-semibold text-red-600">
|
||||
{metrics.error_stats?.server_errors || 0}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
占总请求的 {metrics.total_requests ?
|
||||
((metrics.error_stats?.server_errors || 0) / metrics.total_requests * 100).toFixed(2) : 0}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-gray-500">总错误率</div>
|
||||
<div className="text-2xl font-semibold">
|
||||
{(metrics.error_rate * 100).toFixed(2)}%
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
总错误数: {metrics.total_errors || 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{metrics.error_stats?.types && Object.keys(metrics.error_stats.types).length > 0 && (
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-medium text-gray-500 mb-2">错误类型分布</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{Object.entries(metrics.error_stats.types).map(([type, count]) => (
|
||||
<div key={type} className="p-3 rounded-lg border bg-card text-card-foreground shadow-sm">
|
||||
<div className="text-sm font-medium text-gray-500">{type}</div>
|
||||
<div className="text-lg font-semibold">{count}</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
{metrics.total_errors ? ((count / metrics.total_errors) * 100).toFixed(1) : 0}%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 引用来源统计卡片 */}
|
||||
{metrics.top_referers && metrics.top_referers.length > 0 && (
|
||||
<Card>
|
||||
|
Loading…
x
Reference in New Issue
Block a user