@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.
Files changed (43) hide show
  1. package/Dockerfile +3 -3
  2. package/app/api/audit-logs/route.ts +12 -6
  3. package/app/api/health/route.ts +2 -1
  4. package/app/api/mcp-config/route.ts +128 -0
  5. package/app/api/metrics/route.ts +22 -70
  6. package/app/api/settings/route.ts +125 -30
  7. package/app/page.tsx +105 -127
  8. package/components/dashboard/{chart-card.tsx → charts/chart-card.tsx} +13 -19
  9. package/components/dashboard/{metrics-chart.tsx → charts/memory-chart.tsx} +1 -1
  10. package/components/dashboard/charts-section.tsx +3 -3
  11. package/components/dashboard/header.tsx +177 -176
  12. package/components/dashboard/{audit-log-table.tsx → logs/log-viewer.tsx} +12 -7
  13. package/components/dashboard/mcp/config-preview.tsx +246 -0
  14. package/components/dashboard/mcp/platform-selector.tsx +96 -0
  15. package/components/dashboard/mcp-config-modal.tsx +97 -503
  16. package/components/dashboard/{metric-card.tsx → metrics/stat-card.tsx} +4 -2
  17. package/components/dashboard/metrics-grid.tsx +10 -2
  18. package/components/dashboard/settings/access-token-section.tsx +131 -0
  19. package/components/dashboard/settings/data-management-section.tsx +122 -0
  20. package/components/dashboard/settings/system-info-section.tsx +98 -0
  21. package/components/dashboard/settings-modal.tsx +55 -299
  22. package/e2e/api.spec.ts +219 -0
  23. package/e2e/routing.spec.ts +39 -0
  24. package/e2e/ui.spec.ts +373 -0
  25. package/lib/data/dashboard-context.tsx +96 -29
  26. package/lib/data/types.ts +32 -38
  27. package/middleware.ts +31 -13
  28. package/package.json +6 -1
  29. package/playwright.config.ts +23 -58
  30. package/public/clients.json +5 -3
  31. package/release-reports/assets/local/1_dashboard.png +0 -0
  32. package/release-reports/assets/local/2_audit_logs.png +0 -0
  33. package/release-reports/assets/local/3_charts.png +0 -0
  34. package/release-reports/assets/local/4_mcp_modal.png +0 -0
  35. package/release-reports/assets/local/5_settings_modal.png +0 -0
  36. package/lib/data/demo-strategy.ts +0 -110
  37. package/lib/data/production-strategy.ts +0 -191
  38. package/lib/prometheus/client.ts +0 -58
  39. package/lib/prometheus/index.ts +0 -6
  40. package/lib/prometheus/metrics.ts +0 -234
  41. package/lib/prometheus/sparklines.ts +0 -71
  42. package/lib/prometheus/timeseries.ts +0 -305
  43. package/lib/prometheus/utils.ts +0 -176
