diff --git a/internal/metrics/collector.go b/internal/metrics/collector.go index 7c5164b..34265de 100644 --- a/internal/metrics/collector.go +++ b/internal/metrics/collector.go @@ -17,35 +17,25 @@ import ( // Collector 指标收集器 type Collector struct { - startTime time.Time - activeRequests int64 - totalRequests int64 - totalErrors int64 - totalBytes int64 - latencySum int64 - maxLatency int64 // 最大响应时间 - minLatency int64 // 最小响应时间 - clientErrors int64 // 4xx错误 - serverErrors int64 // 5xx错误 - pathStats sync.Map - statusCodeStats sync.Map - latencyBuckets sync.Map // 响应时间分布 - bandwidthStats sync.Map // 带宽统计 - errorTypes sync.Map // 错误类型统计 - recentRequests []models.RequestLog - recentRequestsMutex sync.RWMutex - pathStatsMutex sync.RWMutex - config *config.Config - lastMinute time.Time // 用于计算每分钟带宽 - minuteBytes int64 // 当前分钟的字节数 - - // 新增:时间段统计 - lastStatsTime time.Time - intervalRequests int64 - intervalErrors int64 - intervalBytes int64 - intervalLatencySum int64 - intervalStatusCodes sync.Map + startTime time.Time + activeRequests int64 + totalBytes int64 + latencySum int64 + maxLatency int64 // 最大响应时间 + minLatency int64 // 最小响应时间 + pathStats sync.Map + statusCodeStats sync.Map + latencyBuckets sync.Map // 响应时间分布 + bandwidthStats struct { + sync.RWMutex + window time.Duration + lastUpdate time.Time + current int64 + history map[string]int64 + } + recentRequests *models.RequestQueue + pathStatsMutex sync.RWMutex + config *config.Config } var ( @@ -58,19 +48,25 @@ func InitCollector(cfg *config.Config) error { once.Do(func() { instance = &Collector{ startTime: time.Now(), - lastMinute: time.Now(), - lastStatsTime: time.Now(), - recentRequests: make([]models.RequestLog, 0, 1000), + recentRequests: models.NewRequestQueue(100), config: cfg, - minLatency: math.MaxInt64, // 初始化为最大值 + minLatency: math.MaxInt64, } + // 初始化带宽统计 + instance.bandwidthStats.window = time.Minute + instance.bandwidthStats.lastUpdate = time.Now() + instance.bandwidthStats.history = make(map[string]int64) + // 初始化延迟分布桶 instance.latencyBuckets.Store("<10ms", new(int64)) instance.latencyBuckets.Store("10-50ms", new(int64)) instance.latencyBuckets.Store("50-200ms", new(int64)) instance.latencyBuckets.Store("200-1000ms", new(int64)) instance.latencyBuckets.Store(">1s", new(int64)) + + // 启动数据一致性检查器 + instance.startConsistencyChecker() }) return nil } @@ -92,41 +88,22 @@ func (c *Collector) EndRequest() { // RecordRequest 记录请求 func (c *Collector) RecordRequest(path string, status int, latency time.Duration, bytes int64, clientIP string, r *http.Request) { - // 更新总体统计 - atomic.AddInt64(&c.totalRequests, 1) - atomic.AddInt64(&c.totalBytes, bytes) - atomic.AddInt64(&c.latencySum, int64(latency)) - - // 更新时间段统计 - atomic.AddInt64(&c.intervalRequests, 1) - atomic.AddInt64(&c.intervalBytes, bytes) - atomic.AddInt64(&c.intervalLatencySum, int64(latency)) - if status >= 400 { - atomic.AddInt64(&c.intervalErrors, 1) - } - - // 更新带宽统计 - atomic.AddInt64(&c.minuteBytes, bytes) - now := time.Now() - if now.Sub(c.lastMinute) >= time.Minute { - currentMinute := now.Format("15:04") - counter := new(int64) - *counter = atomic.SwapInt64(&c.minuteBytes, 0) - c.bandwidthStats.Store(currentMinute, counter) - c.lastMinute = now - } - - // 更新时间段状态码统计 + // 更新状态码统计 statusKey := fmt.Sprintf("%d", status) - if counter, ok := c.intervalStatusCodes.Load(statusKey); ok { + if counter, ok := c.statusCodeStats.Load(statusKey); ok { atomic.AddInt64(counter.(*int64), 1) } else { counter := new(int64) *counter = 1 - c.intervalStatusCodes.Store(statusKey, counter) + c.statusCodeStats.Store(statusKey, counter) } - // 更新最小和最大响应时间 + // 更新总字节数和带宽统计 + atomic.AddInt64(&c.totalBytes, bytes) + c.updateBandwidthStats(bytes) + + // 更新延迟统计 + atomic.AddInt64(&c.latencySum, int64(latency)) latencyNanos := int64(latency) for { oldMin := atomic.LoadInt64(&c.minLatency) @@ -170,80 +147,39 @@ func (c *Collector) RecordRequest(path string, status int, latency time.Duration c.latencyBuckets.Store(bucketKey, counter) } - // 更新错误统计 - if status >= 400 { - atomic.AddInt64(&c.totalErrors, 1) - if status >= 500 { - atomic.AddInt64(&c.serverErrors, 1) - } else { - atomic.AddInt64(&c.clientErrors, 1) - } - errKey := fmt.Sprintf("%d %s", status, http.StatusText(status)) - if counter, ok := c.errorTypes.Load(errKey); ok { - atomic.AddInt64(counter.(*int64), 1) - } else { - counter := new(int64) - *counter = 1 - c.errorTypes.Store(errKey, counter) - } - } - - // 收集状态码统计 - statusCodeStats := make(map[string]int64) - c.statusCodeStats.Range(func(key, value interface{}) bool { - if counter, ok := value.(*int64); ok { - statusCodeStats[key.(string)] = atomic.LoadInt64(counter) - } else { - statusCodeStats[key.(string)] = value.(int64) - } - return true - }) - - // 更新状态码统计 - statusKey = fmt.Sprintf("%d", status) - if counter, ok := c.statusCodeStats.Load(statusKey); ok { - atomic.AddInt64(counter.(*int64), 1) - } else { - counter := new(int64) - *counter = 1 - c.statusCodeStats.Store(statusKey, counter) - } - // 更新路径统计 c.pathStatsMutex.Lock() if value, ok := c.pathStats.Load(path); ok { stat := value.(*models.PathMetrics) - atomic.AddInt64(&stat.RequestCount, 1) + stat.AddRequest() if status >= 400 { - atomic.AddInt64(&stat.ErrorCount, 1) + stat.AddError() } - atomic.AddInt64(&stat.TotalLatency, int64(latency)) - atomic.AddInt64(&stat.BytesTransferred, bytes) + stat.AddLatency(int64(latency)) + stat.AddBytes(bytes) } else { - c.pathStats.Store(path, &models.PathMetrics{ - Path: path, - RequestCount: 1, - ErrorCount: map[bool]int64{true: 1, false: 0}[status >= 400], - TotalLatency: int64(latency), - BytesTransferred: bytes, - }) + newStat := &models.PathMetrics{ + Path: path, + } + newStat.AddRequest() + if status >= 400 { + newStat.AddError() + } + newStat.AddLatency(int64(latency)) + newStat.AddBytes(bytes) + c.pathStats.Store(path, newStat) } c.pathStatsMutex.Unlock() // 更新最近请求记录 - c.recentRequestsMutex.Lock() - c.recentRequests = append([]models.RequestLog{{ + c.recentRequests.Push(models.RequestLog{ Time: time.Now(), Path: path, Status: status, Latency: int64(latency), BytesSent: bytes, ClientIP: clientIP, - }}, c.recentRequests...) - if len(c.recentRequests) > 100 { // 只保留最近100条记录 - c.recentRequests = c.recentRequests[:100] - } - c.recentRequestsMutex.Unlock() + }) } // FormatUptime 格式化运行时间 @@ -267,54 +203,58 @@ func FormatUptime(d time.Duration) string { // GetStats 获取统计数据 func (c *Collector) GetStats() map[string]interface{} { + // 获取统计数据 var mem runtime.MemStats runtime.ReadMemStats(&mem) now := time.Now() - interval := now.Sub(c.lastStatsTime) - c.lastStatsTime = now + totalRuntime := now.Sub(c.startTime) - // 获取并重置时间段统计 - intervalRequests := atomic.SwapInt64(&c.intervalRequests, 0) - intervalBytes := atomic.SwapInt64(&c.intervalBytes, 0) - intervalLatencySum := atomic.SwapInt64(&c.intervalLatencySum, 0) - intervalErrors := atomic.SwapInt64(&c.intervalErrors, 0) - - // 计算时间段平均延迟 - avgLatency := float64(0) - if intervalRequests > 0 { - avgLatency = float64(intervalLatencySum) / float64(intervalRequests) - } - - // 收集并重置时间段状态码统计 - intervalStatusStats := make(map[string]int64) - c.intervalStatusCodes.Range(func(key, value interface{}) bool { - intervalStatusStats[key.(string)] = atomic.SwapInt64(value.(*int64), 0) + // 计算总请求数和平均延迟 + var totalRequests int64 + c.statusCodeStats.Range(func(key, value interface{}) bool { + if counter, ok := value.(*int64); ok { + totalRequests += atomic.LoadInt64(counter) + } return true }) + avgLatency := float64(0) + if totalRequests > 0 { + avgLatency = float64(atomic.LoadInt64(&c.latencySum)) / float64(totalRequests) + } + + // 计算总体平均每秒请求数 + requestsPerSecond := float64(totalRequests) / totalRuntime.Seconds() + // 收集状态码统计 statusCodeStats := make(map[string]int64) c.statusCodeStats.Range(func(key, value interface{}) bool { - statusCodeStats[key.(string)] = atomic.LoadInt64(value.(*int64)) + if counter, ok := value.(*int64); ok { + statusCodeStats[key.(string)] = atomic.LoadInt64(counter) + } else { + statusCodeStats[key.(string)] = value.(int64) + } return true }) // 收集路径统计 - var pathMetrics []models.PathMetrics + var pathMetrics []*models.PathMetrics c.pathStats.Range(func(key, value interface{}) bool { stats := value.(*models.PathMetrics) - if stats.RequestCount > 0 { - avgLatencyMs := float64(stats.TotalLatency) / float64(stats.RequestCount) / float64(time.Millisecond) + requestCount := stats.GetRequestCount() + if requestCount > 0 { + totalLatency := stats.GetTotalLatency() + avgLatencyMs := float64(totalLatency) / float64(requestCount) / float64(time.Millisecond) stats.AvgLatency = fmt.Sprintf("%.2fms", avgLatencyMs) } - pathMetrics = append(pathMetrics, *stats) + pathMetrics = append(pathMetrics, stats) return true }) // 按请求数降序排序 sort.Slice(pathMetrics, func(i, j int) bool { - return pathMetrics[i].RequestCount > pathMetrics[j].RequestCount + return pathMetrics[i].GetRequestCount() > pathMetrics[j].GetRequestCount() }) // 只保留前10个 @@ -333,37 +273,8 @@ func (c *Collector) GetStats() map[string]interface{} { return true }) - // 收集错误类型统计 - errorTypeStats := make(map[string]int64) - c.errorTypes.Range(func(key, value interface{}) bool { - if counter, ok := value.(*int64); ok { - errorTypeStats[key.(string)] = atomic.LoadInt64(counter) - } else { - errorTypeStats[key.(string)] = value.(int64) - } - return true - }) - - // 收集最近5分钟的带宽统计 - bandwidthHistory := make(map[string]string) - var times []string - c.bandwidthStats.Range(func(key, value interface{}) bool { - times = append(times, key.(string)) - return true - }) - sort.Strings(times) - if len(times) > 5 { - times = times[len(times)-5:] - } - for _, t := range times { - if bytes, ok := c.bandwidthStats.Load(t); ok { - if counter, ok := bytes.(*int64); ok { - bandwidthHistory[t] = utils.FormatBytes(atomic.LoadInt64(counter)) + "/min" - } else { - bandwidthHistory[t] = utils.FormatBytes(bytes.(int64)) + "/min" - } - } - } + // 获取最近请求记录(使用读锁) + recentRequests := c.recentRequests.GetAll() // 获取最小和最大响应时间 minLatency := atomic.LoadInt64(&c.minLatency) @@ -372,25 +283,19 @@ func (c *Collector) GetStats() map[string]interface{} { minLatency = 0 } - // 获取最近请求记录(加锁) - c.recentRequestsMutex.RLock() - recentRequests := make([]models.RequestLog, len(c.recentRequests)) - copy(recentRequests, c.recentRequests) - c.recentRequestsMutex.RUnlock() + // 收集带宽历史记录 + bandwidthHistory := c.getBandwidthHistory() return map[string]interface{}{ - "uptime": FormatUptime(time.Since(c.startTime)), + "uptime": FormatUptime(totalRuntime), "active_requests": atomic.LoadInt64(&c.activeRequests), - "total_requests": atomic.LoadInt64(&c.totalRequests), - "total_errors": atomic.LoadInt64(&c.totalErrors), - "interval_errors": intervalErrors, "total_bytes": atomic.LoadInt64(&c.totalBytes), "num_goroutine": runtime.NumGoroutine(), "memory_usage": utils.FormatBytes(int64(mem.Alloc)), "avg_response_time": fmt.Sprintf("%.2fms", avgLatency/float64(time.Millisecond)), - "requests_per_second": float64(intervalRequests) / interval.Seconds(), - "bytes_per_second": float64(intervalBytes) / interval.Seconds(), - "status_code_stats": intervalStatusStats, + "requests_per_second": requestsPerSecond, + "bytes_per_second": float64(atomic.LoadInt64(&c.totalBytes)) / totalRuntime.Seconds(), + "status_code_stats": statusCodeStats, "top_paths": pathMetrics, "recent_requests": recentRequests, "latency_stats": map[string]interface{}{ @@ -398,13 +303,8 @@ func (c *Collector) GetStats() map[string]interface{} { "max": fmt.Sprintf("%.2fms", float64(maxLatency)/float64(time.Millisecond)), "distribution": latencyDistribution, }, - "error_stats": map[string]interface{}{ - "client_errors": atomic.LoadInt64(&c.clientErrors), - "server_errors": atomic.LoadInt64(&c.serverErrors), - "types": errorTypeStats, - }, "bandwidth_history": bandwidthHistory, - "current_bandwidth": utils.FormatBytes(atomic.LoadInt64(&c.minuteBytes)) + "/min", + "current_bandwidth": utils.FormatBytes(int64(c.getCurrentBandwidth())) + "/s", } } @@ -429,36 +329,41 @@ func (c *Collector) LoadRecentStats() error { // validateLoadedData 验证当前数据的有效性 func (c *Collector) validateLoadedData() error { // 验证基础指标 - if c.totalRequests < 0 || - c.totalErrors < 0 || - c.totalBytes < 0 { - return fmt.Errorf("invalid stats values") - } - - // 验证错误数不能大于总请求数 - if c.totalErrors > c.totalRequests { - return fmt.Errorf("total errors exceeds total requests") + if c.totalBytes < 0 || + c.activeRequests < 0 { + return fmt.Errorf("invalid negative stats values") } // 验证状态码统计 + var statusCodeTotal int64 c.statusCodeStats.Range(func(key, value interface{}) bool { - return value.(int64) >= 0 + count := atomic.LoadInt64(value.(*int64)) + if count < 0 { + return false + } + statusCodeTotal += count + return true }) // 验证路径统计 var totalPathRequests int64 c.pathStats.Range(func(_, value interface{}) bool { stats := value.(*models.PathMetrics) - if stats.RequestCount < 0 || stats.ErrorCount < 0 { + requestCount := stats.GetRequestCount() + errorCount := stats.GetErrorCount() + if requestCount < 0 || errorCount < 0 { return false } - totalPathRequests += stats.RequestCount + if errorCount > requestCount { + return false + } + totalPathRequests += requestCount return true }) - // 验证总数一致性 - if totalPathRequests > c.totalRequests { - return fmt.Errorf("path stats total exceeds total requests") + if totalPathRequests != statusCodeTotal { + return fmt.Errorf("path stats total (%d) does not match status code total (%d)", + totalPathRequests, statusCodeTotal) } return nil @@ -474,8 +379,81 @@ func (c *Collector) GetLastSaveTime() time.Time { // CheckDataConsistency 实现 interfaces.MetricsCollector 接口 func (c *Collector) CheckDataConsistency() error { // 简单的数据验证 - if c.totalErrors > c.totalRequests { - return fmt.Errorf("total errors exceeds total requests") + if err := c.validateLoadedData(); err != nil { + return err } return nil } + +// 添加定期检查数据一致性的功能 +func (c *Collector) startConsistencyChecker() { + go func() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for range ticker.C { + if err := c.validateLoadedData(); err != nil { + log.Printf("[Metrics] Data consistency check failed: %v", err) + // 可以在这里添加修复逻辑或报警通知 + } + } + }() +} + +// updateBandwidthStats 更新带宽统计 +func (c *Collector) updateBandwidthStats(bytes int64) { + c.bandwidthStats.Lock() + defer c.bandwidthStats.Unlock() + + now := time.Now() + if now.Sub(c.bandwidthStats.lastUpdate) >= c.bandwidthStats.window { + // 保存当前时间窗口的数据 + key := c.bandwidthStats.lastUpdate.Format("01-02 15:04") + c.bandwidthStats.history[key] = c.bandwidthStats.current + + // 清理旧数据(保留最近5个时间窗口) + if len(c.bandwidthStats.history) > 5 { + var oldestTime time.Time + var oldestKey string + for k := range c.bandwidthStats.history { + t, _ := time.Parse("01-02 15:04", k) + if oldestTime.IsZero() || t.Before(oldestTime) { + oldestTime = t + oldestKey = k + } + } + delete(c.bandwidthStats.history, oldestKey) + } + + // 重置当前窗口 + c.bandwidthStats.current = bytes + c.bandwidthStats.lastUpdate = now + } else { + c.bandwidthStats.current += bytes + } +} + +// getCurrentBandwidth 获取当前带宽 +func (c *Collector) getCurrentBandwidth() float64 { + c.bandwidthStats.RLock() + defer c.bandwidthStats.RUnlock() + + now := time.Now() + duration := now.Sub(c.bandwidthStats.lastUpdate).Seconds() + if duration == 0 { + return 0 + } + return float64(c.bandwidthStats.current) / duration +} + +// getBandwidthHistory 获取带宽历史记录 +func (c *Collector) getBandwidthHistory() map[string]string { + c.bandwidthStats.RLock() + defer c.bandwidthStats.RUnlock() + + history := make(map[string]string) + for k, v := range c.bandwidthStats.history { + history[k] = utils.FormatBytes(v) + "/min" + } + return history +} diff --git a/internal/models/metrics.go b/internal/models/metrics.go index fde01be..7bd4ac8 100644 --- a/internal/models/metrics.go +++ b/internal/models/metrics.go @@ -11,14 +11,54 @@ type PathStats struct { LatencySum atomic.Int64 } -// PathMetrics 路径指标 +// PathMetrics 路径统计信息 type PathMetrics struct { - Path string `json:"path"` - RequestCount int64 `json:"request_count"` - ErrorCount int64 `json:"error_count"` - TotalLatency int64 `json:"total_latency"` - BytesTransferred int64 `json:"bytes_transferred"` - AvgLatency string `json:"avg_latency"` + Path string `json:"path"` + RequestCount atomic.Int64 `json:"request_count"` + ErrorCount atomic.Int64 `json:"error_count"` + TotalLatency atomic.Int64 `json:"-"` + BytesTransferred atomic.Int64 `json:"bytes_transferred"` + AvgLatency string `json:"avg_latency"` +} + +// GetRequestCount 获取请求数 +func (p *PathMetrics) GetRequestCount() int64 { + return p.RequestCount.Load() +} + +// GetErrorCount 获取错误数 +func (p *PathMetrics) GetErrorCount() int64 { + return p.ErrorCount.Load() +} + +// GetTotalLatency 获取总延迟 +func (p *PathMetrics) GetTotalLatency() int64 { + return p.TotalLatency.Load() +} + +// GetBytesTransferred 获取传输字节数 +func (p *PathMetrics) GetBytesTransferred() int64 { + return p.BytesTransferred.Load() +} + +// AddRequest 增加请求数 +func (p *PathMetrics) AddRequest() { + p.RequestCount.Add(1) +} + +// AddError 增加错误数 +func (p *PathMetrics) AddError() { + p.ErrorCount.Add(1) +} + +// AddLatency 增加延迟 +func (p *PathMetrics) AddLatency(latency int64) { + p.TotalLatency.Add(latency) +} + +// AddBytes 增加传输字节数 +func (p *PathMetrics) AddBytes(bytes int64) { + p.BytesTransferred.Add(bytes) } type HistoricalMetrics struct { diff --git a/internal/models/request.go b/internal/models/request.go index 6a63010..06dd335 100644 --- a/internal/models/request.go +++ b/internal/models/request.go @@ -7,12 +7,12 @@ import ( // RequestLog 请求日志 type RequestLog struct { - Time time.Time `json:"Time"` - Path string `json:"Path"` - Status int `json:"Status"` - Latency int64 `json:"Latency"` - BytesSent int64 `json:"BytesSent"` - ClientIP string `json:"ClientIP"` + Time time.Time `json:"time"` + Path string `json:"path"` + Status int `json:"status"` + Latency int64 `json:"latency"` + BytesSent int64 `json:"bytes_sent"` + ClientIP string `json:"client_ip"` } // RequestQueue 请求队列 diff --git a/web/app/dashboard/config/page.tsx b/web/app/dashboard/config/page.tsx index 6838fcf..909d12b 100644 --- a/web/app/dashboard/config/page.tsx +++ b/web/app/dashboard/config/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useEffect, useState, useCallback } from "react" +import { useEffect, useState, useCallback, useRef } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { useToast } from "@/components/ui/use-toast" @@ -70,6 +70,9 @@ export default function ConfigPage() { const { toast } = useToast() const router = useRouter() + // 使用 ref 来保存滚动位置 + const scrollPositionRef = useRef(0) + // 对话框状态 const [pathDialogOpen, setPathDialogOpen] = useState(false) const [newPathData, setNewPathData] = useState({ @@ -191,40 +194,60 @@ export default function ConfigPage() { } } - const handlePathDialogOpenChange = (open: boolean) => { - setPathDialogOpen(open) - if (!open) { - setEditingPathData(null) - setNewPathData({ - path: "", - defaultTarget: "", - extensionMap: {}, - sizeThreshold: 0, - sizeUnit: 'MB', + // 处理对话框打开和关闭时的滚动位置 + const handleDialogOpenChange = useCallback((open: boolean, handler: (open: boolean) => void) => { + if (open) { + // 对话框打开时,保存当前滚动位置 + scrollPositionRef.current = window.scrollY + } else { + // 对话框关闭时,恢复滚动位置 + handler(open) + requestAnimationFrame(() => { + window.scrollTo(0, scrollPositionRef.current) }) } - } + }, []) - const handleFixedPathDialogOpenChange = (open: boolean) => { - setFixedPathDialogOpen(open) - if (!open) { - setEditingFixedPath(null) - setNewFixedPath({ - Path: "", - TargetHost: "", - TargetURL: "", - }) - } - } + const handlePathDialogOpenChange = useCallback((open: boolean) => { + handleDialogOpenChange(open, (isOpen) => { + setPathDialogOpen(isOpen) + if (!isOpen) { + setEditingPathData(null) + setNewPathData({ + path: "", + defaultTarget: "", + extensionMap: {}, + sizeThreshold: 0, + sizeUnit: 'MB', + }) + } + }) + }, [handleDialogOpenChange]) - const handleExtensionMapDialogOpenChange = (open: boolean) => { - setExtensionMapDialogOpen(open) - if (!open) { - setEditingPath(null) - setEditingExtension(null) - setNewExtension({ ext: "", target: "" }) - } - } + const handleFixedPathDialogOpenChange = useCallback((open: boolean) => { + handleDialogOpenChange(open, (isOpen) => { + setFixedPathDialogOpen(isOpen) + if (!isOpen) { + setEditingFixedPath(null) + setNewFixedPath({ + Path: "", + TargetHost: "", + TargetURL: "", + }) + } + }) + }, [handleDialogOpenChange]) + + const handleExtensionMapDialogOpenChange = useCallback((open: boolean) => { + handleDialogOpenChange(open, (isOpen) => { + setExtensionMapDialogOpen(isOpen) + if (!isOpen) { + setEditingPath(null) + setEditingExtension(null) + setNewExtension({ ext: "", target: "" }) + } + }) + }, [handleDialogOpenChange]) const addOrUpdatePath = () => { if (!config) return @@ -663,6 +686,18 @@ export default function ConfigPage() { setPathDialogOpen(true) } + // 处理删除对话框的滚动位置 + const handleDeleteDialogOpenChange = useCallback((open: boolean, setter: (value: null) => void) => { + if (open) { + scrollPositionRef.current = window.scrollY + } else { + setter(null) + requestAnimationFrame(() => { + window.scrollTo(0, scrollPositionRef.current) + }) + } + }, []) + if (loading) { return (
@@ -1090,7 +1125,10 @@ export default function ConfigPage() { - !open && setDeletingPath(null)}> + handleDeleteDialogOpenChange(open, setDeletingPath)} + > 确认删除 @@ -1105,7 +1143,10 @@ export default function ConfigPage() { - !open && setDeletingFixedPath(null)}> + handleDeleteDialogOpenChange(open, setDeletingFixedPath)} + > 确认删除 @@ -1120,7 +1161,10 @@ export default function ConfigPage() { - !open && setDeletingExtension(null)}> + handleDeleteDialogOpenChange(open, setDeletingExtension)} + > 确认删除 diff --git a/web/app/dashboard/page.tsx b/web/app/dashboard/page.tsx index a9b84b2..74dbd61 100644 --- a/web/app/dashboard/page.tsx +++ b/web/app/dashboard/page.tsx @@ -148,14 +148,6 @@ export default function DashboardPage() {
当前活跃请求
{metrics.active_requests}
-
-
总请求数
-
{metrics.total_requests}
-
-
-
错误数
-
{metrics.total_errors}
-
总传输数据
{formatBytes(metrics.total_bytes)}
@@ -187,7 +179,7 @@ export default function DashboardPage() {
{metrics.avg_response_time}
-
每秒请求数
+
平均每秒请求数
{metrics.requests_per_second.toFixed(2)}
@@ -205,17 +197,36 @@ export default function DashboardPage() {
{Object.entries(metrics.status_code_stats || {}) .sort((a, b) => a[0].localeCompare(b[0])) - .map(([status, count]) => ( -
-
- 状态码 {status} + .map(([status, count]) => { + const statusNum = parseInt(status); + let colorClass = "text-green-600"; + if (statusNum >= 500) { + colorClass = "text-red-600"; + } else if (statusNum >= 400) { + colorClass = "text-yellow-600"; + } else if (statusNum >= 300) { + colorClass = "text-blue-600"; + } + + // 计算总请求数 + const totalRequests = Object.values(metrics.status_code_stats || {}).reduce((a, b) => a + (b as number), 0); + + return ( +
+
+ 状态码 {status} +
+
{count}
+
+ {totalRequests ? + ((count as number / totalRequests) * 100).toFixed(1) : 0}% +
-
{count}
-
- ))} + ); + })}