@cybermem/dashboard 0.16.0 → 0.16.1
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/CHANGELOG.md +6 -0
- package/app/api/audit-logs/route.ts +2 -11
- package/components/dashboard/charts/chart-card.tsx +92 -2
- package/components/dashboard/settings-modal.tsx +41 -4
- package/e2e/api.spec.ts +1 -1
- package/lib/data/dashboard-context.tsx +120 -17
- package/lib/data/types.ts +1 -0
- package/package.json +1 -1
- package/public/clients.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -92,23 +92,14 @@ export async function GET(request: Request) {
|
|
|
92
92
|
else if (statusCode >= 300) status = "Warning";
|
|
93
93
|
|
|
94
94
|
const rawTool = log.tool ?? "unknown";
|
|
95
|
-
let toolDisplayName = String(rawTool).toLowerCase();
|
|
96
|
-
// Friendly labels for core tools if needed
|
|
97
|
-
if (toolDisplayName === "add_memory") toolDisplayName = "Write";
|
|
98
|
-
else if (toolDisplayName === "query_memory") toolDisplayName = "Read";
|
|
99
|
-
else if (toolDisplayName === "update_memory") toolDisplayName = "Update";
|
|
100
|
-
else if (toolDisplayName === "delete_memory") toolDisplayName = "Delete";
|
|
101
|
-
else
|
|
102
|
-
toolDisplayName =
|
|
103
|
-
toolDisplayName.charAt(0).toUpperCase() + toolDisplayName.slice(1);
|
|
104
95
|
|
|
105
96
|
return {
|
|
106
97
|
timestamp: log.timestamp,
|
|
107
98
|
client: normalizeClientName(log.client_name),
|
|
108
|
-
tool:
|
|
99
|
+
tool: String(rawTool).toLowerCase(),
|
|
109
100
|
status: status,
|
|
110
101
|
method: log.method,
|
|
111
|
-
description:
|
|
102
|
+
description: "",
|
|
112
103
|
rawStatus: log.status,
|
|
113
104
|
};
|
|
114
105
|
});
|
|
@@ -6,6 +6,80 @@ import { ChevronDown } from "lucide-react";
|
|
|
6
6
|
import dynamic from "next/dynamic";
|
|
7
7
|
import { useEffect, useRef, useState } from "react";
|
|
8
8
|
|
|
9
|
+
// Pre-built demo timeseries (24h, 3 clients, cumulative counts)
|
|
10
|
+
const DEMO_CLIENT_NAMES = ["VS Code", "Gemini", "Perplexity"];
|
|
11
|
+
const DEMO_TIME_SERIES = (() => {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
const POINTS = 24;
|
|
14
|
+
const makeHour = (i: number) => {
|
|
15
|
+
const t = new Date(now - (POINTS - 1 - i) * 3600000);
|
|
16
|
+
return t.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
17
|
+
};
|
|
18
|
+
type Row = Record<string, number | string>;
|
|
19
|
+
const makeSeries = (c: number[], g: number[], p: number[]): Row[] =>
|
|
20
|
+
Array.from({ length: POINTS }, (_, i) => ({
|
|
21
|
+
time: makeHour(i),
|
|
22
|
+
"VS Code": c[i],
|
|
23
|
+
Gemini: g[i],
|
|
24
|
+
Perplexity: p[i],
|
|
25
|
+
}));
|
|
26
|
+
// Creates
|
|
27
|
+
const cC = [
|
|
28
|
+
20, 21, 22, 22, 22, 23, 25, 28, 32, 38, 44, 51, 58, 63, 67, 70, 74, 79, 84,
|
|
29
|
+
88, 89, 89, 89, 89,
|
|
30
|
+
];
|
|
31
|
+
const gC = [
|
|
32
|
+
8, 8, 8, 8, 8, 9, 10, 12, 14, 17, 20, 23, 27, 29, 31, 33, 35, 36, 36, 36,
|
|
33
|
+
36, 36, 36, 36,
|
|
34
|
+
];
|
|
35
|
+
const pC = [
|
|
36
|
+
15, 15, 15, 15, 16, 16, 18, 21, 24, 28, 32, 37, 43, 47, 50, 54, 58, 62, 64,
|
|
37
|
+
65, 65, 65, 65, 65,
|
|
38
|
+
];
|
|
39
|
+
// Reads
|
|
40
|
+
const cR = [
|
|
41
|
+
45, 47, 48, 49, 50, 52, 56, 62, 70, 79, 89, 100, 112, 121, 128, 134, 140,
|
|
42
|
+
147, 153, 157, 160, 162, 163, 163,
|
|
43
|
+
];
|
|
44
|
+
const gR = [
|
|
45
|
+
20, 20, 21, 21, 22, 23, 25, 28, 31, 35, 40, 46, 52, 57, 60, 63, 66, 68, 70,
|
|
46
|
+
72, 73, 73, 73, 73,
|
|
47
|
+
];
|
|
48
|
+
const pR = [
|
|
49
|
+
30, 31, 31, 32, 32, 34, 37, 42, 48, 55, 63, 72, 81, 88, 93, 98, 103, 108,
|
|
50
|
+
112, 115, 116, 117, 117, 117,
|
|
51
|
+
];
|
|
52
|
+
// Updates
|
|
53
|
+
const cU = [
|
|
54
|
+
5, 5, 5, 5, 5, 5, 6, 7, 8, 9, 11, 13, 15, 16, 17, 18, 19, 20, 21, 22, 22,
|
|
55
|
+
22, 22, 22,
|
|
56
|
+
];
|
|
57
|
+
const gU = [
|
|
58
|
+
2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 7, 7, 8, 8, 9, 9, 10, 10, 10, 10, 10,
|
|
59
|
+
10,
|
|
60
|
+
];
|
|
61
|
+
const pU = [
|
|
62
|
+
3, 3, 3, 3, 3, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 11, 12, 12, 13, 13, 14, 14,
|
|
63
|
+
14, 14,
|
|
64
|
+
];
|
|
65
|
+
// Deletes
|
|
66
|
+
const cD = [
|
|
67
|
+
1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
68
|
+
];
|
|
69
|
+
const gD = [
|
|
70
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
|
71
|
+
];
|
|
72
|
+
const pD = [
|
|
73
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
|
|
74
|
+
];
|
|
75
|
+
return {
|
|
76
|
+
creates: makeSeries(cC, gC, pC),
|
|
77
|
+
reads: makeSeries(cR, gR, pR),
|
|
78
|
+
updates: makeSeries(cU, gU, pU),
|
|
79
|
+
deletes: makeSeries(cD, gD, pD),
|
|
80
|
+
};
|
|
81
|
+
})();
|
|
82
|
+
|
|
9
83
|
// Dynamic import with SSR disabled
|
|
10
84
|
const MemoryChart = dynamic(() => import("./memory-chart"), { ssr: false });
|
|
11
85
|
|
|
@@ -36,7 +110,7 @@ const periods = [
|
|
|
36
110
|
];
|
|
37
111
|
|
|
38
112
|
export default function ChartCard({ service }: ChartCardProps) {
|
|
39
|
-
const { refreshSignal, clientConfigs } = useDashboard();
|
|
113
|
+
const { refreshSignal, clientConfigs, isDemo } = useDashboard();
|
|
40
114
|
const [period, setPeriod] = useState("24h");
|
|
41
115
|
const [hovered, setHovered] = useState<string | null>(null);
|
|
42
116
|
const [data, setData] = useState<any[]>([]);
|
|
@@ -49,6 +123,22 @@ export default function ChartCard({ service }: ChartCardProps) {
|
|
|
49
123
|
async function fetchData() {
|
|
50
124
|
if (isInitialLoad.current) setLoading(true);
|
|
51
125
|
|
|
126
|
+
// Demo mode: use static fake data, skip network fetch
|
|
127
|
+
if (isDemo) {
|
|
128
|
+
const key = service.includes("Creates")
|
|
129
|
+
? "creates"
|
|
130
|
+
: service.includes("Reads")
|
|
131
|
+
? "reads"
|
|
132
|
+
: service.includes("Updates")
|
|
133
|
+
? "updates"
|
|
134
|
+
: "deletes";
|
|
135
|
+
setData(DEMO_TIME_SERIES[key] as any[]);
|
|
136
|
+
setClientNames(DEMO_CLIENT_NAMES);
|
|
137
|
+
setLoading(false);
|
|
138
|
+
isInitialLoad.current = false;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
52
142
|
try {
|
|
53
143
|
const res = await fetch(`/api/metrics?period=${period}`);
|
|
54
144
|
if (!res.ok) throw new Error("Failed to fetch metrics");
|
|
@@ -134,7 +224,7 @@ export default function ChartCard({ service }: ChartCardProps) {
|
|
|
134
224
|
}
|
|
135
225
|
}
|
|
136
226
|
fetchData();
|
|
137
|
-
}, [period, service, refreshSignal]);
|
|
227
|
+
}, [period, service, refreshSignal, isDemo]);
|
|
138
228
|
|
|
139
229
|
const isMultiSeries = clientNames.length > 0;
|
|
140
230
|
|
|
@@ -60,9 +60,10 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
60
60
|
if (localKey && !data.isManaged) {
|
|
61
61
|
setApiKey(localKey);
|
|
62
62
|
// Mask the local key for display
|
|
63
|
-
const maskedLocal =
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
const maskedLocal =
|
|
64
|
+
localKey.length > 10
|
|
65
|
+
? `${localKey.slice(0, 7)}...${localKey.slice(-4)}`
|
|
66
|
+
: "****";
|
|
66
67
|
setApiKeyMasked(maskedLocal);
|
|
67
68
|
} else {
|
|
68
69
|
setApiKey(data.apiKey !== "not-set" ? data.apiKey : "");
|
|
@@ -120,7 +121,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
120
121
|
const url = window.URL.createObjectURL(blob);
|
|
121
122
|
const a = document.createElement("a");
|
|
122
123
|
a.href = url;
|
|
123
|
-
a.download = `cybermem-backup-${
|
|
124
|
+
a.download = `cybermem-backup-${
|
|
125
|
+
new Date().toISOString().split("T")[0]
|
|
126
|
+
}.tar.gz`;
|
|
124
127
|
document.body.appendChild(a);
|
|
125
128
|
a.click();
|
|
126
129
|
window.URL.revokeObjectURL(url);
|
|
@@ -262,6 +265,40 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
262
265
|
handleRestart={handleRestart}
|
|
263
266
|
isRestarting={isRestarting}
|
|
264
267
|
/>
|
|
268
|
+
|
|
269
|
+
{/* Demo Mode — staging only */}
|
|
270
|
+
{settings?.env === "staging" && (
|
|
271
|
+
<section className="pb-4">
|
|
272
|
+
<h3 className="text-sm font-medium text-neutral-400 uppercase tracking-widest mb-4 flex items-center gap-2">
|
|
273
|
+
<span>🎬</span>
|
|
274
|
+
Developer
|
|
275
|
+
</h3>
|
|
276
|
+
<div className="bg-white/[0.032] border-[0.5px] border-white/10 rounded-2xl p-5 flex items-center justify-between">
|
|
277
|
+
<div>
|
|
278
|
+
<div className="text-sm font-medium text-white">
|
|
279
|
+
Demo Mode
|
|
280
|
+
</div>
|
|
281
|
+
<div className="text-xs text-neutral-500 mt-0.5">
|
|
282
|
+
Show fake data — vscode, gemini, perplexity
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
<button
|
|
286
|
+
role="switch"
|
|
287
|
+
aria-checked={isDemo}
|
|
288
|
+
onClick={toggleDemo}
|
|
289
|
+
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
|
290
|
+
isDemo ? "bg-emerald-500" : "bg-white/15"
|
|
291
|
+
}`}
|
|
292
|
+
>
|
|
293
|
+
<span
|
|
294
|
+
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
|
295
|
+
isDemo ? "translate-x-5" : "translate-x-0"
|
|
296
|
+
}`}
|
|
297
|
+
/>
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
300
|
+
</section>
|
|
301
|
+
)}
|
|
265
302
|
</div>
|
|
266
303
|
|
|
267
304
|
{/* Footer */}
|
package/e2e/api.spec.ts
CHANGED
|
@@ -320,7 +320,7 @@ test.describe("Dashboard:E2E:API (Deep Verification)", () => {
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
expect(latestLog).toBeDefined();
|
|
323
|
-
expect(latestLog.tool).toBe("
|
|
323
|
+
expect(latestLog.tool).toBe("add_memory");
|
|
324
324
|
|
|
325
325
|
await testInfo.attach("📋 Audit Log Entry", {
|
|
326
326
|
body: `Found: ${latestLog ? "YES" : "NO"}\nClient: ${
|
|
@@ -54,34 +54,137 @@ const DashboardContext = createContext<DashboardContextType | undefined>(
|
|
|
54
54
|
);
|
|
55
55
|
|
|
56
56
|
const DEMO_STATS: DashboardStats = {
|
|
57
|
-
memoryRecords:
|
|
58
|
-
totalClients:
|
|
59
|
-
successRate: 98.
|
|
60
|
-
totalRequests:
|
|
61
|
-
topWriter: { name: "
|
|
62
|
-
topReader: { name: "
|
|
63
|
-
lastWriter: { name: "
|
|
64
|
-
lastReader: { name: "
|
|
57
|
+
memoryRecords: 47,
|
|
58
|
+
totalClients: 3,
|
|
59
|
+
successRate: 98.7,
|
|
60
|
+
totalRequests: 234,
|
|
61
|
+
topWriter: { name: "Perplexity", count: 89 },
|
|
62
|
+
topReader: { name: "VS Code", count: 117 },
|
|
63
|
+
lastWriter: { name: "Gemini", timestamp: Date.now() - 120000 },
|
|
64
|
+
lastReader: { name: "Perplexity", timestamp: Date.now() - 30000 },
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
const _now = Date.now();
|
|
67
68
|
const DEMO_LOGS: AuditLogEntry[] = [
|
|
68
69
|
{
|
|
69
70
|
id: 1,
|
|
70
|
-
date: new Date().toISOString(),
|
|
71
|
-
client: "
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
date: new Date(_now - 30000).toISOString(),
|
|
72
|
+
client: "Perplexity",
|
|
73
|
+
tool: "query_memory",
|
|
74
|
+
operation: "Read",
|
|
75
|
+
description: "",
|
|
74
76
|
status: "Success",
|
|
75
|
-
timestamp:
|
|
77
|
+
timestamp: _now - 30000,
|
|
76
78
|
},
|
|
77
79
|
{
|
|
78
80
|
id: 2,
|
|
79
|
-
date: new Date(
|
|
80
|
-
client: "
|
|
81
|
+
date: new Date(_now - 120000).toISOString(),
|
|
82
|
+
client: "Gemini",
|
|
83
|
+
tool: "add_memory",
|
|
84
|
+
operation: "Write",
|
|
85
|
+
description: "",
|
|
86
|
+
status: "Success",
|
|
87
|
+
timestamp: _now - 120000,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 3,
|
|
91
|
+
date: new Date(_now - 180000).toISOString(),
|
|
92
|
+
client: "VS Code",
|
|
93
|
+
tool: "query_memory",
|
|
94
|
+
operation: "Read",
|
|
95
|
+
description: "",
|
|
96
|
+
status: "Success",
|
|
97
|
+
timestamp: _now - 180000,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 4,
|
|
101
|
+
date: new Date(_now - 300000).toISOString(),
|
|
102
|
+
client: "Perplexity",
|
|
103
|
+
tool: "add_memory",
|
|
104
|
+
operation: "Write",
|
|
105
|
+
description: "",
|
|
106
|
+
status: "Success",
|
|
107
|
+
timestamp: _now - 300000,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 5,
|
|
111
|
+
date: new Date(_now - 420000).toISOString(),
|
|
112
|
+
client: "VS Code",
|
|
113
|
+
tool: "add_memory",
|
|
114
|
+
operation: "Write",
|
|
115
|
+
description: "",
|
|
116
|
+
status: "Success",
|
|
117
|
+
timestamp: _now - 420000,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 6,
|
|
121
|
+
date: new Date(_now - 600000).toISOString(),
|
|
122
|
+
client: "Gemini",
|
|
123
|
+
tool: "query_memory",
|
|
124
|
+
operation: "Read",
|
|
125
|
+
description: "",
|
|
126
|
+
status: "Success",
|
|
127
|
+
timestamp: _now - 600000,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 7,
|
|
131
|
+
date: new Date(_now - 720000).toISOString(),
|
|
132
|
+
client: "Perplexity",
|
|
133
|
+
tool: "update_memory",
|
|
134
|
+
operation: "Update",
|
|
135
|
+
description: "",
|
|
136
|
+
status: "Success",
|
|
137
|
+
timestamp: _now - 720000,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 8,
|
|
141
|
+
date: new Date(_now - 900000).toISOString(),
|
|
142
|
+
client: "VS Code",
|
|
143
|
+
tool: "query_memory",
|
|
144
|
+
operation: "Read",
|
|
145
|
+
description: "",
|
|
146
|
+
status: "Warning",
|
|
147
|
+
timestamp: _now - 900000,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 9,
|
|
151
|
+
date: new Date(_now - 1200000).toISOString(),
|
|
152
|
+
client: "Gemini",
|
|
153
|
+
tool: "add_memory",
|
|
154
|
+
operation: "Write",
|
|
155
|
+
description: "",
|
|
156
|
+
status: "Success",
|
|
157
|
+
timestamp: _now - 1200000,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 10,
|
|
161
|
+
date: new Date(_now - 1500000).toISOString(),
|
|
162
|
+
client: "Perplexity",
|
|
163
|
+
tool: "query_memory",
|
|
164
|
+
operation: "Read",
|
|
165
|
+
description: "",
|
|
166
|
+
status: "Success",
|
|
167
|
+
timestamp: _now - 1500000,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 11,
|
|
171
|
+
date: new Date(_now - 1800000).toISOString(),
|
|
172
|
+
client: "VS Code",
|
|
173
|
+
tool: "add_memory",
|
|
174
|
+
operation: "Write",
|
|
175
|
+
description: "",
|
|
176
|
+
status: "Success",
|
|
177
|
+
timestamp: _now - 1800000,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 12,
|
|
181
|
+
date: new Date(_now - 2400000).toISOString(),
|
|
182
|
+
client: "Perplexity",
|
|
183
|
+
tool: "query_memory",
|
|
81
184
|
operation: "Read",
|
|
82
|
-
description: "
|
|
185
|
+
description: "",
|
|
83
186
|
status: "Success",
|
|
84
|
-
timestamp:
|
|
187
|
+
timestamp: _now - 2400000,
|
|
85
188
|
},
|
|
86
189
|
];
|
|
87
190
|
|
package/lib/data/types.ts
CHANGED
package/package.json
CHANGED
package/public/clients.json
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
{
|
|
37
37
|
"id": "vscode",
|
|
38
38
|
"name": "VS Code",
|
|
39
|
-
"match": "vscode|copilot",
|
|
39
|
+
"match": "vscode|vs code|copilot",
|
|
40
40
|
"color": "#23a9f2",
|
|
41
41
|
"icon": "/icons/vscode.png",
|
|
42
42
|
"description": "VS Code requires the MCP Servers extension for MCP support.",
|
|
@@ -146,9 +146,9 @@
|
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"id": "gemini-cli",
|
|
149
|
-
"name": "Gemini
|
|
149
|
+
"name": "Gemini",
|
|
150
150
|
"match": "gemini",
|
|
151
|
-
"color": "#
|
|
151
|
+
"color": "#8b5cf6",
|
|
152
152
|
"icon": "/icons/gemini.png",
|
|
153
153
|
"description": "Gemini CLI supports MCP servers via stdio transport.",
|
|
154
154
|
"steps": [
|