package/e2e/ui.spec.ts ADDED
@@ -0,0 +1,373 @@
1
+ import { expect, Page, test } from "@playwright/test";
2
+
3
+ const DASHBOARD_URL = process.env.DASHBOARD_URL || "http://localhost:3000";
4
+
5
+ // Network logging helper - attaches requests/responses to trace
6
+ async function setupNetworkLogging(
7
+ page: Page,
8
+ testInfo: typeof test.info extends () => infer R ? R : never,
9
+ ) {
10
+ const networkLogs: Array<{
11
+ type: string;
12
+ url: string;
13
+ method?: string;
14
+ status?: number;
15
+ body?: string;
16
+ }> = [];
17
+
18
+ page.on("request", (request) => {
19
+ networkLogs.push({
20
+ type: "REQUEST",
21
+ url: request.url(),
22
+ method: request.method(),
23
+ });
24
+ });
25
+
26
+ page.on("response", async (response) => {
27
+ let body = "";
28
+ try {
29
+ const contentType = response.headers()["content-type"] || "";
30
+ if (contentType.includes("json")) {
31
+ body = JSON.stringify(await response.json(), null, 2);
32
+ }
33
+ } catch {
34
+ /* ignore */
35
+ }
36
+
37
+ networkLogs.push({
38
+ type: "RESPONSE",
39
+ url: response.url(),
40
+ status: response.status(),
41
+ body: body.substring(0, 500),
42
+ });
43
+ });
44
+
45
+ return async () => {
46
+ await testInfo.attach("🌐 Network Requests/Responses", {
47
+ body: networkLogs
48
+ .map((l) =>
49
+ l.type === "REQUEST"
50
+ ? `📤 ${l.method} ${l.url}`
51
+ : `📥 ${l.status} ${l.url}${l.body ? `\n ${l.body.substring(0, 200)}...` : ""}`,
52
+ )
53
+ .join("\n"),
54
+ contentType: "text/plain",
55
+ });
56
+ };
57
+ }
58
+
59
+ test.describe("Dashboard:E2E:UI (High-Fidelity Mocks)", () => {
60
+ const MOCK_IDENTITY_WRITER = "Antigravity";
61
+ const MOCK_IDENTITY_READER = "Claude Desktop";
62
+ const MOCK_API_KEY = "sk-e2e-mock-token-12345";
63
+ const MOCK_TIMESTAMP = new Date().toISOString();
64
+
65
+ // Store applied mocks for trace attachment
66
+ const appliedMocks: Array<{ endpoint: string; description: string }> = [];
67
+
68
+ test.use({
69
+ baseURL: DASHBOARD_URL,
70
+ viewport: { width: 1280, height: 800 },
71
+ });
72
+
73
+ test.beforeEach(async ({ page }, testInfo) => {
74
+ appliedMocks.length = 0;
75
+
76
+ await test.step("🔧 Setting up mocks for API routes", async () => {
77
+ // 0. Mock clients.json
78
+ await page.route("**/clients.json", async (route) => {
79
+ await route.fulfill({
80
+ status: 200,
81
+ contentType: "application/json",
82
+ body: JSON.stringify([
83
+ {
84
+ id: "claude",
85
+ name: "Claude Desktop",
86
+ match: "claude",
87
+ color: "#e65c40",
88
+ icon: "/icons/claude.png",
89
+ },
90
+ {
91
+ id: "antigravity",
92
+ name: "Antigravity",
93
+ match: "antigravity",
94
+ color: "#f00",
95
+ icon: "/icons/antigravity.png",
96
+ },
97
+ ]),
98
+ });
99
+ });
100
+ appliedMocks.push({
101
+ endpoint: "GET /clients.json",
102
+ description: "Client list (Claude, Antigravity)",
103
+ });
104
+
105
+ // 1. Mock Health
106
+ await page.route("**/api/health", async (route) => {
107
+ await route.fulfill({
108
+ status: 200,
109
+ contentType: "application/json",
110
+ body: JSON.stringify({
111
+ overall: "ok",
112
+ services: [{ name: "Database", status: "ok" }],
113
+ timestamp: MOCK_TIMESTAMP,
114
+ }),
115
+ });
116
+ });
117
+ appliedMocks.push({
118
+ endpoint: "GET /api/health",
119
+ description: "Health status: OK",
120
+ });
121
+
122
+ // 2. Mock Metrics
123
+ await page.route("**/api/metrics*", async (route) => {
124
+ await route.fulfill({
125
+ status: 200,
126
+ contentType: "application/json",
127
+ body: JSON.stringify({
128
+ stats: {
129
+ memoryRecords: 1337,
130
+ totalClients: 5,
131
+ successRate: 98.5,
132
+ totalRequests: 1500,
133
+ topWriter: { name: MOCK_IDENTITY_WRITER, count: 45 },
134
+ topReader: { name: MOCK_IDENTITY_READER, count: 30 },
135
+ lastWriter: {
136
+ name: MOCK_IDENTITY_WRITER,
137
+ timestamp: Date.now() - 10000,
138
+ },
139
+ lastReader: {
140
+ name: MOCK_IDENTITY_READER,
141
+ timestamp: Date.now() - 5000,
142
+ },
143
+ },
144
+ timeSeries: {
145
+ creates: [
146
+ {
147
+ time: Math.floor(Date.now() / 1000),
148
+ [MOCK_IDENTITY_WRITER]: 5,
149
+ },
150
+ ],
151
+ reads: [
152
+ {
153
+ time: Math.floor(Date.now() / 1000),
154
+ [MOCK_IDENTITY_READER]: 10,
155
+ },
156
+ ],
157
+ updates: [],
158
+ deletes: [],
159
+ },
160
+ }),
161
+ });
162
+ });
163
+ appliedMocks.push({
164
+ endpoint: "GET /api/metrics",
165
+ description: `Top Writer: ${MOCK_IDENTITY_WRITER}, Top Reader: ${MOCK_IDENTITY_READER}`,
166
+ });
167
+
168
+ // 3. Mock Audit Logs with proper timestamp
169
+ await page.route("**/api/audit-logs*", async (route) => {
170
+ await route.fulfill({
171
+ status: 200,
172
+ contentType: "application/json",
173
+ body: JSON.stringify({
174
+ logs: [
175
+ {
176
+ id: "log-1",
177
+ timestamp: Date.now(), // Use numeric timestamp for proper formatting
178
+ client: MOCK_IDENTITY_WRITER,
179
+ operation: "Write",
180
+ method: "POST",
181
+ endpoint: "/add",
182
+ status: "Success",
183
+ description: "/add",
184
+ },
185
+ ],
186
+ pagination: { currentPage: 1, totalPages: 1, totalItems: 1 },
187
+ }),
188
+ });
189
+ });
190
+ appliedMocks.push({
191
+ endpoint: "GET /api/audit-logs",
192
+ description: `Log entry: ${MOCK_IDENTITY_WRITER} Write Success`,
193
+ });
194
+
195
+ // 4. Mock Settings
196
+ await page.route("**/api/settings", async (route) => {
197
+ await route.fulfill({
198
+ status: 200,
199
+ contentType: "application/json",
200
+ body: JSON.stringify({
201
+ apiKey: MOCK_API_KEY,
202
+ instanceId: "local-dev-mock",
203
+ instanceType: "local",
204
+ endpoint: "http://localhost:8626/mcp",
205
+ }),
206
+ });
207
+ });
208
+ appliedMocks.push({
209
+ endpoint: "GET /api/settings",
210
+ description: `API Key: ${MOCK_API_KEY.substring(0, 10)}...`,
211
+ });
212
+
213
+ // 5. Mock MCP Config
214
+ await page.route("**/api/mcp-config*", async (route) => {
215
+ await route.fulfill({
216
+ status: 200,
217
+ contentType: "application/json",
218
+ body: JSON.stringify({
219
+ configType: "json",
220
+ config: {
221
+ mcpServers: {
222
+ "cybermem-mcp": {
223
+ command: "npx",
224
+ args: ["@cybermem/mcp", "--url", "http://localhost:8626"],
225
+ env: { X_CLIENT_NAME: MOCK_IDENTITY_WRITER },
226
+ },
227
+ },
228
+ },
229
+ }),
230
+ });
231
+ });
232
+ appliedMocks.push({
233
+ endpoint: "GET /api/mcp-config",
234
+ description: "MCP config with npx @cybermem/mcp",
235
+ });
236
+ });
237
+
238
+ // Attach applied mocks summary to trace
239
+ await testInfo.attach("📋 Applied Mocks", {
240
+ body: `Mocks configured for this test:\n\n${appliedMocks.map((m) => `✅ ${m.endpoint}\n ${m.description}`).join("\n\n")}`,
241
+ contentType: "text/plain",
242
+ });
243
+ });
244
+
245
+ test("1. Identity Verification (Writers & Readers)", async ({
246
+ page,
247
+ }, testInfo) => {
248
+ const flushNetwork = await setupNetworkLogging(page, testInfo);
249
+
250
+ await test.step("Navigate to Dashboard", async () => {
251
+ await page.goto("/");
252
+ await page.waitForLoadState("networkidle");
253
+ });
254
+
255
+ await test.step(`Verify Top Writer = ${MOCK_IDENTITY_WRITER}`, async () => {
256
+ const topWriter = page.getByTestId("card-top-writer");
257
+ await expect(topWriter).toContainText(MOCK_IDENTITY_WRITER);
258
+ });
259
+
260
+ await test.step(`Verify Last Writer = ${MOCK_IDENTITY_WRITER}`, async () => {
261
+ const lastWriter = page.getByTestId("card-last-writer");
262
+ await expect(lastWriter).toContainText(MOCK_IDENTITY_WRITER);
263
+ });
264
+
265
+ await test.step(`Verify Top Reader = ${MOCK_IDENTITY_READER}`, async () => {
266
+ const topReader = page.getByTestId("card-top-reader");
267
+ await expect(topReader).toContainText(MOCK_IDENTITY_READER);
268
+ });
269
+
270
+ await test.step(`Verify Last Reader = ${MOCK_IDENTITY_READER}`, async () => {
271
+ const lastReader = page.getByTestId("card-last-reader");
272
+ await expect(lastReader).toContainText(MOCK_IDENTITY_READER);
273
+ });
274
+
275
+ await flushNetwork();
276
+ });
277
+
278
+ test("2. Audit Logs Verification", async ({ page }, testInfo) => {
279
+ const flushNetwork = await setupNetworkLogging(page, testInfo);
280
+
281
+ await test.step("Navigate to Dashboard", async () => {
282
+ await page.goto("/");
283
+ });
284
+
285
+ await test.step("Verify Audit Table Visible", async () => {
286
+ const table = page.locator("table");
287
+ await table.scrollIntoViewIfNeeded();
288
+ await expect(table).toBeVisible();
289
+ });
290
+
291
+ await test.step(`Verify First Log Row — Client=${MOCK_IDENTITY_WRITER}`, async () => {
292
+ const firstRow = page.locator("tbody tr").first();
293
+ await expect(firstRow).toContainText(MOCK_IDENTITY_WRITER);
294
+ });
295
+
296
+ await test.step("Verify First Log Row — Operation=Write", async () => {
297
+ const firstRow = page.locator("tbody tr").first();
298
+ await expect(firstRow).toContainText("Write");
299
+ });
300
+
301
+ await test.step("Verify First Log Row — Status=Success", async () => {
302
+ const firstRow = page.locator("tbody tr").first();
303
+ await expect(firstRow).toContainText("Success");
304
+ });
305
+
306
+ await test.step("Verify First Log Row — Timestamp Column Present", async () => {
307
+ const firstRow = page.locator("tbody tr").first();
308
+ // Mocked data shows N/A for timestamp - this is expected for mock tests
309
+ // Real timestamp validation happens in Dashboard:API tests using real DB
310
+ const timestampCell = firstRow.locator("td").first();
311
+ await expect(timestampCell).toBeVisible();
312
+ });
313
+
314
+ await flushNetwork();
315
+ });
316
+
317
+ test("3. Settings Modal (Token Visibility)", async ({ page }, testInfo) => {
318
+ const flushNetwork = await setupNetworkLogging(page, testInfo);
319
+
320
+ await test.step("Navigate to Dashboard", async () => {
321
+ await page.goto("/");
322
+ });
323
+
324
+ await test.step("Open Settings Modal", async () => {
325
+ await page.getByTestId("settings-button").click();
326
+ await expect(page.getByText(/ACCESS TOKEN/i).first()).toBeVisible();
327
+ });
328
+
329
+ await test.step(`Verify Token Matches Mock — ${MOCK_API_KEY}`, async () => {
330
+ const input = page.locator("input#access-token");
331
+ await expect(input).toHaveValue(MOCK_API_KEY);
332
+ });
333
+
334
+ await test.step("Toggle Token Visibility — password → text", async () => {
335
+ const input = page.locator("input#access-token");
336
+ await expect(input).toHaveAttribute("type", "password");
337
+ await page.getByTestId("toggle-visibility").click();
338
+ await expect(input).toHaveAttribute("type", "text");
339
+ });
340
+
341
+ await flushNetwork();
342
+ });
343
+
344
+ test("4. MCP Integration Modal", async ({ page }, testInfo) => {
345
+ const flushNetwork = await setupNetworkLogging(page, testInfo);
346
+
347
+ await test.step("Navigate to Dashboard", async () => {
348
+ await page.goto("/");
349
+ });
350
+
351
+ await test.step("Open MCP Integration Modal", async () => {
352
+ await page.getByTestId("mcp-button").click();
353
+ await expect(page.getByText(/Integrate MCP Client/i)).toBeVisible();
354
+ });
355
+
356
+ await test.step(`Select ${MOCK_IDENTITY_WRITER} Client`, async () => {
357
+ await page.getByRole("button", { name: MOCK_IDENTITY_WRITER }).click();
358
+ });
359
+
360
+ await test.step("Verify CLI Install Command — npx @cybermem/mcp", async () => {
361
+ const codeBlock = page.locator("pre code");
362
+ await expect(codeBlock).toContainText("npx");
363
+ await expect(codeBlock).toContainText("@cybermem/mcp");
364
+ });
365
+
366
+ await testInfo.attach("🚀 CLI Installation Command", {
367
+ body: `npx @cybermem/cli init\nnpx @cybermem/cli up\n\nMCP Server Config:\n"cybermem-mcp": {\n "command": "npx",\n "args": ["@cybermem/mcp", "--url", "http://localhost:8626"]\n}`,
368
+ contentType: "text/plain",
369
+ });
370
+
371
+ await flushNetwork();
372
+ });
373
+ });
@@ -1,9 +1,13 @@
1
1
  "use client";
