@cybermem/dashboard 0.15.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @cybermem/dashboard
2
2
 
3
+ ## 0.16.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Automated patch version bump.
8
+
9
+ ## 0.16.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#140](https://github.com/mikhailkogan17/cybermem/pull/140) [`bcf40e2`](https://github.com/mikhailkogan17/cybermem/commit/bcf40e2173ad66214cfc6089d45481966255581d) Thanks [@mikhailkogan17-antigravity](https://github.com/mikhailkogan17-antigravity)! - Moved server tools usage to FastMCP
14
+
3
15
  ## 0.15.0
4
16
 
5
17
  ### Minor Changes
@@ -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: toolDisplayName,
99
+ tool: String(rawTool).toLowerCase(),
109
100
  status: status,
110
101
  method: log.method,
111
- description: log.endpoint,
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 = localKey.length > 10
64
- ? `${localKey.slice(0, 7)}...${localKey.slice(-4)}`
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-${new Date().toISOString().split("T")[0]}.tar.gz`;
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("Write");
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: 1337,
58
- totalClients: 5,
59
- successRate: 98.5,
60
- totalRequests: 1500,
61
- topWriter: { name: "Antigravity", count: 420 },
62
- topReader: { name: "Claude", count: 310 },
63
- lastWriter: { name: "Antigravity", timestamp: Date.now() - 120000 },
64
- lastReader: { name: "Claude", timestamp: Date.now() - 60000 },
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: "Antigravity",
72
- operation: "Write",
73
- description: "POST /add",
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: Date.now(),
77
+ timestamp: _now - 30000,
76
78
  },
77
79
  {
78
80
  id: 2,
79
- date: new Date(Date.now() - 60000).toISOString(),
80
- client: "Claude",
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: "POST /query",
185
+ description: "",
83
186
  status: "Success",
84
- timestamp: Date.now() - 60000,
187
+ timestamp: _now - 2400000,
85
188
  },
86
189
  ];
87
190
 
package/lib/data/types.ts CHANGED
@@ -25,6 +25,7 @@ export interface AuditLogEntry {
25
25
  date: string;
26
26
  client: string;
27
27
  operation: string;
28
+ tool?: string;
28
29
  status: string;
29
30
  description: string;
30
31
  timestamp: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/dashboard",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "CyberMem Monitoring Dashboard",
5
5
  "homepage": "https://cybermem.dev",
6
6
  "repository": {
@@ -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 CLI",
149
+ "name": "Gemini",
150
150
  "match": "gemini",
151
- "color": "#9ca3af",
151
+ "color": "#8b5cf6",
152
152
  "icon": "/icons/gemini.png",
153
153
  "description": "Gemini CLI supports MCP servers via stdio transport.",
154
154
  "steps": [