Skip to content

Commit 82ca7f8

Browse files
committed
feat: 仪表盘加入今日统计
1 parent 3bf30d7 commit 82ca7f8

File tree

5 files changed

+93
-73
lines changed

5 files changed

+93
-73
lines changed

app/dashboard/page.tsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -381,25 +381,42 @@ export default function DashboardPage() {
381381
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
382382
}, []);
383383

384-
// 处理今日流量数据 - 优化内存使用,减少中间对象创建
384+
// 处理今日流量数据 - 通过最早和最晚时间的差值计算今日消耗流量
385385
const processTodayTrafficData = useCallback((trafficData: TrafficTrendData[]) => {
386386
if (!isMountedRef.current || !trafficData?.length) return;
387387

388388
const today = new Date();
389389
const todayStartTimestamp = Math.floor(new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime() / 1000);
390390

391-
// 使用更高效的reduce,避免创建多个中间数组
392-
const todayTraffic = trafficData.reduce((acc, item) => {
393-
// 直接在reduce中进行时间判断,避免filter创建新数组
394-
if (item.hourTime >= todayStartTimestamp) {
395-
acc.tcpIn += item.tcpRx;
396-
acc.tcpOut += item.tcpTx;
397-
acc.udpIn += item.udpRx;
398-
acc.udpOut += item.udpTx;
399-
acc.total += item.tcpRx + item.tcpTx + item.udpRx + item.udpTx;
391+
// 筛选出今天的数据
392+
const todayData = trafficData.filter(item => item.hourTime >= todayStartTimestamp);
393+
394+
if (todayData.length === 0) {
395+
// 如果没有今天的数据,设置为0
396+
if (isMountedRef.current) {
397+
setTodayTrafficData({ tcpIn: 0, tcpOut: 0, udpIn: 0, udpOut: 0, total: 0 });
400398
}
401-
return acc;
402-
}, { tcpIn: 0, tcpOut: 0, udpIn: 0, udpOut: 0, total: 0 });
399+
return;
400+
}
401+
402+
// 按时间排序,确保数据是按时间顺序的
403+
const sortedTodayData = todayData.sort((a, b) => a.hourTime - b.hourTime);
404+
405+
// 获取最早和最晚的时间点数据
406+
const earliestData = sortedTodayData[0];
407+
const latestData = sortedTodayData[sortedTodayData.length - 1];
408+
409+
// 计算差值(最晚 - 最早 = 今日消耗流量)
410+
const todayTraffic = {
411+
tcpIn: Math.max(0, latestData.tcpRx - earliestData.tcpRx),
412+
tcpOut: Math.max(0, latestData.tcpTx - earliestData.tcpTx),
413+
udpIn: Math.max(0, latestData.udpRx - earliestData.udpRx),
414+
udpOut: Math.max(0, latestData.udpTx - earliestData.udpTx),
415+
total: 0
416+
};
417+
418+
// 计算总流量
419+
todayTraffic.total = todayTraffic.tcpIn + todayTraffic.tcpOut + todayTraffic.udpIn + todayTraffic.udpOut;
403420

404421
if (isMountedRef.current) {
405422
setTodayTrafficData(todayTraffic);

app/tunnels/details/page.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,24 +2026,24 @@ export default function TunnelDetailPage({
20262026

20272027
{/* 第二行:延迟和连接池 */}
20282028
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
2029-
{/* 连接池趋势 */}
2029+
{/* 延迟 */}
20302030
<Card className="p-2">
20312031
<CardHeader className="pb-1 pt-2 px-2 flex items-center justify-between">
2032-
<h4 className="text-sm font-semibold">池连接数</h4>
2032+
<h4 className="text-sm font-semibold">端内延迟</h4>
20332033
<Button
20342034
size="sm"
20352035
variant="light"
20362036
isIconOnly
2037-
onPress={() => openFullscreenChart("pool", "池连接数")}
2037+
onPress={() => openFullscreenChart("latency", "端内延迟")}
20382038
className="h-6 w-6 min-w-0"
20392039
>
20402040
<FontAwesomeIcon icon={faExpand} className="text-xs" />
20412041
</Button>
20422042
</CardHeader>
20432043
<CardBody className="pt-0 px-2 pb-2">
20442044
<div className="h-[140px]">
2045-
<PoolChart
2046-
data={transformPoolData(metricsData?.data)}
2045+
<LatencyChart
2046+
data={transformLatencyData(metricsData?.data)}
20472047
height={140}
20482048
loading={metricsLoading && !metricsData}
20492049
error={metricsError || undefined}
@@ -2052,24 +2052,24 @@ export default function TunnelDetailPage({
20522052
</div>
20532053
</CardBody>
20542054
</Card>
2055-
{/* 延迟 */}
2055+
{/* 连接池趋势 */}
20562056
<Card className="p-2">
20572057
<CardHeader className="pb-1 pt-2 px-2 flex items-center justify-between">
2058-
<h4 className="text-sm font-semibold">端内延迟</h4>
2058+
<h4 className="text-sm font-semibold">池连接数</h4>
20592059
<Button
20602060
size="sm"
20612061
variant="light"
20622062
isIconOnly
2063-
onPress={() => openFullscreenChart("latency", "端内延迟")}
2063+
onPress={() => openFullscreenChart("pool", "池连接数")}
20642064
className="h-6 w-6 min-w-0"
20652065
>
20662066
<FontAwesomeIcon icon={faExpand} className="text-xs" />
20672067
</Button>
20682068
</CardHeader>
20692069
<CardBody className="pt-0 px-2 pb-2">
20702070
<div className="h-[140px]">
2071-
<LatencyChart
2072-
data={transformLatencyData(metricsData?.data)}
2071+
<PoolChart
2072+
data={transformPoolData(metricsData?.data)}
20732073
height={140}
20742074
loading={metricsLoading && !metricsData}
20752075
error={metricsError || undefined}

components/ui/today-traffic-chart.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,15 @@ export const TodayTrafficChart = React.forwardRef<
9090
);
9191
}
9292

93-
if (chartData.length === 0) {
93+
// 检查是否有数据或者总流量为0
94+
const totalTraffic = chartData.reduce((sum, item) => sum + (item.value as number || 0), 0);
95+
const hasNoTraffic = chartData.length === 0 || totalTraffic === 0;
96+
97+
if (hasNoTraffic) {
9498
return (
9599
<Card
96100
ref={ref}
97-
className={cn("dark:border-default-100 min-h-[340px] border border-transparent", className)}
101+
className={cn("dark:border-default-100 min-h-[340px] border border-transparent bg-gradient-to-br from-default-50/50 to-default-100/30", className)}
98102
{...props}
99103
>
100104
<div className="flex flex-col gap-y-2 p-4 pb-0">
@@ -104,19 +108,31 @@ export const TodayTrafficChart = React.forwardRef<
104108
</dt>
105109
</div>
106110
<dd className="flex items-baseline gap-x-1">
107-
<span className="text-default-900 text-3xl font-semibold">0 B</span>
108-
<span className="text-medium text-default-500 font-medium">{unit}</span>
111+
<span className="text-default-600 text-3xl font-semibold">0 B</span>
112+
<span className="text-medium text-default-400 font-medium">{unit}</span>
109113
</dd>
110114
</div>
111115
<div className="h-[200px] flex items-center justify-center">
112116
<div className="text-center space-y-3">
113-
<Icon icon="solar:chart-2-bold" className="w-12 h-12 text-default-300 mx-auto" />
114-
<div className="space-y-1">
115-
<p className="text-sm font-medium text-default-600">暂无数据</p>
116-
<p className="text-xs text-default-400">今日暂无流量数据</p>
117+
<div className="relative">
118+
<Icon icon="solar:chart-2-linear" className="w-16 h-16 text-default-300 mx-auto" />
119+
</div>
120+
<div className="space-y-2">
121+
<p className="text-sm font-medium text-default-600">暂无流量变化</p>
117122
</div>
118123
</div>
119124
</div>
125+
{/* 空状态下的图例 */}
126+
<div className="text-tiny text-default-400 flex w-full flex-wrap justify-center gap-4 px-4 pb-4">
127+
{categories.map((category, index) => (
128+
<div key={index} className="flex items-center gap-2 opacity-50">
129+
<span
130+
className="h-2 w-2 rounded-full bg-default-300"
131+
/>
132+
<span className="capitalize">{category}</span>
133+
</div>
134+
))}
135+
</div>
120136
</Card>
121137
);
122138
}

components/ui/traffic-overview-chart.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export function TrafficOverviewChart({
183183
key: "tcp-in",
184184
title: "TCP In",
185185
value: currentValues.tcpIn,
186-
suffix: "B/s",
186+
suffix: "",
187187
type: "number" as const,
188188
change: calculateChange(currentValues.tcpIn, previousValues.tcpIn),
189189
changeType: getChangeType(currentValues.tcpIn, previousValues.tcpIn),
@@ -194,7 +194,7 @@ export function TrafficOverviewChart({
194194
key: "tcp-out",
195195
title: "TCP Out",
196196
value: currentValues.tcpOut,
197-
suffix: "B/s",
197+
suffix: "",
198198
type: "number" as const,
199199
change: calculateChange(currentValues.tcpOut, previousValues.tcpOut),
200200
changeType: getChangeType(currentValues.tcpOut, previousValues.tcpOut),
@@ -205,7 +205,7 @@ export function TrafficOverviewChart({
205205
key: "udp-in",
206206
title: "UDP In",
207207
value: currentValues.udpIn,
208-
suffix: "B/s",
208+
suffix: "",
209209
type: "number" as const,
210210
change: calculateChange(currentValues.udpIn, previousValues.udpIn),
211211
changeType: getChangeType(currentValues.udpIn, previousValues.udpIn),
@@ -216,7 +216,7 @@ export function TrafficOverviewChart({
216216
key: "udp-out",
217217
title: "UDP Out",
218218
value: currentValues.udpOut,
219-
suffix: "B/s",
219+
suffix: "",
220220
type: "number" as const,
221221
change: calculateChange(currentValues.udpOut, previousValues.udpOut),
222222
changeType: getChangeType(currentValues.udpOut, previousValues.udpOut),
@@ -427,7 +427,6 @@ export function TrafficOverviewChart({
427427
<>
428428
<span>{formatted.value}</span>
429429
{formatted.unit && <span>{formatted.unit}</span>}
430-
<span>{activeMetricData.metric?.suffix}</span>
431430
</>
432431
);
433432
})()}

internal/sse/history_worker.go

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,8 @@ func (hw *HistoryWorker) dataProcessWorker() {
130130

131131
// processMonitoringData 处理单个监控数据点
132132
// 重要:每个实例独立处理,互不影响
133-
// 每个实例维护自己的30个数据点累积数组和流量差值计算状态
133+
// 每个实例维护自己的数据点累积数组和流量差值计算状态
134134
func (hw *HistoryWorker) processMonitoringData(data MonitoringData) {
135-
// 数据验证
136-
if data.EndpointID <= 0 || data.InstanceID == "" {
137-
log.Warnf("[HistoryWorker]无效的监控数据: 端点ID=%d, 实例ID=%s", data.EndpointID, data.InstanceID)
138-
return
139-
}
140-
141135
// 构建实例唯一键:endpointID_instanceID
142136
key := hw.buildDataKey(data.EndpointID, data.InstanceID)
143137

@@ -147,10 +141,9 @@ func (hw *HistoryWorker) processMonitoringData(data MonitoringData) {
147141
hw.mu.RUnlock()
148142

149143
if !exists {
150-
// 为该实例创建独立的状态数据容器
151-
// 每个实例都有自己的30个数据点累积数组
144+
// 为该实例创建独立的状态数据容器,每个实例都有自己的数据点累积数组
152145
currentStatus = &ServiceCurrentStatus{
153-
Result: make([]MonitoringData, 0, _CurrentStatusSize), // 独立的30个数据点数组
146+
Result: make([]MonitoringData, 0, _CurrentStatusSize), // 独立的数据点数组
154147
}
155148

156149
hw.mu.Lock()
@@ -182,10 +175,9 @@ func (hw *HistoryWorker) processMonitoringData(data MonitoringData) {
182175

183176
log.Debugf("[HistoryWorker]实例 %s 累积数据点: %d/%d", key, resultLength, _CurrentStatusSize)
184177

185-
// 检查该实例是否独立达到累积阈值(30个数据点)
178+
// 检查该实例是否独立达到累积阈值
186179
// 重要:每个实例独立计算,不同实例之间互不影响
187180
if resultLength >= _CurrentStatusSize {
188-
log.Infof("[HistoryWorker]实例 %s 达到累积阈值,触发独立批量写入", key)
189181
hw.triggerBatchWrite(key, currentStatus)
190182
}
191183
}
@@ -227,7 +219,7 @@ func (hw *HistoryWorker) aggregateAndWrite(dataPoints []MonitoringData) {
227219
lastPoint := dataPoints[len(dataPoints)-1]
228220

229221
// 初始化聚合结果
230-
aggregated := &models.ServiceHistory{
222+
historyModel := &models.ServiceHistory{
231223
EndpointID: firstPoint.EndpointID,
232224
InstanceID: firstPoint.InstanceID,
233225
RecordTime: time.Now().Truncate(time.Minute), // 按分钟取整
@@ -236,11 +228,10 @@ func (hw *HistoryWorker) aggregateAndWrite(dataPoints []MonitoringData) {
236228
}
237229

238230
// 1. 存储最后一个数据点的累计值(保持原始int64类型)
239-
aggregated.DeltaTCPIn = lastPoint.TCPIn
240-
aggregated.DeltaTCPOut = lastPoint.TCPOut
241-
aggregated.DeltaUDPIn = lastPoint.UDPIn
242-
aggregated.DeltaUDPOut = lastPoint.UDPOut
243-
231+
historyModel.DeltaTCPIn = lastPoint.TCPIn
232+
historyModel.DeltaTCPOut = lastPoint.TCPOut
233+
historyModel.DeltaUDPIn = lastPoint.UDPIn
234+
historyModel.DeltaUDPOut = lastPoint.UDPOut
244235
// 2. 计算时间跨度
245236
totalTime := lastPoint.Timestamp.Sub(firstPoint.Timestamp).Seconds()
246237

@@ -270,8 +261,8 @@ func (hw *HistoryWorker) aggregateAndWrite(dataPoints []MonitoringData) {
270261
udpOutDelta = float64(lastPoint.UDPOut)
271262
}
272263

273-
aggregated.AvgSpeedIn = (tcpInDelta + udpInDelta) / totalTime
274-
aggregated.AvgSpeedOut = (tcpOutDelta + udpOutDelta) / totalTime
264+
historyModel.AvgSpeedIn = (tcpInDelta + udpInDelta) / totalTime
265+
historyModel.AvgSpeedOut = (tcpOutDelta + udpOutDelta) / totalTime
275266
} else {
276267
// 异常情况:时间差为0,使用理论时间间隔
277268
log.Warnf("[HistoryWorker]时间差为0,使用理论时间间隔计算速度")
@@ -282,11 +273,11 @@ func (hw *HistoryWorker) aggregateAndWrite(dataPoints []MonitoringData) {
282273
udpInDelta := float64(lastPoint.UDPIn - firstPoint.UDPIn)
283274
udpOutDelta := float64(lastPoint.UDPOut - firstPoint.UDPOut)
284275

285-
aggregated.AvgSpeedIn = (tcpInDelta + udpInDelta) / theoreticalTime
286-
aggregated.AvgSpeedOut = (tcpOutDelta + udpOutDelta) / theoreticalTime
276+
historyModel.AvgSpeedIn = (tcpInDelta + udpInDelta) / theoreticalTime
277+
historyModel.AvgSpeedOut = (tcpOutDelta + udpOutDelta) / theoreticalTime
287278
} else {
288-
aggregated.AvgSpeedIn = 0
289-
aggregated.AvgSpeedOut = 0
279+
historyModel.AvgSpeedIn = 0
280+
historyModel.AvgSpeedOut = 0
290281
}
291282
}
292283

@@ -302,35 +293,32 @@ func (hw *HistoryWorker) aggregateAndWrite(dataPoints []MonitoringData) {
302293
}
303294

304295
if pingCount > 0 {
305-
aggregated.AvgPing = pingSum / float64(pingCount)
296+
historyModel.AvgPing = pingSum / float64(pingCount)
306297
} else {
307-
aggregated.AvgPing = 0
298+
historyModel.AvgPing = 0
308299
}
309300

310301
// 5. Pool连接池:直接使用最后一个值(反映最新状态)
311302
if lastPoint.Pool != nil {
312-
aggregated.AvgPool = *lastPoint.Pool
303+
historyModel.AvgPool = *lastPoint.Pool
313304
} else {
314-
aggregated.AvgPool = 0
305+
historyModel.AvgPool = 0
315306
}
316307

317-
log.Infof("[HistoryWorker]聚合完成 - 端点:%d 实例:%s 数据点:%d 时间跨度:%.1fs TCP入累计:%d TCP出累计:%d UDP入累计:%d UDP出累计:%d 延迟平均:%.2fms 连接池最新:%d 入站速度:%.2f bytes/s 出站速度:%.2f bytes/s",
318-
aggregated.EndpointID, aggregated.InstanceID, aggregated.RecordCount, totalTime,
319-
aggregated.DeltaTCPIn, aggregated.DeltaTCPOut, aggregated.DeltaUDPIn, aggregated.DeltaUDPOut,
320-
aggregated.AvgPing, int64(aggregated.AvgPool), aggregated.AvgSpeedIn, aggregated.AvgSpeedOut)
321-
322308
// 6. 立即写入数据库
323309
startTime := time.Now()
324-
err := hw.db.Create(aggregated).Error
310+
err := hw.db.Create(historyModel).Error
325311
duration := time.Since(startTime)
326312

327313
if err != nil {
328314
log.Errorf("[HistoryWorker]立即写入失败 (端点:%d 实例:%s, 耗时%v): %v",
329-
aggregated.EndpointID, aggregated.InstanceID, duration, err)
330-
} else {
331-
log.Infof("[HistoryWorker]立即写入成功: 端点:%d 实例:%s, 数据点:%d, 耗时%v",
332-
aggregated.EndpointID, aggregated.InstanceID, aggregated.RecordCount, duration)
315+
historyModel.EndpointID, historyModel.InstanceID, duration, err)
333316
}
317+
318+
log.Infof("[HistoryWorker]聚合完成 - 端点:%d 实例:%s 数据点:%d 时间跨度:%.1fs TCP入累计:%d TCP出累计:%d UDP入累计:%d UDP出累计:%d 延迟平均:%.2fms 连接池最新:%d 入站速度:%.2f bytes/s 出站速度:%.2f bytes/s",
319+
historyModel.EndpointID, historyModel.InstanceID, historyModel.RecordCount, totalTime,
320+
historyModel.DeltaTCPIn, historyModel.DeltaTCPOut, historyModel.DeltaUDPIn, historyModel.DeltaUDPOut,
321+
historyModel.AvgPing, int64(historyModel.AvgPool), historyModel.AvgSpeedIn, historyModel.AvgSpeedOut)
334322
}
335323

336324
// 移除批量写入相关方法(改为立即写入)

0 commit comments

Comments
 (0)