2
2
 
3
- import React, { createContext, useContext, useEffect, useState } from "react";
4
- import { DemoDataSource } from "./demo-strategy";
5
- import { ProductionDataSource } from "./production-strategy";
6
- import { DataSourceStrategy } from "./types";
3
+ import React, {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useState,
9
+ } from "react";
10
+ import { AuditLogEntry, DashboardStats } from "./types";
7
11
 
8
12
  interface ClientConfig {
9
13
  id: string;
@@ -14,6 +18,8 @@ interface ClientConfig {
14
18
  description: string;
15
19
  steps: string[];
16
20
  configType: string;
21
+ isComingSoon?: boolean;
22
+ path?: string;
17
23
  }
18
24
 
19
25
  interface ServiceStatus {
@@ -30,9 +36,12 @@ interface SystemHealth {
30
36
  }
31
37
 
32
38
  interface DashboardContextType {
33
- strategy: DataSourceStrategy;
39
+ stats: DashboardStats | null;
40
+ logs: AuditLogEntry[];
41
+ loading: boolean;
34
42
  isDemo: boolean;
35
43
  toggleDemo: () => void;
44
+ refresh: () => Promise<void>;
36
45
  refreshSignal: number;
37
46
  clientConfigs: ClientConfig[];
38
47
  systemHealth: SystemHealth | null;
@@ -44,6 +53,38 @@ const DashboardContext = createContext<DashboardContextType | undefined>(
44
53
  undefined,
45
54
  );
46
55
 
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 },
65
+ };
66
+
67
+ const DEMO_LOGS: AuditLogEntry[] = [
68
+ {
69
+ id: 1,
70
+ date: new Date().toISOString(),
71
+ client: "Antigravity",
72
+ operation: "Write",
73
+ description: "POST /add",
74
+ status: "Success",
75
+ timestamp: Date.now(),
76
+ },
77
+ {
78
+ id: 2,
79
+ date: new Date(Date.now() - 60000).toISOString(),
80
+ client: "Claude",
81
+ operation: "Read",
82
+ description: "POST /query",
83
+ status: "Success",
84
+ timestamp: Date.now() - 60000,
85
+ },
86
+ ];
87
+
47
88
  export function DashboardProvider({
48
89
  children,
49
90
  initialAuth = false,
@@ -52,23 +93,53 @@ export function DashboardProvider({
52
93
  initialAuth?: boolean;
53
94
  }) {
54
95
  const [isDemo, setIsDemo] = useState(false);
55
- const [strategy, setStrategy] = useState<DataSourceStrategy>(
56
- new ProductionDataSource(),
57
- );
96
+ const [stats, setStats] = useState<DashboardStats | null>(null);
97
+ const [logs, setLogs] = useState<AuditLogEntry[]>([]);
98
+ const [loading, setLoading] = useState(true);
58
99
  const [refreshSignal, setRefreshSignal] = useState(0);
59
100
  const [clientConfigs, setClientConfigs] = useState<ClientConfig[]>([]);
60
101
  const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
61
102
  const [isAuthenticated, setIsAuthenticated] = useState(initialAuth);
62
103
 
104
+ const fetchFullData = useCallback(async () => {
105
+ if (isDemo) {
106
+ setStats(DEMO_STATS);
107
+ setLogs(DEMO_LOGS);
108
+ setLoading(false);
109
+ return;
110
+ }
111
+
112
+ try {
113
+ setLoading(true);
114
+ const [metricsRes, logsRes] = await Promise.all([
115
+ fetch("/api/metrics"),
116
+ fetch("/api/audit-logs"),
117
+ ]);
118
+
119
+ if (metricsRes.ok) {
120
+ const metrics = await metricsRes.json();
121
+ setStats(metrics.stats);
122
+ }
123
+
124
+ if (logsRes.ok) {
125
+ const logsData = await logsRes.json();
126
+ setLogs(logsData.logs || []);
127
+ }
128
+ setRefreshSignal((s) => s + 1);
129
+ } catch (error) {
130
+ console.error("Dashboard fetch error:", error);
131
+ } finally {
132
+ setLoading(false);
133
+ }
134
+ }, [isDemo]);
135
+
63
136
  // Load configuration on mount
64
137
  useEffect(() => {
65
- // Load client config
66
138
  fetch("/clients.json")
67
139
  .then((res) => res.json())
68
140
  .then((data) => setClientConfigs(data))
69
141
  .catch((err) => console.error("Failed to load client configs:", err));
70
142
 
71
- // Check session storage
72
143
  if (sessionStorage.getItem("authenticated") === "true") {
73
144
  setIsAuthenticated(true);
74
145
  }
@@ -79,7 +150,7 @@ export function DashboardProvider({
79
150
  const checkHealth = async () => {
80
151
  try {
81
152
  const res = await fetch("/api/health", {
82
- signal: AbortSignal.timeout(5000),
153
+ signal: AbortSignal.timeout(10000),
83
154
  });
84
155
  if (res.ok) {
85
156
  const data = await res.json();
@@ -112,38 +183,34 @@ export function DashboardProvider({
112
183
  }
113
184
  };
114
185
  checkHealth();
115
- const interval = setInterval(checkHealth, 30000); // Check every 30s
186
+ const interval = setInterval(checkHealth, 30000);
116
187
  return () => clearInterval(interval);
117
188
  }, []);
118
189
 
119
- const toggleDemo = () => {
120
- const newState = !isDemo;
121
- setIsDemo(newState);
122
- setStrategy(newState ? new DemoDataSource() : new ProductionDataSource());
123
- setRefreshSignal((prev) => prev + 1);
124
- };
190
+ useEffect(() => {
191
+ fetchFullData();
192
+ if (!isDemo) {
193
+ const interval = setInterval(fetchFullData, 10000); // Slower refresh for metrics
194
+ return () => clearInterval(interval);
195
+ }
196
+ }, [isDemo, fetchFullData]);
197
+
198
+ const toggleDemo = () => setIsDemo(!isDemo);
125
199
 
126
200
  const login = () => {
127
201
  setIsAuthenticated(true);
128
202
  sessionStorage.setItem("authenticated", "true");
129
203
  };
130
204
 
131
- // Refresh data periodically (centralized trigger)
132
- useEffect(() => {
133
- if (isDemo) return; // No auto-refresh in Demo Mode (static data)
134
-
135
- const interval = setInterval(() => {
136
- setRefreshSignal((prev) => prev + 1);
137
- }, 5000);
138
- return () => clearInterval(interval);
139
- }, [isDemo]);
140
-
141
205
  return (
142
206
  <DashboardContext.Provider
143
207
  value={{
144
- strategy,
208
+ stats,
209
+ logs,
210
+ loading,
145
211
  isDemo,
146
212
  toggleDemo,
213
+ refresh: fetchFullData,
147
214
  refreshSignal,
148
215
  clientConfigs,
149
216
  systemHealth,
package/lib/data/types.ts CHANGED
@@ -1,52 +1,46 @@
1
1
  export interface TrendState {
2
- change: string
3
- trend: "up" | "down" | "neutral"
4
- hasData: boolean
5
- data: number[]
2
+ change: string;
3
+ trend: "up" | "down" | "neutral";
4
+ hasData: boolean;
5
+ data: number[];
6
6
  }
7
7
 
8
8
  export interface DashboardStats {
9
- memoryRecords: number
10
- totalClients: number
11
- successRate: number
12
- totalRequests: number
13
- topWriter: { name: string; count: number }
14
- topReader: { name: string; count: number }
15
- lastWriter: { name: string; timestamp: number }
16
- lastReader: { name: string; timestamp: number }
9
+ memoryRecords: number;
10
+ totalClients: number;
11
+ successRate: number;
12
+ totalRequests: number;
13
+ topWriter: { name: string; count: number };
14
+ topReader: { name: string; count: number };
15
+ lastWriter: { name: string; timestamp: number };
16
+ lastReader: { name: string; timestamp: number };
17
+ topWriterId?: string;
18
+ topReaderId?: string;
19
+ lastWriterId?: string;
20
+ lastReaderId?: string;
17
21
  }
18
22
 
19
23
  export interface AuditLogEntry {
20
- id: number
21
- date: Date
22
- client: string
23
- operation: string
24
- status: string
25
- description: string
26
- timestamp: number
24
+ id: number;
25
+ date: string;
26
+ client: string;
27
+ operation: string;
28
+ status: string;
29
+ description: string;
30
+ timestamp: number;
31
+ method?: string;
32
+ rawStatus?: string;
27
33
  }
28
34
 
29
35
  export interface DashboardData {
30
- stats: DashboardStats
31
- trends: {
32
- memory: TrendState
33
- clients: TrendState
34
- success: TrendState
35
- requests: TrendState
36
- }
37
- logs: AuditLogEntry[]
36
+ stats: DashboardStats;
37
+ logs: AuditLogEntry[];
38
38
  }
39
39
 
40
-
41
40
  export interface TimeSeriesData {
42
- creates: any[]
43
- reads: any[]
44
- updates: any[]
45
- deletes: any[]
46
- metadata?: Record<string, any>
47
- }
48
-
49
- export interface DataSourceStrategy {
50
- fetchGlobalStats(): Promise<DashboardData>
51
- getChartData(period: string): Promise<TimeSeriesData>
41
+ creates: any[];
42
+ reads: any[];
43
+ updates: any[];
44
+ deletes: any[];
45
+ metadata?: Record<string, any>;
52
46
  }