agentflow-dashboard 0.1.3 → 0.2.0

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/dist/cli.js ADDED
@@ -0,0 +1,120 @@
1
+ import {
2
+ DashboardServer
3
+ } from "./chunk-L24LYP6L.js";
4
+
5
+ // src/cli.ts
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ async function startDashboard() {
9
+ var _a;
10
+ const args = process.argv.slice(2);
11
+ const config = {
12
+ port: 3e3,
13
+ tracesDir: "./traces",
14
+ host: "localhost",
15
+ enableCors: false
16
+ };
17
+ for (let i = 0; i < args.length; i++) {
18
+ switch (args[i]) {
19
+ case "--port":
20
+ case "-p":
21
+ config.port = parseInt(args[++i]) || 3e3;
22
+ break;
23
+ case "--traces":
24
+ case "-t":
25
+ config.tracesDir = args[++i];
26
+ break;
27
+ case "--host":
28
+ case "-h":
29
+ config.host = args[++i];
30
+ break;
31
+ case "--data-dir":
32
+ if (!config.dataDirs) config.dataDirs = [];
33
+ config.dataDirs.push(args[++i]);
34
+ break;
35
+ case "--cors":
36
+ config.enableCors = true;
37
+ break;
38
+ case "--help":
39
+ printHelp();
40
+ process.exit(0);
41
+ }
42
+ }
43
+ const tracesPath = path.resolve(config.tracesDir);
44
+ if (!fs.existsSync(tracesPath)) {
45
+ console.log(`Traces directory doesn't exist: ${tracesPath}`);
46
+ console.log("Creating traces directory...");
47
+ fs.mkdirSync(tracesPath, { recursive: true });
48
+ }
49
+ config.tracesDir = tracesPath;
50
+ console.log("\u{1F680} Starting AgentFlow Dashboard...");
51
+ console.log(` Port: ${config.port}`);
52
+ console.log(` Host: ${config.host}`);
53
+ console.log(` Traces: ${config.tracesDir}`);
54
+ console.log(` CORS: ${config.enableCors ? "enabled" : "disabled"}`);
55
+ if ((_a = config.dataDirs) == null ? void 0 : _a.length) {
56
+ console.log(` Data dirs: ${config.dataDirs.join(", ")}`);
57
+ }
58
+ const dashboard = new DashboardServer(config);
59
+ process.on("SIGINT", async () => {
60
+ console.log("\\n\u{1F6D1} Shutting down dashboard...");
61
+ await dashboard.stop();
62
+ process.exit(0);
63
+ });
64
+ process.on("SIGTERM", async () => {
65
+ console.log("\\n\u{1F6D1} Received SIGTERM, shutting down...");
66
+ await dashboard.stop();
67
+ process.exit(0);
68
+ });
69
+ try {
70
+ await dashboard.start();
71
+ console.log("\u2705 Dashboard started successfully!");
72
+ console.log(` Open: http://${config.host}:${config.port}`);
73
+ setTimeout(() => {
74
+ const stats = dashboard.getStats();
75
+ const traces = dashboard.getTraces();
76
+ console.log(`\\n\u{1F4CA} Dashboard Status:`);
77
+ console.log(` Total Traces: ${traces.length}`);
78
+ console.log(` Total Agents: ${stats.totalAgents}`);
79
+ console.log(` Success Rate: ${stats.globalSuccessRate.toFixed(1)}%`);
80
+ console.log(` Active Agents: ${stats.activeAgents}`);
81
+ }, 1e3);
82
+ } catch (error) {
83
+ console.error("\u274C Failed to start dashboard:", error);
84
+ process.exit(1);
85
+ }
86
+ }
87
+ function printHelp() {
88
+ console.log(`
89
+ \u{1F50D} AgentFlow Dashboard - Real-time monitoring for AI agent executions
90
+
91
+ Usage:
92
+ agentflow-dashboard [options]
93
+ npx agentflow-dashboard [options]
94
+
95
+ Options:
96
+ -p, --port <number> Server port (default: 3000)
97
+ -t, --traces <path> Traces directory (default: ./traces)
98
+ -h, --host <address> Host address (default: localhost)
99
+ --data-dir <path> Extra directory for process discovery (repeatable)
100
+ --cors Enable CORS headers
101
+ --help Show this help message
102
+
103
+ Examples:
104
+ agentflow-dashboard --port 8080 --traces /var/log/agentflow
105
+ agentflow-dashboard --host 0.0.0.0 --cors
106
+ agentflow-dashboard --traces ./my-agent-traces
107
+
108
+ Features:
109
+ \u2728 Real-time trace monitoring
110
+ \u{1F4CA} Agent performance analytics
111
+ \u{1F3AF} Execution graph visualization
112
+ \u{1F4C8} Success/failure tracking
113
+ \u{1F50D} Multi-agent system overview
114
+
115
+ Visit: https://github.com/ClemenceChee/AgentFlow
116
+ `);
117
+ }
118
+ export {
119
+ startDashboard
120
+ };
package/dist/index.cjs CHANGED
@@ -38,233 +38,20 @@ module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/server.ts
40
40
  var import_express = __toESM(require("express"), 1);
