Skip to content

Commit 383ceca

Browse files
committed
Improve event pipeline and dashboard UX
- Remove otel-collector batch processor for real-time span forwarding - Sort events chronologically in history replay and within OTLP batches - Replace /api/reset with full re-enrollment via host reset server - Add /api/events endpoint for simple shell-script event ingestion - Consolidate agent.requested + agent.prompted into single agent.prompted carrying both user.id and agent.id; animate user→platform→agent sequentially - Add agent.created audit log entry showing user → agent pairing - Standardize audit summary format for agent.* events
1 parent 0d272e6 commit 383ceca

4 files changed

Lines changed: 30 additions & 58 deletions

File tree

otel-collector.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ receivers:
44
grpc:
55
endpoint: 0.0.0.0:4317
66

7-
processors:
8-
batch:
9-
timeout: 1s
10-
send_batch_size: 50
11-
127
exporters:
138
otlphttp/dashboard:
149
endpoint: http://dashboard:3000
@@ -20,7 +15,6 @@ service:
2015
pipelines:
2116
traces:
2217
receivers: [otlp]
23-
processors: [batch]
2418
exporters: [otlphttp/dashboard]
2519

2620
telemetry:

public/index.html

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -649,13 +649,15 @@
649649

650650
switch (eventType) {
651651
case 'agent.created':
652-
return 'Agent ' + (attrs['agent.id'] || '') + ' provisioned';
652+
return (attrs['user.id'] || '') + ' → ' + (attrs['agent.id'] || '');
653653
case 'agent.terminated':
654654
return 'Agent ' + (attrs['agent.id'] || '') + ' terminated';
655655
case 'request.allowed':
656656
return (attrs['http.host'] || '') + ' -- allowed';
657657
case 'request.denied':
658658
return (attrs['http.host'] || '') + ' -- denied' + (attrs['denial.reason'] ? ': ' + attrs['denial.reason'] : '');
659+
case 'agent.prompted':
660+
return (attrs['user.id'] || 'user') + ' → ' + (attrs['agent.id'] || 'agent');
659661
default:
660662
if (attrs['agent.id']) return eventType + ' for ' + attrs['agent.id'];
661663
return eventType;
@@ -711,12 +713,11 @@
711713
if (userId) Topology.removeUserNode(userId);
712714
}
713715

714-
// User actions — light up the user→platform edge.
715-
if (eventType === 'agent.requested' || eventType === 'prompt.sent' || eventType === 'permission.change_requested' || eventType === 'termination.requested') {
716+
// agent.prompted: user→platform then platform→agent sequentially
717+
if (eventType === 'agent.prompted') {
716718
const userId = attrs['user.id'] || '';
717-
if (userId) {
718-
Topology.highlightEdge(userId, Topology.EDGE_HIGHLIGHT_MS);
719-
}
719+
if (userId) Topology.highlightEdge(userId, Topology.EDGE_HIGHLIGHT_MS);
720+
if (agentId) setTimeout(() => Topology.highlightEdge('platform-' + agentId, Topology.EDGE_HIGHLIGHT_MS), 400);
720721
}
721722

722723
// Agent lifecycle. Light up the platform→agent edge: the platform is
@@ -731,11 +732,6 @@
731732
Topology.removeAgentRuntimeNode(agentId);
732733
}
733734

734-
// Prompt delivered: platform exec'd into the agent via the supervisor.
735-
if (eventType === 'agent.prompted') {
736-
if (agentId) Topology.highlightEdge('platform-' + agentId, Topology.EDGE_HIGHLIGHT_MS);
737-
}
738-
739735
// Gateway decisions
740736
if (eventType === 'auth.allowed' || eventType === 'request.allowed' || eventType === 'request.denied' || eventType === 'permission.checked') {
741737
const decision = {

server.js

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -228,46 +228,27 @@ const server = http.createServer(async (req, res) => {
228228
}
229229
}
230230

231-
// POST /api/reset - proxy to demo-admin reset endpoint
231+
// POST /api/reset - trigger full demo reset via host-side reset server
232232
if (pathName === '/api/reset' && method === 'POST') {
233-
const adminHost = process.env.ADMIN_HOST || 'demo-admin';
234-
const adminPort = process.env.ADMIN_PORT || '8080';
235-
return new Promise((resolve) => {
236-
let responded = false;
237-
const proxyReq = http.request({
238-
hostname: adminHost,
239-
port: parseInt(adminPort),
240-
path: '/api/reset',
241-
method: 'POST',
242-
headers: { 'Content-Type': 'application/json' },
243-
timeout: 30000,
244-
}, (proxyRes) => {
245-
let body = '';
246-
proxyRes.on('data', chunk => { body += chunk; });
247-
proxyRes.on('end', () => {
248-
if (responded) return;
249-
responded = true;
250-
events = [];
251-
res.writeHead(proxyRes.statusCode, { 'Content-Type': 'application/json' });
252-
res.end(body);
253-
resolve();
233+
const resetServerUrl = process.env.RESET_SERVER_URL || 'http://host.docker.internal:8765/reset';
234+
try {
235+
await new Promise((resolve, reject) => {
236+
const resetReq = http.request(resetServerUrl, { method: 'POST' }, (resetRes) => {
237+
resetRes.resume();
238+
if (resetRes.statusCode === 200) resolve();
239+
else reject(new Error(`reset server returned ${resetRes.statusCode}`));
254240
});
241+
resetReq.on('error', reject);
242+
resetReq.setTimeout(180000, () => { resetReq.destroy(); reject(new Error('reset timed out')); });
243+
resetReq.end();
255244
});
256-
proxyReq.on('error', (err) => {
257-
if (responded) return;
258-
responded = true;
259-
sendJson(res, 502, { error: 'Failed to reach demo-admin: ' + err.message });
260-
resolve();
261-
});
262-
proxyReq.on('timeout', () => {
263-
proxyReq.destroy();
264-
if (responded) return;
265-
responded = true;
266-
sendJson(res, 504, { error: 'demo-admin request timed out' });
267-
resolve();
268-
});
269-
proxyReq.end();
270-
});
245+
} catch (err) {
246+
return sendJson(res, 502, { error: `reset failed: ${err.message}` });
247+
}
248+
events = [];
249+
const resetEvent = { source: 'dashboard', event_type: 'demo.reset', timestamp: new Date().toISOString(), attributes: {}, received_at: new Date().toISOString() };
250+
broadcastEvent(resetEvent);
251+
return sendJson(res, 200, {});
271252
}
272253

273254
// Serve static files for everything else
@@ -283,8 +264,9 @@ const wss = new WebSocketServer({ server, path: '/ws' });
283264
wss.on('connection', (ws) => {
284265
wsClients.add(ws);
285266

286-
// Send event history on connect
287-
ws.send(JSON.stringify({ type: 'history', events }));
267+
// Send event history on connect, sorted chronologically
268+
const sortedEvents = [...events].sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
269+
ws.send(JSON.stringify({ type: 'history', events: sortedEvents }));
288270

289271
ws.on('close', () => {
290272
wsClients.delete(ws);

translate.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function translateOtlpLogs(otlp) {
5353
}
5454
}
5555
}
56-
return out;
56+
return out.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
5757
}
5858

5959
// ─── Jaeger trace export → flat events ─────────────────────────────────────
@@ -198,7 +198,7 @@ function translateOtlpTraces(payload) {
198198
}
199199
}
200200
}
201-
return out;
201+
return out.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
202202
}
203203

204204
module.exports = { translateOtlpLogs, translateJaegerTraces, translateOtlpTraces };

0 commit comments

Comments
 (0)