mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 16:41:54 +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
|
||||||
data/config.json
|
data/config.json
|
||||||
kaifa.md
|
kaifa.md
|
||||||
|
.cursor
|
||||||
|
@ -51,13 +51,6 @@ type Metrics struct {
|
|||||||
Distribution map[string]int64 `json:"distribution"`
|
Distribution map[string]int64 `json:"distribution"`
|
||||||
} `json:"latency_stats"`
|
} `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"`
|
BandwidthHistory map[string]string `json:"bandwidth_history"`
|
||||||
CurrentBandwidth string `json:"current_bandwidth"`
|
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"])
|
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{
|
metrics := Metrics{
|
||||||
Uptime: metrics.FormatUptime(uptime),
|
Uptime: metrics.FormatUptime(uptime),
|
||||||
@ -197,11 +172,6 @@ func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
metrics.LatencyStats.Distribution["gt1s"] = 0
|
metrics.LatencyStats.Distribution["gt1s"] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 填充错误统计数据
|
|
||||||
metrics.ErrorStats.ClientErrors = clientErrors
|
|
||||||
metrics.ErrorStats.ServerErrors = serverErrors
|
|
||||||
metrics.ErrorStats.Types = errorTypes
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if err := json.NewEncoder(w).Encode(metrics); err != nil {
|
if err := json.NewEncoder(w).Encode(metrics); err != nil {
|
||||||
log.Printf("Error encoding metrics: %v", err)
|
log.Printf("Error encoding metrics: %v", err)
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { Slider } from "@/components/ui/slider"
|
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 {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@ -66,6 +66,10 @@ export default function ConfigPage() {
|
|||||||
|
|
||||||
// 使用 ref 来保存滚动位置
|
// 使用 ref 来保存滚动位置
|
||||||
const scrollPositionRef = useRef(0)
|
const scrollPositionRef = useRef(0)
|
||||||
|
// 添加一个ref来跟踪是否是初始加载
|
||||||
|
const isInitialLoadRef = useRef(true)
|
||||||
|
// 添加一个防抖定时器ref
|
||||||
|
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
|
||||||
// 对话框状态
|
// 对话框状态
|
||||||
const [pathDialogOpen, setPathDialogOpen] = useState(false)
|
const [pathDialogOpen, setPathDialogOpen] = useState(false)
|
||||||
@ -139,7 +143,8 @@ export default function ConfigPage() {
|
|||||||
fetchConfig()
|
fetchConfig()
|
||||||
}, [fetchConfig])
|
}, [fetchConfig])
|
||||||
|
|
||||||
const handleSave = async () => {
|
|
||||||
|
const handleSave = useCallback(async () => {
|
||||||
if (!config) return
|
if (!config) return
|
||||||
|
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
@ -172,7 +177,7 @@ export default function ConfigPage() {
|
|||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "成功",
|
title: "成功",
|
||||||
description: "配置已保存",
|
description: "配置已自动保存",
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
@ -183,8 +188,36 @@ export default function ConfigPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
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) => {
|
const handleDialogOpenChange = useCallback((open: boolean, handler: (open: boolean) => void) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@ -289,11 +322,6 @@ export default function ConfigPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setPathDialogOpen(false)
|
setPathDialogOpen(false)
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "成功",
|
|
||||||
description: `${editingPathData ? '更新' : '添加'}路径配置成功`,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletePath = (path: string) => {
|
const deletePath = (path: string) => {
|
||||||
@ -306,10 +334,6 @@ export default function ConfigPage() {
|
|||||||
delete newConfig.MAP[deletingPath]
|
delete newConfig.MAP[deletingPath]
|
||||||
setConfig(newConfig)
|
setConfig(newConfig)
|
||||||
setDeletingPath(null)
|
setDeletingPath(null)
|
||||||
toast({
|
|
||||||
title: "成功",
|
|
||||||
description: "路径映射已删除",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateCompression = (type: 'Gzip' | 'Brotli', field: 'Enabled' | 'Level', value: boolean | number) => {
|
const updateCompression = (type: 'Gzip' | 'Brotli', field: 'Enabled' | 'Level', value: boolean | number) => {
|
||||||
@ -397,11 +421,6 @@ export default function ConfigPage() {
|
|||||||
setExtensionMapDialogOpen(false)
|
setExtensionMapDialogOpen(false)
|
||||||
setEditingExtension(null)
|
setEditingExtension(null)
|
||||||
setNewExtension({ ext: "", target: "" })
|
setNewExtension({ ext: "", target: "" })
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "成功",
|
|
||||||
description: "扩展名映射已更新",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteExtensionMap = (path: string, ext: string) => {
|
const deleteExtensionMap = (path: string, ext: string) => {
|
||||||
@ -419,15 +438,8 @@ export default function ConfigPage() {
|
|||||||
}
|
}
|
||||||
setConfig(newConfig)
|
setConfig(newConfig)
|
||||||
setDeletingExtension(null)
|
setDeletingExtension(null)
|
||||||
toast({
|
|
||||||
title: "成功",
|
|
||||||
description: "扩展名映射已删除",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const openAddPathDialog = () => {
|
const openAddPathDialog = () => {
|
||||||
setEditingPathData(null)
|
setEditingPathData(null)
|
||||||
setNewPathData({
|
setNewPathData({
|
||||||
@ -571,12 +583,8 @@ export default function ConfigPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div className="flex flex-row items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle>Proxy Go配置</CardTitle>
|
<CardTitle>Proxy Go配置</CardTitle>
|
||||||
<p className="text-sm text-muted-foreground mt-1">编辑后需要点击右上角保存配置按钮</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button onClick={exportConfig} variant="outline">
|
<Button onClick={exportConfig} variant="outline">
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
@ -594,11 +602,12 @@ export default function ConfigPage() {
|
|||||||
onChange={importConfig}
|
onChange={importConfig}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<Button onClick={handleSave} disabled={saving}>
|
{saving && (
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<div className="flex items-center text-sm text-muted-foreground">
|
||||||
{saving ? "保存中..." : "保存配置"}
|
<span className="animate-pulse mr-2">●</span>
|
||||||
</Button>
|
正在自动保存...
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
@ -37,11 +37,6 @@ interface Metrics {
|
|||||||
max: string
|
max: string
|
||||||
distribution: Record<string, number>
|
distribution: Record<string, number>
|
||||||
}
|
}
|
||||||
error_stats: {
|
|
||||||
client_errors: number
|
|
||||||
server_errors: number
|
|
||||||
types: Record<string, number>
|
|
||||||
}
|
|
||||||
bandwidth_history: Record<string, string>
|
bandwidth_history: Record<string, string>
|
||||||
current_bandwidth: string
|
current_bandwidth: string
|
||||||
total_bytes: number
|
total_bytes: number
|
||||||
@ -325,65 +320,6 @@ export default function DashboardPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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 && (
|
{metrics.top_referers && metrics.top_referers.length > 0 && (
|
||||||
<Card>
|
<Card>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user