41
- var import_ws = require("ws");
41
+ var fs2 = __toESM(require("fs"), 1);
42
42
  var import_http = require("http");
43
43
  var path2 = __toESM(require("path"), 1);
44
- var fs2 = __toESM(require("fs"), 1);
45
44
  var import_url = require("url");
46
-
47
- // src/watcher.ts
48
- var import_chokidar = __toESM(require("chokidar"), 1);
49
- var fs = __toESM(require("fs"), 1);
50
- var path = __toESM(require("path"), 1);
51
- var import_events = require("events");
52
- var TraceWatcher = class extends import_events.EventEmitter {
53
- watcher;
54
- traces = /* @__PURE__ */ new Map();
55
- tracesDir;
56
- constructor(tracesDir) {
57
- super();
58
- this.tracesDir = path.resolve(tracesDir);
59
- this.ensureTracesDir();
60
- this.loadExistingTraces();
61
- this.startWatching();
62
- }
63
- ensureTracesDir() {
64
- if (!fs.existsSync(this.tracesDir)) {
65
- fs.mkdirSync(this.tracesDir, { recursive: true });
66
- console.log(`Created traces directory: ${this.tracesDir}`);
67
- }
68
- }
69
- loadExistingTraces() {
70
- try {
71
- const files = fs.readdirSync(this.tracesDir).filter((file) => file.endsWith(".json"));
72
- console.log(`Loading ${files.length} existing trace files...`);
73
- for (const file of files) {
74
- this.loadTraceFile(path.join(this.tracesDir, file));
75
- }
76
- console.log(`Loaded ${this.traces.size} traces`);
77
- } catch (error) {
78
- console.error("Error loading existing traces:", error);
79
- }
80
- }
81
- loadTraceFile(filePath) {
82
- try {
83
- const content = fs.readFileSync(filePath, "utf8");
84
- const trace = JSON.parse(content);
85
- const filename = path.basename(filePath);
86
- const stats = fs.statSync(filePath);
87
- trace.filename = filename;
88
- trace.lastModified = stats.mtime.getTime();
89
- if (Array.isArray(trace.nodes)) {
90
- const nodesMap = new Map(trace.nodes);
91
- trace.nodes = nodesMap;
92
- }
93
- this.traces.set(filename, trace);
94
- return true;
95
- } catch (error) {
96
- console.error(`Error loading trace file ${filePath}:`, error);
97
- return false;
98
- }
99
- }
100
- startWatching() {
101
- this.watcher = import_chokidar.default.watch(this.tracesDir, {
102
- ignored: /^\\./,
103
- persistent: true,
104
- ignoreInitial: true
105
- });
106
- this.watcher.on("add", (filePath) => {
107
- if (filePath.endsWith(".json")) {
108
- console.log(`New trace file: ${path.basename(filePath)}`);
109
- if (this.loadTraceFile(filePath)) {
110
- const filename = path.basename(filePath);
111
- const trace = this.traces.get(filename);
112
- if (trace) {
113
- this.emit("trace-added", trace);
114
- }
115
- }
116
- }
117
- });
118
- this.watcher.on("change", (filePath) => {
119
- if (filePath.endsWith(".json")) {
120
- console.log(`Trace file updated: ${path.basename(filePath)}`);
121
- if (this.loadTraceFile(filePath)) {
122
- const filename = path.basename(filePath);
123
- const trace = this.traces.get(filename);
124
- if (trace) {
125
- this.emit("trace-updated", trace);
126
- }
127
- }
128
- }
129
- });
130
- this.watcher.on("unlink", (filePath) => {
131
- if (filePath.endsWith(".json")) {
132
- const filename = path.basename(filePath);
133
- console.log(`Trace file removed: ${filename}`);
134
- this.traces.delete(filename);
135
- this.emit("trace-removed", filename);
136
- }
137
- });
138
- this.watcher.on("error", (error) => {
139
- console.error("Trace watcher error:", error);
140
- });
141
- console.log(`Started watching traces directory: ${this.tracesDir}`);
142
- }
143
- getAllTraces() {
144
- return Array.from(this.traces.values()).sort((a, b) => {
145
- return (b.lastModified || b.timestamp) - (a.lastModified || a.timestamp);
146
- });
147
- }
148
- getTrace(filename) {
149
- return this.traces.get(filename);
150
- }
151
- getTracesByAgent(agentId) {
152
- return this.getAllTraces().filter((trace) => trace.agentId === agentId);
153
- }
154
- getRecentTraces(limit = 50) {
155
- return this.getAllTraces().slice(0, limit);
156
- }
157
- getTraceCount() {
158
- return this.traces.size;
159
- }
160
- getAgentIds() {
161
- const agentIds = /* @__PURE__ */ new Set();
162
- for (const trace of this.traces.values()) {
163
- agentIds.add(trace.agentId);
164
- }
165
- return Array.from(agentIds).sort();
166
- }
167
- stop() {
168
- if (this.watcher) {
169
- this.watcher.close();
170
- this.watcher = void 0;
171
- console.log("Stopped watching traces directory");
172
- }
173
- }
174
- // Utility method to get trace statistics
175
- getTraceStats() {
176
- const total = this.traces.size;
177
- const agentCount = this.getAgentIds().length;
178
- const recentCount = this.getRecentTraces(24).length;
179
- const triggers = /* @__PURE__ */ new Map();
180
- for (const trace of this.traces.values()) {
181
- const trigger = trace.trigger || "unknown";
182
- triggers.set(trigger, (triggers.get(trigger) || 0) + 1);
183
- }
184
- return {
185
- total,
186
- agentCount,
187
- recentCount,
188
- triggers: Object.fromEntries(triggers)
189
- };
190
- }
191
- };
192
-
193
- // ../core/dist/index.js
194
- function getFailures(graph) {
195
- const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
196
- return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
197
- }
198
- function getHungNodes(graph) {
199
- return [...graph.nodes.values()].filter(
200
- (node) => node.status === "running" && node.endTime === null
201
- );
202
- }
203
- function getDuration(graph) {
204
- const end = graph.endTime ?? Date.now();
205
- return end - graph.startTime;
206
- }
207
- function getDepth(graph) {
208
- const root = graph.nodes.get(graph.rootNodeId);
209
- if (!root) return -1;
210
- function dfs(node, depth) {
211
- if (node.children.length === 0) return depth;
212
- let maxDepth = depth;
213
- for (const childId of node.children) {
214
- const child = graph.nodes.get(childId);
215
- if (!child) continue;
216
- const childDepth = dfs(child, depth + 1);
217
- if (childDepth > maxDepth) maxDepth = childDepth;
218
- }
219
- return maxDepth;
220
- }
221
- return dfs(root, 0);
222
- }
223
- function getStats(graph) {
224
- const byStatus = {
225
- running: 0,
226
- completed: 0,
227
- failed: 0,
228
- hung: 0,
229
- timeout: 0
230
- };
231
- const byType = {
232
- agent: 0,
233
- tool: 0,
234
- subagent: 0,
235
- wait: 0,
236
- decision: 0,
237
- custom: 0
238
- };
239
- let failureCount = 0;
240
- let hungCount = 0;
241
- for (const node of graph.nodes.values()) {
242
- byStatus[node.status]++;
243
- byType[node.type]++;
244
- if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
245
- failureCount++;
246
- }
247
- if (node.status === "running" && node.endTime === null) {
248
- hungCount++;
249
- }
250
- }
251
- return {
252
- totalNodes: graph.nodes.size,
253
- byStatus,
254
- byType,
255
- depth: getDepth(graph),
256
- duration: getDuration(graph),
257
- failureCount,
258
- hungCount
259
- };
260
- }
45
+ var import_ws = require("ws");
46
+ var import_agentflow_core3 = require("agentflow-core");
261
47
 
