From bf71bc540bf9f6e0ec514e680805e79c905b5808 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 18 Jul 2026 21:41:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(access-logs):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=A6=82=E8=A7=88=E9=A5=BC=E5=9B=BE=E5=B8=83=E5=B1=80=E5=B9=B6?= =?UTF-8?q?=E5=9C=A8=E6=9F=A5=E8=AF=A2=E5=87=BA=E9=94=99=E6=97=B6=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将设备类型与状态码饼图的断点由 xl 降为 lg,在大屏/笔记本视口下保持双列展示 - 修复 valueCountDistribution 在 ClickHouse 查询出错时静默吞掉错误的缺陷,引入 logger.ErrorF 捕获 - 补充 unreleased 变更日志 --- docs/changelog/index.md | 3 + .../access-logs/components/overview-tab.tsx | 48 ++------------- frontend/components/data/rank-card.tsx | 48 +++++++++++++++ frontend/components/data/rank-chart.tsx | 58 ++++++++++++------- .../observability/access_log_logics.go | 15 +++-- .../repository/analytics/node_access_log.go | 8 ++- 6 files changed, 109 insertions(+), 71 deletions(-) create mode 100644 frontend/components/data/rank-card.tsx diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 466e5df6..3974c93f 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -23,8 +23,11 @@ sidebar: false ### 变更 +- 优化访问日志概览及其他图表全局排行榜 (RankChart) 的样式布局:将每一项改为单行横向排布(左侧标签、中间进度条、右侧数值),数值支持自动格式化为 Compact 形式(如 39.57k、1.2M),同时调整默认高度为 320px 并隐藏滚动条。 - 访问日志页面重构为「概览」与「日志明细」两个标签页:概览展示请求量、访问量、带宽趋势与 Top Paths/Hosts/IPs,明细列表保留检索能力;已移除时间折叠与 IP 汇总视图。 - 边缘访问日志现支持上报并存储 User-Agent;概览新增设备类型饼图,以及浏览器、操作系统、User-Agent 排行。 +- 优化访问日志概览的设备类型与状态码饼图布局,使设备类型与状态码饼图在 1024px(lg)以上屏幕保持双列并排展示,提升页面紧凑度。 +- 修复访问日志概览在 ClickHouse 查询发生错误时静默吞掉报错的缺陷,引入错误日志输出,方便定位问题。 ## [v3.4.0] - 2026-07-18 diff --git a/frontend/app/(main)/access-logs/components/overview-tab.tsx b/frontend/app/(main)/access-logs/components/overview-tab.tsx index 5fd8b510..e1ede172 100644 --- a/frontend/app/(main)/access-logs/components/overview-tab.tsx +++ b/frontend/app/(main)/access-logs/components/overview-tab.tsx @@ -5,7 +5,7 @@ import type { EChartsOption } from 'echarts'; import ReactECharts from 'echarts-for-react'; import { Cell, Pie, PieChart } from 'recharts'; -import { RankChart } from '@/components/data/rank-chart'; +import { RankCard } from '@/components/data/rank-card'; import { TrendChart } from '@/components/data/trend-chart'; import { EmptyStateWithBorder } from '@/components/layout/empty'; import { ErrorInline } from '@/components/layout/error'; @@ -160,38 +160,6 @@ function toRankItems(items: DistributionItem[] | undefined) { })); } -function RankCard({ - title, - description, - items, - color, -}: { - title: string; - description: string; - items: { label: string; value: number }[]; - color: string; -}) { - return ( - - - - {title} - - - {description} - - - - - - - ); -} - function PieDistributionCard({ title, description, @@ -236,13 +204,13 @@ function PieDistributionCard({ {chartData.length === 0 ? ( -
+
{emptyMessage}
) : ( {chartData.map((entry) => ( @@ -456,19 +424,16 @@ function OverviewContent({
@@ -477,19 +442,16 @@ function OverviewContent({ diff --git a/frontend/components/data/rank-card.tsx b/frontend/components/data/rank-card.tsx new file mode 100644 index 00000000..dde35466 --- /dev/null +++ b/frontend/components/data/rank-card.tsx @@ -0,0 +1,48 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; + +import { RankChart } from './rank-chart'; + +type RankCardItem = { + label: string; + value: number; +}; + +type RankCardProps = { + title: string; + description: string; + items: RankCardItem[]; + color?: string; +}; + +export function RankCard({ + title, + description, + items, + color = '#3b82f6', +}: RankCardProps) { + return ( + + + + {title} + + + {description} + + + + + + + ); +} diff --git a/frontend/components/data/rank-chart.tsx b/frontend/components/data/rank-chart.tsx index 4921c853..d0162b35 100644 --- a/frontend/components/data/rank-chart.tsx +++ b/frontend/components/data/rank-chart.tsx @@ -17,7 +17,15 @@ type RankChartProps = { className?: string; }; -const defaultFormatter = (value: number) => value.toLocaleString('zh-CN'); +const defaultFormatter = (value: number) => { + if (value >= 1000000) { + return `${Number((value / 1000000).toFixed(2))}M`; + } + if (value >= 1000) { + return `${Number((value / 1000).toFixed(2))}k`; + } + return value.toLocaleString('zh-CN'); +}; export function RankChart({ items, @@ -35,7 +43,7 @@ export function RankChart({ return (
@@ -45,31 +53,39 @@ export function RankChart({ } return ( -
+
{items.map((item) => { const ratio = maxValue > 0 ? (item.value / maxValue) * 100 : 0; return ( -
-
- - {item.label} - - +
+ + {item.label} + +
+
+
+
+ {valueFormatter(item.value)}
-
-
-
); })} diff --git a/internal/apps/openflare/observability/access_log_logics.go b/internal/apps/openflare/observability/access_log_logics.go index 634d3c4c..bc27af77 100644 --- a/internal/apps/openflare/observability/access_log_logics.go +++ b/internal/apps/openflare/observability/access_log_logics.go @@ -10,6 +10,7 @@ import ( "github.com/Rain-kl/Wavelet/internal/model" analyticsrepo "github.com/Rain-kl/Wavelet/internal/repository/analytics" + "github.com/Rain-kl/Wavelet/pkg/logger" ) const ( @@ -236,10 +237,10 @@ type AccessLogCleanupResult struct { } const ( - defaultAccessLogOverviewHours = 24 - maxAccessLogOverviewHours = 24 * 30 - accessLogOverviewTopLimit = 10 - accessLogOverviewUASampleLimit = 200 + defaultAccessLogOverviewHours = 24 + maxAccessLogOverviewHours = 24 * 30 + accessLogOverviewTopLimit = 10 + accessLogOverviewUASampleLimit = 200 ) // GetAccessLogOverview returns summary metrics, trends, and top rankings. @@ -310,7 +311,11 @@ func valueCountDistribution( limit int, ) []DistributionItem { rows, err := model.ValueCountsOpenFlareAccessLogs(ctx, query, column, limit) - if err != nil || len(rows) == 0 { + if err != nil { + logger.ErrorF(ctx, "[AccessLog] ValueCountsOpenFlareAccessLogs failed for column %s: %v", column, err) + return []DistributionItem{} + } + if len(rows) == 0 { return []DistributionItem{} } items := make([]DistributionItem, 0, len(rows)) diff --git a/internal/repository/analytics/node_access_log.go b/internal/repository/analytics/node_access_log.go index d0a8a661..a53ca80a 100644 --- a/internal/repository/analytics/node_access_log.go +++ b/internal/repository/analytics/node_access_log.go @@ -221,12 +221,16 @@ func ValueCountsNodeAccessLogs(ctx context.Context, filter NodeAccessLogFilter, } clause, args := buildNodeAccessLogFilterClause(filter) tableName := nodeAccessLogTableName() + filterExpr := valueExpr + " != ''" + if col == nodeAccessLogColumnStatusCode { + filterExpr = "status_code >= 0" + } sql := fmt.Sprintf(` SELECT %s AS value, count() AS count FROM %s -WHERE %s AND %s != '' +WHERE %s AND %s GROUP BY value -ORDER BY count DESC, value ASC`, valueExpr, tableName, clause, valueExpr) +ORDER BY count DESC, value ASC`, valueExpr, tableName, clause, filterExpr) if limit > 0 { sql += clickHouseLimitClause args = append(args, limit)