@cybermem/dashboard 0.9.12 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +3 -3
- package/app/api/audit-logs/route.ts +12 -6
- package/app/api/health/route.ts +2 -1
- package/app/api/mcp-config/route.ts +128 -0
- package/app/api/metrics/route.ts +22 -70
- package/app/api/settings/route.ts +125 -30
- package/app/page.tsx +105 -127
- package/components/dashboard/{chart-card.tsx → charts/chart-card.tsx} +13 -19
- package/components/dashboard/{metrics-chart.tsx → charts/memory-chart.tsx} +1 -1
- package/components/dashboard/charts-section.tsx +3 -3
- package/components/dashboard/header.tsx +177 -176
- package/components/dashboard/{audit-log-table.tsx → logs/log-viewer.tsx} +12 -7
- package/components/dashboard/mcp/config-preview.tsx +246 -0
- package/components/dashboard/mcp/platform-selector.tsx +96 -0
- package/components/dashboard/mcp-config-modal.tsx +97 -503
- package/components/dashboard/{metric-card.tsx → metrics/stat-card.tsx} +4 -2
- package/components/dashboard/metrics-grid.tsx +10 -2
- package/components/dashboard/settings/access-token-section.tsx +131 -0
- package/components/dashboard/settings/data-management-section.tsx +122 -0
- package/components/dashboard/settings/system-info-section.tsx +98 -0
- package/components/dashboard/settings-modal.tsx +55 -299
- package/e2e/api.spec.ts +219 -0
- package/e2e/routing.spec.ts +39 -0
- package/e2e/ui.spec.ts +373 -0
- package/lib/data/dashboard-context.tsx +96 -29
- package/lib/data/types.ts +32 -38
- package/middleware.ts +31 -13
- package/package.json +6 -1
- package/playwright.config.ts +23 -58
- package/public/clients.json +5 -3
- package/release-reports/assets/local/1_dashboard.png +0 -0
- package/release-reports/assets/local/2_audit_logs.png +0 -0
- package/release-reports/assets/local/3_charts.png +0 -0
- package/release-reports/assets/local/4_mcp_modal.png +0 -0
- package/release-reports/assets/local/5_settings_modal.png +0 -0
- package/lib/data/demo-strategy.ts +0 -110
- package/lib/data/production-strategy.ts +0 -191
- package/lib/prometheus/client.ts +0 -58
- package/lib/prometheus/index.ts +0 -6
- package/lib/prometheus/metrics.ts +0 -234
- package/lib/prometheus/sparklines.ts +0 -71
- package/lib/prometheus/timeseries.ts +0 -305
- package/lib/prometheus/utils.ts +0 -176
package/lib/prometheus/utils.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { PrometheusQueryResult } from './client'
|
|
2
|
-
|
|
3
|
-
export function parseDuration(duration: string): number {
|
|
4
|
-
const match = duration.match(/^(\d+)([smhdwMYy])$/)
|
|
5
|
-
if (!match) return 3600
|
|
6
|
-
|
|
7
|
-
const [, amount, unit] = match
|
|
8
|
-
const multipliers: Record<string, number> = {
|
|
9
|
-
s: 1,
|
|
10
|
-
m: 60,
|
|
11
|
-
h: 3600,
|
|
12
|
-
d: 86400,
|
|
13
|
-
w: 604800, // 7 days
|
|
14
|
-
M: 2592000, // 30 days (approximation)
|
|
15
|
-
Y: 31536000, // 365 days
|
|
16
|
-
y: 31536000
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return parseInt(amount) * (multipliers[unit] || 3600)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function chooseStep(duration: string): string {
|
|
23
|
-
const seconds = parseDuration(duration)
|
|
24
|
-
if (seconds <= 3600) return '4m' // 1h -> 4m (15 pts). More reliable than 2m.
|
|
25
|
-
if (seconds <= 6 * 3600) return '5m' // 6h -> 5m (72 pts)
|
|
26
|
-
if (seconds <= 24 * 3600) return '15m' // 24h -> 15m (96 pts)
|
|
27
|
-
if (seconds <= 7 * 86400) return '1h' // 7d -> 1h (168 pts)
|
|
28
|
-
return '4h' // >7d -> 4h (e.g. 90d -> 540 pts)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// PromQL doesn't support M/Y, map them to days so query_range stays valid
|
|
32
|
-
export function toPromDuration(duration: string): string {
|
|
33
|
-
const match = duration.match(/^(\d+)([smhdwMyY])$/)
|
|
34
|
-
if (!match) return duration
|
|
35
|
-
const amount = parseInt(match[1])
|
|
36
|
-
const unit = match[2]
|
|
37
|
-
if (unit === 'M') return `${amount * 30}d`
|
|
38
|
-
if (unit === 'Y' || unit === 'y') return `${amount * 365}d`
|
|
39
|
-
return `${amount}${unit}`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Improved version that handles the map lookup correctly
|
|
43
|
-
export function fillSparklineData(values: Array<[number, string]>, start: number, end: number, step: number, defaultValue: number = 0): number[] {
|
|
44
|
-
const map = new Map<number, number>()
|
|
45
|
-
values.forEach(([t, v]) => map.set(t, parseFloat(v)))
|
|
46
|
-
|
|
47
|
-
const result: number[] = []
|
|
48
|
-
|
|
49
|
-
for (let t = start; t <= end; t += step) {
|
|
50
|
-
// Allow for small jitter (1s)
|
|
51
|
-
let val = defaultValue
|
|
52
|
-
for (let offset = -1; offset <= 1; offset++) {
|
|
53
|
-
if (map.has(t + offset)) {
|
|
54
|
-
val = map.get(t + offset)!
|
|
55
|
-
break
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
result.push(val)
|
|
59
|
-
}
|
|
60
|
-
return result
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Format range query results (already aggregated) into series by client
|
|
64
|
-
export function formatRangeSeriesByClient(
|
|
65
|
-
result: PrometheusQueryResult,
|
|
66
|
-
start: number,
|
|
67
|
-
end: number,
|
|
68
|
-
stepSeconds: number,
|
|
69
|
-
allClients?: string[],
|
|
70
|
-
labelKey: string = 'client_name',
|
|
71
|
-
isCumulative: boolean = true,
|
|
72
|
-
integrate: boolean = false
|
|
73
|
-
): Array<{ time: number, [client: string]: number }> {
|
|
74
|
-
const timeMap = new Map<number, Record<string, number>>()
|
|
75
|
-
|
|
76
|
-
// Initialize all timestamps with 0s
|
|
77
|
-
for (let t = start; t <= end; t += stepSeconds) {
|
|
78
|
-
timeMap.set(t, {})
|
|
79
|
-
if (allClients) {
|
|
80
|
-
allClients.forEach(client => {
|
|
81
|
-
timeMap.get(t)![client] = 0
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
result.data.result.forEach((series) => {
|
|
87
|
-
const clientName = series.metric[labelKey] || 'unknown'
|
|
88
|
-
const values = series.values || []
|
|
89
|
-
values.forEach(([timestamp, value]) => {
|
|
90
|
-
// Find closest timestamp in our map (to handle slight jitter)
|
|
91
|
-
let targetTs = timestamp
|
|
92
|
-
if (!timeMap.has(timestamp)) {
|
|
93
|
-
// Try to find within 1 second
|
|
94
|
-
for (let offset = -1; offset <= 1; offset++) {
|
|
95
|
-
if (timeMap.has(timestamp + offset)) {
|
|
96
|
-
targetTs = timestamp + offset
|
|
97
|
-
break
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (timeMap.has(targetTs)) {
|
|
103
|
-
timeMap.get(targetTs)![clientName] = parseFloat(value)
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
// Fill in zeros for all clients at all timestamps
|
|
109
|
-
if (allClients && allClients.length > 0) {
|
|
110
|
-
// Iterate over all timestamps in the map
|
|
111
|
-
Array.from(timeMap.keys()).forEach((timestamp) => {
|
|
112
|
-
const timestampData = timeMap.get(timestamp)!
|
|
113
|
-
allClients.forEach((client) => {
|
|
114
|
-
if (!(client in timestampData)) {
|
|
115
|
-
timestampData[client] = 0
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const sortedSeries = Array.from(timeMap.entries())
|
|
122
|
-
.sort((a, b) => a[0] - b[0])
|
|
123
|
-
.map(([timestamp, clients]) => ({
|
|
124
|
-
time: timestamp,
|
|
125
|
-
...clients
|
|
126
|
-
}))
|
|
127
|
-
|
|
128
|
-
// "Tare" logic: Subtract the initial value (at t=0 of the graph) from all subsequent values
|
|
129
|
-
// This ensures the graph starts at 0 and shows cumulative growth relative to the start of the period.
|
|
130
|
-
if (isCumulative && sortedSeries.length > 0) {
|
|
131
|
-
const initialValues: Record<string, number> = {}
|
|
132
|
-
// Initialize with the values from the first point
|
|
133
|
-
const firstPoint = sortedSeries[0] as Record<string, any>
|
|
134
|
-
Object.keys(firstPoint).forEach(key => {
|
|
135
|
-
if (key !== 'time') {
|
|
136
|
-
initialValues[key] = firstPoint[key] as number
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// Subtract initial values from all points
|
|
141
|
-
return sortedSeries.map(point => {
|
|
142
|
-
const p = point as Record<string, any>
|
|
143
|
-
const newPoint: any = { time: p.time }
|
|
144
|
-
Object.keys(p).forEach(key => {
|
|
145
|
-
if (key !== 'time') {
|
|
146
|
-
const rawValue = p[key] as number
|
|
147
|
-
const startValue = initialValues[key] || 0
|
|
148
|
-
// Ensure we don't go below zero (in case of resets)
|
|
149
|
-
newPoint[key] = Math.max(0, rawValue - startValue)
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
return newPoint
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Integration logic: Accumulate values over time
|
|
157
|
-
if (integrate && sortedSeries.length > 0) {
|
|
158
|
-
const runningTotals: Record<string, number> = {}
|
|
159
|
-
|
|
160
|
-
return sortedSeries.map(point => {
|
|
161
|
-
const p = point as Record<string, any>
|
|
162
|
-
const newPoint: any = { time: p.time }
|
|
163
|
-
|
|
164
|
-
Object.keys(p).forEach(key => {
|
|
165
|
-
if (key !== 'time') {
|
|
166
|
-
const delta = p[key] as number
|
|
167
|
-
runningTotals[key] = (runningTotals[key] || 0) + delta
|
|
168
|
-
newPoint[key] = Math.round(runningTotals[key])
|
|
169
|
-
}
|
|
170
|
-
})
|
|
171
|
-
return newPoint
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return sortedSeries
|
|
176
|
-
}
|