262
48
  // src/stats.ts
49
+ var import_agentflow_core = require("agentflow-core");
263
50
  var AgentStats = class {
264
51
  agentMetrics = /* @__PURE__ */ new Map();
265
52
  processedTraces = /* @__PURE__ */ new Set();
266
53
  processTrace(trace) {
267
- const traceKey = `${trace.filename || trace.agentId}-${trace.timestamp}`;
54
+ const traceKey = `${trace.filename || trace.agentId}-${trace.startTime}`;
268
55
  if (this.processedTraces.has(traceKey)) {
269
56
  return;
270
57
  }
@@ -287,7 +74,7 @@ var AgentStats = class {
287
74
  }
288
75
  const analysis = this.analyzeExecution(trace);
289
76
  metrics.totalExecutions++;
290
- metrics.lastExecution = Math.max(metrics.lastExecution, trace.timestamp);
77
+ metrics.lastExecution = Math.max(metrics.lastExecution, trace.startTime);
291
78
  const trigger = trace.trigger || "unknown";
292
79
  metrics.triggers[trigger] = (metrics.triggers[trigger] || 0) + 1;
293
80
  if (analysis.success) {
@@ -302,7 +89,7 @@ var AgentStats = class {
302
89
  metrics.avgExecutionTime = (currentAvg * (count - 1) + analysis.executionTime) / count;
303
90
  }
304
91
  metrics.recentActivity.push({
305
- timestamp: trace.timestamp,
92
+ timestamp: trace.startTime,
306
93
  success: analysis.success,
307
94
  executionTime: analysis.executionTime,
308
95
  trigger
@@ -314,23 +101,22 @@ var AgentStats = class {
314
101
  }
315
102
  analyzeExecution(trace) {
316
103
  try {
317
- const stats = getStats(trace);
318
- const failures = getFailures(trace);
319
- const hungNodes = getHungNodes(trace);
104
+ const stats = (0, import_agentflow_core.getStats)(trace);
105
+ const failures = (0, import_agentflow_core.getFailures)(trace);
106
+ const hungNodes = (0, import_agentflow_core.getHungNodes)(trace);
320
107
  return {
321
108
  success: failures.length === 0 && hungNodes.length === 0,
322
- executionTime: stats.totalTime || 0,
109
+ executionTime: stats.duration || 0,
323
110
  nodeCount: stats.totalNodes || 0,
324
111
  failureCount: failures.length,
325
112
  hungCount: hungNodes.length
326
113
  };
327
114
  } catch (error) {
328
115
  console.warn("Error analyzing trace with AgentFlow:", error);
329
- const nodes = trace.nodes instanceof Map ? Array.from(trace.nodes.values()) : Array.isArray(trace.nodes) ? trace.nodes.map(([, node]) => node) : [];
116
+ const nodes = trace.nodes instanceof Map ? Array.from(trace.nodes.values()) : [];
330
117
  const failedNodes = nodes.filter((node) => node.status === "failed").length;
331
- const success = failedNodes === 0;
332
118
  return {
333
- success,
119
+ success: failedNodes === 0,
334
120
  executionTime: 0,
335
121
  nodeCount: nodes.length,
336
122
  failureCount: failedNodes,
@@ -420,7 +206,6 @@ var AgentStats = class {
420
206
  hourlySuccessRate: recentExecutions > 0 ? Math.round((recentExecutions - recentFailures) / recentExecutions * 1e4) / 100 : 0
421
207
  };
422
208
  }
423
- // Clear old data (useful for long-running dashboard instances)
424
209
  cleanup() {
425
210
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
426
211
  for (const [agentId, metrics] of this.agentMetrics.entries()) {
@@ -435,6 +220,148 @@ var AgentStats = class {
435
220
  }
436
221
  };
437
222
 
223
+ // src/watcher.ts
224
+ var import_agentflow_core2 = require("agentflow-core");
225
+ var import_chokidar = __toESM(require("chokidar"), 1);
226
+ var import_events = require("events");
227
+ var fs = __toESM(require("fs"), 1);
228
+ var path = __toESM(require("path"), 1);
229
+ var TraceWatcher = class extends import_events.EventEmitter {
230
+ watcher;
231
+ traces = /* @__PURE__ */ new Map();
232
+ tracesDir;
233
+ constructor(tracesDir) {
234
+ super();
235
+ this.tracesDir = path.resolve(tracesDir);
236
+ this.ensureTracesDir();
237
+ this.loadExistingTraces();
238
+ this.startWatching();
239
+ }
240
+ ensureTracesDir() {
241
+ if (!fs.existsSync(this.tracesDir)) {
242
+ fs.mkdirSync(this.tracesDir, { recursive: true });
243
+ console.log(`Created traces directory: ${this.tracesDir}`);
244
+ }
245
+ }
246
+ loadExistingTraces() {
247
+ try {
248
+ const files = fs.readdirSync(this.tracesDir).filter((file) => file.endsWith(".json"));
249
+ console.log(`Loading ${files.length} existing trace files...`);
250
+ for (const file of files) {
251
+ this.loadTraceFile(path.join(this.tracesDir, file));
252
+ }
253
+ console.log(`Loaded ${this.traces.size} traces`);
254
+ } catch (error) {
255
+ console.error("Error loading existing traces:", error);
256
+ }
257
+ }
258
+ loadTraceFile(filePath) {
259
+ try {
260
+ const content = fs.readFileSync(filePath, "utf8");
261
+ const graph = (0, import_agentflow_core2.loadGraph)(content);
262
+ const filename = path.basename(filePath);
263
+ const stats = fs.statSync(filePath);
264
+ graph.filename = filename;
265
+ graph.lastModified = stats.mtime.getTime();
266
+ this.traces.set(filename, graph);
267
+ return true;
268
+ } catch (error) {
269
+ console.error(`Error loading trace file ${filePath}:`, error);
270
+ return false;
271
+ }
272
+ }
273
+ startWatching() {
274
+ this.watcher = import_chokidar.default.watch(this.tracesDir, {
275
+ ignored: /^\./,
276
+ persistent: true,
277
+ ignoreInitial: true
278
+ });
279
+ this.watcher.on("add", (filePath) => {
280
+ if (filePath.endsWith(".json")) {
281
+ console.log(`New trace file: ${path.basename(filePath)}`);
282
+ if (this.loadTraceFile(filePath)) {
283
+ const filename = path.basename(filePath);
284
+ const trace = this.traces.get(filename);
285
+ if (trace) {
286
+ this.emit("trace-added", trace);
287
+ }
288
+ }
289
+ }
290
+ });
291
+ this.watcher.on("change", (filePath) => {
292
+ if (filePath.endsWith(".json")) {
293
+ console.log(`Trace file updated: ${path.basename(filePath)}`);
294
+ if (this.loadTraceFile(filePath)) {
295
+ const filename = path.basename(filePath);
296
+ const trace = this.traces.get(filename);
297
+ if (trace) {
298
+ this.emit("trace-updated", trace);
299
+ }
300
+ }
301
+ }
302
+ });
303
+ this.watcher.on("unlink", (filePath) => {
304
+ if (filePath.endsWith(".json")) {
305
+ const filename = path.basename(filePath);
306
+ console.log(`Trace file removed: ${filename}`);
307
+ this.traces.delete(filename);
308
+ this.emit("trace-removed", filename);
309
+ }
310
+ });
311
+ this.watcher.on("error", (error) => {
312
+ console.error("Trace watcher error:", error);
313
+ });
314
+ console.log(`Started watching traces directory: ${this.tracesDir}`);
315
+ }
316
+ getAllTraces() {
317
+ return Array.from(this.traces.values()).sort((a, b) => {
318
+ return (b.lastModified || b.startTime) - (a.lastModified || a.startTime);
319
+ });
320
+ }
321
+ getTrace(filename) {
322
+ return this.traces.get(filename);
323
+ }
324
+ getTracesByAgent(agentId) {
325
+ return this.getAllTraces().filter((trace) => trace.agentId === agentId);
326
+ }
327
+ getRecentTraces(limit = 50) {
328
+ return this.getAllTraces().slice(0, limit);
329
+ }
330
+ getTraceCount() {
331
+ return this.traces.size;
332
+ }
333
+ getAgentIds() {
334
+ const agentIds = /* @__PURE__ */ new Set();
335
+ for (const trace of this.traces.values()) {
336
+ agentIds.add(trace.agentId);
337
+ }
338
+ return Array.from(agentIds).sort();
339
+ }
340
+ stop() {
341
+ if (this.watcher) {
342
+ this.watcher.close();
343
+ this.watcher = void 0;
344
+ console.log("Stopped watching traces directory");
345
+ }
346
+ }
347
+ getTraceStats() {
348
+ const total = this.traces.size;
349
+ const agentCount = this.getAgentIds().length;
350
+ const recentCount = this.getRecentTraces(24).length;
351
+ const triggers = /* @__PURE__ */ new Map();
352
+ for (const trace of this.traces.values()) {
353
+ const trigger = trace.trigger || "unknown";
354
+ triggers.set(trigger, (triggers.get(trigger) || 0) + 1);
355
+ }
356
+ return {
357
+ total,
358
+ agentCount,
359
+ recentCount,
360
+ triggers: Object.fromEntries(triggers)
361
+ };
362
+ }
363
+ };
364
+
438
365
  // src/server.ts
439
366
  var import_meta = {};
440
367
  var __filename = (0, import_url.fileURLToPath)(import_meta.url);
@@ -453,11 +380,15 @@ var DashboardServer = class {
453
380
  wss = new import_ws.WebSocketServer({ server: this.server });
454
381
  watcher;
455
382
  stats;
383
+ processHealthCache = { result: null, ts: 0 };
456
384
  setupExpress() {
457
385
  if (this.config.enableCors) {
458
386
  this.app.use((req, res, next) => {
459
387
  res.header("Access-Control-Allow-Origin", "*");
460
- res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
388
+ res.header(
389
+ "Access-Control-Allow-Headers",
390
+ "Origin, X-Requested-With, Content-Type, Accept"
391
+ );
461
392
  next();
462
393
  });
463
394
  }
@@ -511,6 +442,28 @@ var DashboardServer = class {
511
442
  res.status(500).json({ error: "Failed to load agent statistics" });
512
443
  }
513
444
  });
445
+ this.app.get("/api/process-health", (req, res) => {
446
+ try {
447
+ const now = Date.now();
448
+ if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
449
+ return res.json(this.processHealthCache.result);
450
+ }
451
+ const discoveryDirs = [
452
+ this.config.tracesDir,
453
+ path2.dirname(this.config.tracesDir),
454
+ ...this.config.dataDirs || []
455
+ ];
456
+ const processConfig = (0, import_agentflow_core3.discoverProcessConfig)(discoveryDirs);
457
+ if (!processConfig) {
458
+ return res.json(null);
459
+ }
460
+ const result = (0, import_agentflow_core3.auditProcesses)(processConfig);
461
+ this.processHealthCache = { result, ts: now };
462
+ res.json(result);
463
+ } catch (error) {
464
+ res.status(500).json({ error: "Failed to audit processes" });
465
+ }
466
+ });
514
467
  this.app.get("*", (req, res) => {
515
468
  const indexPath = path2.join(__dirname, "../public/index.html");
516
469
  if (fs2.existsSync(indexPath)) {
@@ -523,13 +476,15 @@ var DashboardServer = class {
523
476
  setupWebSocket() {
524
477
  this.wss.on("connection", (ws) => {
525
478
  console.log("Dashboard client connected");
526
- ws.send(JSON.stringify({
527
- type: "init",
528
- data: {
529
- traces: this.watcher.getAllTraces(),
530
- stats: this.stats.getGlobalStats()
531
- }
532
- }));
479
+ ws.send(
480
+ JSON.stringify({
481
+ type: "init",
482
+ data: {
483
+ traces: this.watcher.getAllTraces(),
484
+ stats: this.stats.getGlobalStats()
485
+ }
486
+ })
487
+ );
533
488
  ws.on("close", () => {
534
489
  console.log("Dashboard client disconnected");
535
490
  });