修复节点详情 OpenResty 连接数与吞吐显示为「—」:节点可观测 API 将 OpenResty 观测数据合并进 metric_snapshots;指标文案改为「请求/分钟」(近 60 秒窗口),连接数为 0 时正常显示 0。

This commit is contained in:
ryan
2026-06-21 10:52:06 +08:00
parent 42896a8473
commit 6e86901a58
9 changed files with 344 additions and 165 deletions
+2
View File
@@ -22,6 +22,8 @@ sidebar: false
### 修复 ### 修复
- 修复节点详情 OpenResty 连接数与吞吐显示为「—」:节点可观测 API 将 OpenResty 观测数据合并进 `metric_snapshots`;指标文案改为「请求/分钟」(近 60 秒窗口),连接数为 0 时正常显示 0。
- 修复仪表盘「24 小时请求趋势」摘要误显示当前小时请求量/错误量:改为汇总近 24 小时总量。 - 修复仪表盘「24 小时请求趋势」摘要误显示当前小时请求量/错误量:改为汇总近 24 小时总量。
- 修复 Pages 部署包上传报「请求超时,请稍后重试」:上传请求使用独立 10 分钟超时(覆盖默认 15 秒),并在大文件上传完成后提示服务端处理中。 - 修复 Pages 部署包上传报「请求超时,请稍后重试」:上传请求使用独立 10 分钟超时(覆盖默认 15 秒),并在大文件上传完成后提示服务端处理中。
+56 -46
View File
@@ -5350,7 +5350,7 @@ const docTemplate = `{
"SessionCookie": [] "SessionCookie": []
} }
], ],
"description": "返回所有已发布的 OpenResty 配置版本摘要,需要管理员权限", "description": "返回所有已发布的 OpenResty 配置版本摘要,按创建时间倒序排列,需要管理员权限",
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -14433,50 +14433,6 @@ const docTemplate = `{
} }
} }
}, },
"model.OpenFlareMetricSnapshot": {
"type": "object",
"properties": {
"captured_at": {
"type": "string"
},
"cpu_usage_percent": {
"type": "number"
},
"created_at": {
"type": "string"
},
"disk_read_bytes": {
"type": "integer"
},
"disk_write_bytes": {
"type": "integer"
},
"id": {
"type": "integer"
},
"memory_total_bytes": {
"type": "integer"
},
"memory_used_bytes": {
"type": "integer"
},
"network_rx_bytes": {
"type": "integer"
},
"network_tx_bytes": {
"type": "integer"
},
"node_id": {
"type": "string"
},
"storage_total_bytes": {
"type": "integer"
},
"storage_used_bytes": {
"type": "integer"
}
}
},
"model.OpenFlareNode": { "model.OpenFlareNode": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -15297,7 +15253,7 @@ const docTemplate = `{
"metric_snapshots": { "metric_snapshots": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/model.OpenFlareMetricSnapshot" "$ref": "#/definitions/observability.NodeMetricSnapshotView"
} }
}, },
"node_id": { "node_id": {
@@ -15928,6 +15884,56 @@ const docTemplate = `{
} }
} }
}, },
"observability.NodeMetricSnapshotView": {
"type": "object",
"properties": {
"captured_at": {
"type": "string"
},
"cpu_usage_percent": {
"type": "number"
},
"disk_read_bytes": {
"type": "integer"
},
"disk_write_bytes": {
"type": "integer"
},
"id": {
"type": "integer"
},
"memory_total_bytes": {
"type": "integer"
},
"memory_used_bytes": {
"type": "integer"
},
"network_rx_bytes": {
"type": "integer"
},
"network_tx_bytes": {
"type": "integer"
},
"node_id": {
"type": "string"
},
"openresty_connections": {
"type": "integer"
},
"openresty_rx_bytes": {
"type": "integer"
},
"openresty_tx_bytes": {
"type": "integer"
},
"storage_total_bytes": {
"type": "integer"
},
"storage_used_bytes": {
"type": "integer"
}
}
},
"observability.NodeTrends": { "observability.NodeTrends": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -16371,6 +16377,10 @@ const docTemplate = `{
}, },
"total_size": { "total_size": {
"type": "integer" "type": "integer"
},
"upload_id": {
"type": "string",
"example": "0"
} }
} }
}, },
+56 -46
View File
@@ -5343,7 +5343,7 @@
"SessionCookie": [] "SessionCookie": []
} }
], ],
"description": "返回所有已发布的 OpenResty 配置版本摘要,需要管理员权限", "description": "返回所有已发布的 OpenResty 配置版本摘要,按创建时间倒序排列,需要管理员权限",
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -14426,50 +14426,6 @@
} }
} }
}, },
"model.OpenFlareMetricSnapshot": {
"type": "object",
"properties": {
"captured_at": {
"type": "string"
},
"cpu_usage_percent": {
"type": "number"
},
"created_at": {
"type": "string"
},
"disk_read_bytes": {
"type": "integer"
},
"disk_write_bytes": {
"type": "integer"
},
"id": {
"type": "integer"
},
"memory_total_bytes": {
"type": "integer"
},
"memory_used_bytes": {
"type": "integer"
},
"network_rx_bytes": {
"type": "integer"
},
"network_tx_bytes": {
"type": "integer"
},
"node_id": {
"type": "string"
},
"storage_total_bytes": {
"type": "integer"
},
"storage_used_bytes": {
"type": "integer"
}
}
},
"model.OpenFlareNode": { "model.OpenFlareNode": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -15290,7 +15246,7 @@
"metric_snapshots": { "metric_snapshots": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/model.OpenFlareMetricSnapshot" "$ref": "#/definitions/observability.NodeMetricSnapshotView"
} }
}, },
"node_id": { "node_id": {
@@ -15921,6 +15877,56 @@
} }
} }
}, },
"observability.NodeMetricSnapshotView": {
"type": "object",
"properties": {
"captured_at": {
"type": "string"
},
"cpu_usage_percent": {
"type": "number"
},
"disk_read_bytes": {
"type": "integer"
},
"disk_write_bytes": {
"type": "integer"
},
"id": {
"type": "integer"
},
"memory_total_bytes": {
"type": "integer"
},
"memory_used_bytes": {
"type": "integer"
},
"network_rx_bytes": {
"type": "integer"
},
"network_tx_bytes": {
"type": "integer"
},
"node_id": {
"type": "string"
},
"openresty_connections": {
"type": "integer"
},
"openresty_rx_bytes": {
"type": "integer"
},
"openresty_tx_bytes": {
"type": "integer"
},
"storage_total_bytes": {
"type": "integer"
},
"storage_used_bytes": {
"type": "integer"
}
}
},
"observability.NodeTrends": { "observability.NodeTrends": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -16364,6 +16370,10 @@
}, },
"total_size": { "total_size": {
"type": "integer" "type": "integer"
},
"upload_id": {
"type": "string",
"example": "0"
} }
} }
}, },
+38 -31
View File
@@ -1130,35 +1130,6 @@ definitions:
updated_at: updated_at:
type: string type: string
type: object type: object
model.OpenFlareMetricSnapshot:
properties:
captured_at:
type: string
cpu_usage_percent:
type: number
created_at:
type: string
disk_read_bytes:
type: integer
disk_write_bytes:
type: integer
id:
type: integer
memory_total_bytes:
type: integer
memory_used_bytes:
type: integer
network_rx_bytes:
type: integer
network_tx_bytes:
type: integer
node_id:
type: string
storage_total_bytes:
type: integer
storage_used_bytes:
type: integer
type: object
model.OpenFlareNode: model.OpenFlareNode:
properties: properties:
auto_update_enabled: auto_update_enabled:
@@ -1713,7 +1684,7 @@ definitions:
type: array type: array
metric_snapshots: metric_snapshots:
items: items:
$ref: '#/definitions/model.OpenFlareMetricSnapshot' $ref: '#/definitions/observability.NodeMetricSnapshotView'
type: array type: array
node_id: node_id:
type: string type: string
@@ -2126,6 +2097,39 @@ definitions:
traffic: traffic:
$ref: '#/definitions/observability.TrafficWindowSummary' $ref: '#/definitions/observability.TrafficWindowSummary'
type: object type: object
observability.NodeMetricSnapshotView:
properties:
captured_at:
type: string
cpu_usage_percent:
type: number
disk_read_bytes:
type: integer
disk_write_bytes:
type: integer
id:
type: integer
memory_total_bytes:
type: integer
memory_used_bytes:
type: integer
network_rx_bytes:
type: integer
network_tx_bytes:
type: integer
node_id:
type: string
openresty_connections:
type: integer
openresty_rx_bytes:
type: integer
openresty_tx_bytes:
type: integer
storage_total_bytes:
type: integer
storage_used_bytes:
type: integer
type: object
observability.NodeTrends: observability.NodeTrends:
properties: properties:
capacity_24h: capacity_24h:
@@ -2416,6 +2420,9 @@ definitions:
type: string type: string
total_size: total_size:
type: integer type: integer
upload_id:
example: "0"
type: string
type: object type: object
pages.Input: pages.Input:
properties: properties:
@@ -7125,7 +7132,7 @@ paths:
- openflare-apply-log - openflare-apply-log
/api/v1/d/config-versions: /api/v1/d/config-versions:
get: get:
description: 返回所有已发布的 OpenResty 配置版本摘要,需要管理员权限 description: 返回所有已发布的 OpenResty 配置版本摘要,按创建时间倒序排列,需要管理员权限
produces: produces:
- application/json - application/json
responses: responses:
@@ -31,6 +31,7 @@ import {
aggregateTrafficBreakdown, aggregateTrafficBreakdown,
formatBytes, formatBytes,
formatBytesPerSecond, formatBytesPerSecond,
formatMetricCount,
formatPercent, formatPercent,
formatRelativeTime, formatRelativeTime,
formatUptime, formatUptime,
@@ -455,11 +456,7 @@ export function NodeObservability({
/> />
<MetricBar <MetricBar
label="连接数" label="连接数"
value={ value={formatMetricCount(latestMetric.openresty_connections)}
latestMetric.openresty_connections
? String(latestMetric.openresty_connections)
: '—'
}
progress={null} progress={null}
hint={connectionHint} hint={connectionHint}
/> />
@@ -521,11 +518,11 @@ export function NodeObservability({
} }
/> />
<SummaryStat <SummaryStat
label="当前窗口请求" label="请求/分钟"
value={trafficSummary ? trafficSummary.request_count.toLocaleString('zh-CN') : '—'} value={formatMetricCount(trafficSummary?.request_count)}
hint={ hint={
trafficSummary trafficSummary
? `QPS ${trafficSummary.estimated_qps.toFixed(1)} · 错误率 ${trafficSummary.error_rate_percent.toFixed(1)}%` ? `近 60 秒 · 错误率 ${trafficSummary.error_rate_percent.toFixed(1)}%`
: '当前没有可展示的请求窗口摘要' : '当前没有可展示的请求窗口摘要'
} }
/> />
@@ -594,11 +591,7 @@ export function NodeObservability({
/> />
<MetricBar <MetricBar
label="连接数" label="连接数"
value={ value={formatMetricCount(latestMetric.openresty_connections)}
latestMetric.openresty_connections
? String(latestMetric.openresty_connections)
: '—'
}
progress={null} progress={null}
hint={connectionHint} hint={connectionHint}
/> />
@@ -661,25 +654,23 @@ export function NodeObservability({
<div className="grid gap-3 sm:grid-cols-2"> <div className="grid gap-3 sm:grid-cols-2">
<div className="rounded-lg border px-3 py-3"> <div className="rounded-lg border px-3 py-3">
<p className="text-xs text-muted-foreground uppercase tracking-wide"> <p className="text-xs text-muted-foreground uppercase tracking-wide">
最近窗口请求 请求/分钟
</p> </p>
<p className="mt-3 text-2xl font-semibold"> <p className="mt-3 text-2xl font-semibold">
{trafficSummary {formatMetricCount(trafficSummary?.request_count)}
? trafficSummary.request_count.toLocaleString('zh-CN')
: '—'}
</p> </p>
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
{trafficSummary {trafficSummary
? `QPS ${trafficSummary.estimated_qps.toFixed(1)} · UV ${trafficSummary.unique_visitor_count}` ? `近 60 秒 · UV ${formatMetricCount(trafficSummary.unique_visitor_count)}`
: '暂无窗口流量摘要'} : '暂无窗口流量摘要'}
</p> </p>
</div> </div>
<div className="rounded-lg border px-3 py-3"> <div className="rounded-lg border px-3 py-3">
<p className="text-xs text-muted-foreground uppercase tracking-wide"> <p className="text-xs text-muted-foreground uppercase tracking-wide">
最近窗口错误 近 60 秒错误
</p> </p>
<p className="mt-3 text-2xl font-semibold"> <p className="mt-3 text-2xl font-semibold">
{trafficSummary ? trafficSummary.error_count.toLocaleString('zh-CN') : '—'} {formatMetricCount(trafficSummary?.error_count)}
</p> </p>
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
{trafficSummary {trafficSummary
@@ -1,10 +1,4 @@
import type { import type {ApplyResult, NodeItem, NodeStatus, NodeTrafficReport, OpenrestyStatus,} from '@/lib/services/openflare';
ApplyResult,
NodeItem,
NodeStatus,
NodeTrafficReport,
OpenrestyStatus,
} from '@/lib/services/openflare';
export const WS_CONNECTED_LAST_SEEN = '__OPENFLARE_WS_CONNECTED__'; export const WS_CONNECTED_LAST_SEEN = '__OPENFLARE_WS_CONNECTED__';
export const FLARED_WS_CONNECTED_LAST_SEEN = '__OPENFLARE_FLARED_WS_CONNECTED__'; export const FLARED_WS_CONNECTED_LAST_SEEN = '__OPENFLARE_FLARED_WS_CONNECTED__';
@@ -176,6 +170,13 @@ export function formatPercent(value?: number | null) {
return `${value.toFixed(1)}%`; return `${value.toFixed(1)}%`;
} }
export function formatMetricCount(value?: number | null) {
if (value === undefined || value === null || !Number.isFinite(value)) {
return '—';
}
return value.toLocaleString('zh-CN');
}
export function formatBytesPerSecond( export function formatBytesPerSecond(
value?: number | null, value?: number | null,
windowSeconds = 1, windowSeconds = 1,
@@ -35,6 +35,27 @@ type TrafficDistributions struct {
SourceCountries []DistributionItem `json:"source_countries"` SourceCountries []DistributionItem `json:"source_countries"`
} }
const metricSnapshotOpenrestyMatchWindow = 2 * time.Minute
// NodeMetricSnapshotView is a metric snapshot enriched with OpenResty observations.
type NodeMetricSnapshotView struct {
ID uint `json:"id,omitempty"`
NodeID string `json:"node_id,omitempty"`
CapturedAt time.Time `json:"captured_at"`
CPUUsagePercent float64 `json:"cpu_usage_percent"`
MemoryUsedBytes int64 `json:"memory_used_bytes"`
MemoryTotalBytes int64 `json:"memory_total_bytes"`
StorageUsedBytes int64 `json:"storage_used_bytes"`
StorageTotalBytes int64 `json:"storage_total_bytes"`
DiskReadBytes int64 `json:"disk_read_bytes"`
DiskWriteBytes int64 `json:"disk_write_bytes"`
NetworkRxBytes int64 `json:"network_rx_bytes"`
NetworkTxBytes int64 `json:"network_tx_bytes"`
OpenrestyRxBytes int64 `json:"openresty_rx_bytes"`
OpenrestyTxBytes int64 `json:"openresty_tx_bytes"`
OpenrestyConnections int64 `json:"openresty_connections"`
}
// TrafficWindowSummary summarizes a traffic reporting window. // TrafficWindowSummary summarizes a traffic reporting window.
type TrafficWindowSummary struct { type TrafficWindowSummary struct {
WindowStartedAt time.Time `json:"window_started_at"` WindowStartedAt time.Time `json:"window_started_at"`
@@ -112,9 +133,9 @@ type diskCounterState struct {
seen bool seen bool
} }
func buildTrafficWindowSummary(report *model.OpenFlareRequestReport) TrafficWindowSummary { func buildTrafficWindowSummary(report *model.OpenFlareRequestReport) *TrafficWindowSummary {
if report == nil { if report == nil {
return TrafficWindowSummary{} return nil
} }
summary := TrafficWindowSummary{ summary := TrafficWindowSummary{
WindowStartedAt: report.WindowStartedAt, WindowStartedAt: report.WindowStartedAt,
@@ -129,7 +150,44 @@ func buildTrafficWindowSummary(report *model.OpenFlareRequestReport) TrafficWind
if report.RequestCount > 0 { if report.RequestCount > 0 {
summary.ErrorRatePercent = (float64(report.ErrorCount) / float64(report.RequestCount)) * 100 summary.ErrorRatePercent = (float64(report.ErrorCount) / float64(report.RequestCount)) * 100
} }
return summary return &summary
}
// BuildMetricSnapshotViews merges metric snapshots with OpenResty observations for API responses.
func BuildMetricSnapshotViews(
snapshots []*model.OpenFlareMetricSnapshot,
openrestyObs []*model.OpenFlareNodeObservationOpenresty,
) []*NodeMetricSnapshotView {
if len(snapshots) == 0 {
return []*NodeMetricSnapshotView{}
}
views := make([]*NodeMetricSnapshotView, 0, len(snapshots))
for _, snapshot := range snapshots {
if snapshot == nil {
continue
}
view := &NodeMetricSnapshotView{
ID: snapshot.ID,
NodeID: snapshot.NodeID,
CapturedAt: snapshot.CapturedAt,
CPUUsagePercent: snapshot.CPUUsagePercent,
MemoryUsedBytes: snapshot.MemoryUsedBytes,
MemoryTotalBytes: snapshot.MemoryTotalBytes,
StorageUsedBytes: snapshot.StorageUsedBytes,
StorageTotalBytes: snapshot.StorageTotalBytes,
DiskReadBytes: snapshot.DiskReadBytes,
DiskWriteBytes: snapshot.DiskWriteBytes,
NetworkRxBytes: snapshot.NetworkRxBytes,
NetworkTxBytes: snapshot.NetworkTxBytes,
}
if matched := matchOpenrestyObservation(snapshot.CapturedAt, openrestyObs); matched != nil {
view.OpenrestyRxBytes = matched.OpenrestyRxBytes
view.OpenrestyTxBytes = matched.OpenrestyTxBytes
view.OpenrestyConnections = matched.OpenrestyConnections
}
views = append(views, view)
}
return views
} }
// BuildTrafficDistributions aggregates traffic distribution charts. // BuildTrafficDistributions aggregates traffic distribution charts.
@@ -352,21 +410,54 @@ func BuildDiskIOTrendPoints(now time.Time, snapshots []*model.OpenFlareMetricSna
} }
func latestMetricSnapshot(snapshots []*model.OpenFlareMetricSnapshot) *model.OpenFlareMetricSnapshot { func latestMetricSnapshot(snapshots []*model.OpenFlareMetricSnapshot) *model.OpenFlareMetricSnapshot {
var latest *model.OpenFlareMetricSnapshot
for _, snapshot := range snapshots { for _, snapshot := range snapshots {
if snapshot != nil { if snapshot == nil {
return snapshot continue
}
if latest == nil || snapshot.CapturedAt.After(latest.CapturedAt) {
latest = snapshot
} }
} }
return nil return latest
} }
func latestTrafficReport(reports []*model.OpenFlareRequestReport) *model.OpenFlareRequestReport { func latestTrafficReport(reports []*model.OpenFlareRequestReport) *model.OpenFlareRequestReport {
var latest *model.OpenFlareRequestReport
for _, report := range reports { for _, report := range reports {
if report != nil { if report == nil {
return report continue
}
if latest == nil || report.WindowEndedAt.After(latest.WindowEndedAt) {
latest = report
} }
} }
return nil return latest
}
func matchOpenrestyObservation(
capturedAt time.Time,
observations []*model.OpenFlareNodeObservationOpenresty,
) *model.OpenFlareNodeObservationOpenresty {
var matched *model.OpenFlareNodeObservationOpenresty
var bestDelta time.Duration = metricSnapshotOpenrestyMatchWindow + time.Second
for _, observation := range observations {
if observation == nil {
continue
}
delta := capturedAt.Sub(observation.CapturedAt)
if delta < 0 {
delta = -delta
}
if delta > metricSnapshotOpenrestyMatchWindow {
continue
}
if matched == nil || delta < bestDelta {
matched = observation
bestDelta = delta
}
}
return matched
} }
// LatestMetricSnapshotsByNode returns the latest snapshot per node. // LatestMetricSnapshotsByNode returns the latest snapshot per node.
@@ -51,4 +51,67 @@ func TestBuildTrafficTrendPointsBucketsByHour(t *testing.T) {
if currentHour.ErrorCount != 0 { if currentHour.ErrorCount != 0 {
t.Fatalf("current hour error_count = %d, want 0", currentHour.ErrorCount) t.Fatalf("current hour error_count = %d, want 0", currentHour.ErrorCount)
} }
} }
func TestBuildMetricSnapshotViewsMergesOpenrestyObservation(t *testing.T) {
t.Parallel()
capturedAt := time.Date(2026, 6, 19, 12, 0, 0, 0, time.UTC)
snapshots := []*model.OpenFlareMetricSnapshot{
{
ID: 1,
NodeID: "node-a",
CapturedAt: capturedAt,
CPUUsagePercent: 12.5,
},
}
openrestyObs := []*model.OpenFlareNodeObservationOpenresty{
{
NodeID: "node-a",
CapturedAt: capturedAt.Add(5 * time.Second),
OpenrestyRxBytes: 4096,
OpenrestyTxBytes: 8192,
OpenrestyConnections: 7,
},
}
views := BuildMetricSnapshotViews(snapshots, openrestyObs)
if len(views) != 1 {
t.Fatalf("BuildMetricSnapshotViews() len = %d, want 1", len(views))
}
if views[0].OpenrestyRxBytes != 4096 {
t.Fatalf("OpenrestyRxBytes = %d, want 4096", views[0].OpenrestyRxBytes)
}
if views[0].OpenrestyTxBytes != 8192 {
t.Fatalf("OpenrestyTxBytes = %d, want 8192", views[0].OpenrestyTxBytes)
}
if views[0].OpenrestyConnections != 7 {
t.Fatalf("OpenrestyConnections = %d, want 7", views[0].OpenrestyConnections)
}
}
func TestLatestTrafficReportUsesLatestWindowEndedAt(t *testing.T) {
t.Parallel()
older := &model.OpenFlareRequestReport{
WindowEndedAt: time.Date(2026, 6, 19, 10, 0, 0, 0, time.UTC),
RequestCount: 3,
}
newer := &model.OpenFlareRequestReport{
WindowEndedAt: time.Date(2026, 6, 19, 11, 0, 0, 0, time.UTC),
RequestCount: 9,
}
latest := latestTrafficReport([]*model.OpenFlareRequestReport{older, newer})
if latest == nil || latest.RequestCount != 9 {
t.Fatalf("latestTrafficReport() = %#v, want newer report with request_count 9", latest)
}
}
func TestBuildTrafficWindowSummaryNilWithoutReport(t *testing.T) {
t.Parallel()
if summary := buildTrafficWindowSummary(nil); summary != nil {
t.Fatalf("buildTrafficWindowSummary(nil) = %#v, want nil", summary)
}
}
@@ -28,9 +28,9 @@ type NodeQuery struct {
// NodeAnalytics groups node observability analytics. // NodeAnalytics groups node observability analytics.
type NodeAnalytics struct { type NodeAnalytics struct {
Traffic TrafficWindowSummary `json:"traffic"` Traffic *TrafficWindowSummary `json:"traffic"`
Distributions TrafficDistributions `json:"distributions"` Distributions TrafficDistributions `json:"distributions"`
Health HealthSummary `json:"health"` Health HealthSummary `json:"health"`
} }
// NodeTrends groups node observability trend series. // NodeTrends groups node observability trend series.
@@ -66,7 +66,7 @@ type RelayProxyStat struct {
type NodeView struct { type NodeView struct {
NodeID string `json:"node_id"` NodeID string `json:"node_id"`
Profile *model.OpenFlareNodeSystemProfile `json:"profile"` Profile *model.OpenFlareNodeSystemProfile `json:"profile"`
MetricSnapshots []*model.OpenFlareMetricSnapshot `json:"metric_snapshots"` MetricSnapshots []*NodeMetricSnapshotView `json:"metric_snapshots"`
TrafficReports []*model.OpenFlareRequestReport `json:"traffic_reports"` TrafficReports []*model.OpenFlareRequestReport `json:"traffic_reports"`
HealthEvents []*model.OpenFlareHealthEvent `json:"health_events"` HealthEvents []*model.OpenFlareHealthEvent `json:"health_events"`
Analytics NodeAnalytics `json:"analytics"` Analytics NodeAnalytics `json:"analytics"`
@@ -103,6 +103,10 @@ func GetNodeObservability(ctx context.Context, id uint, query NodeQuery) (*NodeV
if err != nil { if err != nil {
return nil, err return nil, err
} }
openrestyObs, err := model.ListOpenFlareNodeObservationOpenresty(ctx, node.NodeID, since, limit)
if err != nil {
return nil, err
}
reports, err := model.ListOpenFlareRequestReportsSince(ctx, node.NodeID, since, limit) reports, err := model.ListOpenFlareRequestReportsSince(ctx, node.NodeID, since, limit)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -131,7 +135,7 @@ func GetNodeObservability(ctx context.Context, id uint, query NodeQuery) (*NodeV
view := &NodeView{ view := &NodeView{
NodeID: node.NodeID, NodeID: node.NodeID,
Profile: profile, Profile: profile,
MetricSnapshots: snapshots, MetricSnapshots: BuildMetricSnapshotViews(snapshots, openrestyObs),
TrafficReports: reports, TrafficReports: reports,
HealthEvents: events, HealthEvents: events,
Analytics: NodeAnalytics{ Analytics: NodeAnalytics{