@bpmnkit/operate 0.0.5

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 (72) hide show
  1. package/README.md +131 -0
  2. package/dist/components/badge.d.ts +2 -0
  3. package/dist/components/badge.js +2 -0
  4. package/dist/components/card.d.ts +2 -0
  5. package/dist/components/card.js +2 -0
  6. package/dist/components/chart.d.ts +6 -0
  7. package/dist/components/chart.js +161 -0
  8. package/dist/components/filter-table.d.ts +20 -0
  9. package/dist/components/filter-table.js +215 -0
  10. package/dist/components/table.d.ts +2 -0
  11. package/dist/components/table.js +2 -0
  12. package/dist/css.d.ts +2 -0
  13. package/dist/css.js +1071 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +2 -0
  16. package/dist/mock-data.d.ts +18 -0
  17. package/dist/mock-data.js +509 -0
  18. package/dist/operate.d.ts +3 -0
  19. package/dist/operate.js +308 -0
  20. package/dist/router.d.ts +9 -0
  21. package/dist/router.js +49 -0
  22. package/dist/stores/base.d.ts +20 -0
  23. package/dist/stores/base.js +32 -0
  24. package/dist/stores/dashboard.d.ts +6 -0
  25. package/dist/stores/dashboard.js +19 -0
  26. package/dist/stores/decisions.d.ts +9 -0
  27. package/dist/stores/decisions.js +19 -0
  28. package/dist/stores/definitions.d.ts +9 -0
  29. package/dist/stores/definitions.js +19 -0
  30. package/dist/stores/incidents.d.ts +10 -0
  31. package/dist/stores/incidents.js +27 -0
  32. package/dist/stores/instances.d.ts +15 -0
  33. package/dist/stores/instances.js +33 -0
  34. package/dist/stores/jobs.d.ts +10 -0
  35. package/dist/stores/jobs.js +19 -0
  36. package/dist/stores/tasks.d.ts +10 -0
  37. package/dist/stores/tasks.js +19 -0
  38. package/dist/stream.d.ts +12 -0
  39. package/dist/stream.js +42 -0
  40. package/dist/types.d.ts +48 -0
  41. package/dist/types.js +2 -0
  42. package/dist/views/dashboard.d.ts +6 -0
  43. package/dist/views/dashboard.js +98 -0
  44. package/dist/views/decision-detail.d.ts +15 -0
  45. package/dist/views/decision-detail.js +167 -0
  46. package/dist/views/decisions.d.ts +7 -0
  47. package/dist/views/decisions.js +78 -0
  48. package/dist/views/definition-detail.d.ts +15 -0
  49. package/dist/views/definition-detail.js +339 -0
  50. package/dist/views/definitions.d.ts +7 -0
  51. package/dist/views/definitions.js +70 -0
  52. package/dist/views/header.d.ts +9 -0
  53. package/dist/views/header.js +49 -0
  54. package/dist/views/incident-detail.d.ts +14 -0
  55. package/dist/views/incident-detail.js +540 -0
  56. package/dist/views/incidents.d.ts +7 -0
  57. package/dist/views/incidents.js +108 -0
  58. package/dist/views/instance-detail.d.ts +16 -0
  59. package/dist/views/instance-detail.js +728 -0
  60. package/dist/views/instances.d.ts +7 -0
  61. package/dist/views/instances.js +249 -0
  62. package/dist/views/jobs.d.ts +6 -0
  63. package/dist/views/jobs.js +60 -0
  64. package/dist/views/messages.d.ts +11 -0
  65. package/dist/views/messages.js +431 -0
  66. package/dist/views/nav.d.ts +10 -0
  67. package/dist/views/nav.js +60 -0
  68. package/dist/views/task-detail.d.ts +13 -0
  69. package/dist/views/task-detail.js +187 -0
  70. package/dist/views/tasks.d.ts +7 -0
  71. package/dist/views/tasks.js +72 -0
  72. package/package.json +46 -0
@@ -0,0 +1,540 @@
1
+ import { BpmnCanvas } from "@bpmnkit/canvas";
2
+ import { createConfigPanelPlugin } from "@bpmnkit/plugins/config-panel";
3
+ import { createConfigPanelBpmnPlugin } from "@bpmnkit/plugins/config-panel-bpmn";
4
+ import { createTokenHighlightPlugin } from "@bpmnkit/plugins/token-highlight";
5
+ import { badge } from "../components/badge.js";
6
+ import { MOCK_BPMN_XML, MOCK_INCIDENTS } from "../mock-data.js";
7
+ function relTime(iso) {
8
+ if (!iso)
9
+ return "—";
10
+ const diff = Date.now() - new Date(iso).getTime();
11
+ const m = Math.floor(diff / 60_000);
12
+ if (m < 1)
13
+ return "just now";
14
+ if (m < 60)
15
+ return `${m}m ago`;
16
+ const h = Math.floor(m / 60);
17
+ if (h < 24)
18
+ return `${h}h ago`;
19
+ return `${Math.floor(h / 24)}d ago`;
20
+ }
21
+ function metaRow(label, value, amber = false) {
22
+ const row = document.createElement("div");
23
+ row.className = "op-task-meta-row";
24
+ const l = document.createElement("span");
25
+ l.className = "op-task-meta-label";
26
+ l.textContent = label;
27
+ const v = document.createElement("span");
28
+ v.className = "op-task-meta-value";
29
+ v.textContent = value;
30
+ if (amber)
31
+ v.style.color = "var(--op-c-amber)";
32
+ row.appendChild(l);
33
+ row.appendChild(v);
34
+ return row;
35
+ }
36
+ export function createIncidentDetailView(incidentKey, store, cfg, onBack) {
37
+ const el = document.createElement("div");
38
+ el.className = "op-view op-instance-detail";
39
+ // Breadcrumb
40
+ const breadcrumb = document.createElement("div");
41
+ breadcrumb.className = "op-breadcrumb";
42
+ const backBtn = document.createElement("button");
43
+ backBtn.className = "op-back-btn";
44
+ backBtn.textContent = "← Incidents";
45
+ backBtn.addEventListener("click", onBack);
46
+ breadcrumb.appendChild(backBtn);
47
+ el.appendChild(breadcrumb);
48
+ // Process chain (shows Root / Parent / Current)
49
+ const processChainEl = document.createElement("div");
50
+ processChainEl.className = "op-process-chain";
51
+ el.appendChild(processChainEl);
52
+ // Meta row
53
+ const meta = document.createElement("div");
54
+ meta.className = "op-instance-meta";
55
+ el.appendChild(meta);
56
+ // Canvas + sidebar layout
57
+ const layout = document.createElement("div");
58
+ layout.className = "op-detail-layout";
59
+ el.appendChild(layout);
60
+ // Canvas pane
61
+ const canvasWrap = document.createElement("div");
62
+ canvasWrap.className = "op-detail-canvas";
63
+ layout.appendChild(canvasWrap);
64
+ // Sidebar pane
65
+ const sidebar = document.createElement("div");
66
+ sidebar.className = "op-detail-sidebar";
67
+ sidebar.dataset.bpmnHudTheme = cfg.theme;
68
+ layout.appendChild(sidebar);
69
+ // Tabs
70
+ const tabNames = ["Details", "Properties", "AI Assist"];
71
+ const tabBar = document.createElement("div");
72
+ tabBar.className = "op-detail-tabs";
73
+ const tabPanels = [];
74
+ let activeTab = 0;
75
+ for (let i = 0; i < tabNames.length; i++) {
76
+ const btn = document.createElement("button");
77
+ btn.className = `op-detail-tab${i === 0 ? " op-detail-tab--active" : ""}`;
78
+ btn.textContent = tabNames[i] ?? "";
79
+ const idx = i;
80
+ btn.addEventListener("click", () => {
81
+ activeTab = idx;
82
+ for (let j = 0; j < tabNames.length; j++) {
83
+ const tabEl = tabBar.children[j];
84
+ const panelEl = tabPanels[j];
85
+ if (tabEl) {
86
+ if (j === activeTab)
87
+ tabEl.classList.add("op-detail-tab--active");
88
+ else
89
+ tabEl.classList.remove("op-detail-tab--active");
90
+ }
91
+ if (panelEl) {
92
+ panelEl.style.display = j === activeTab ? "" : "none";
93
+ }
94
+ }
95
+ });
96
+ tabBar.appendChild(btn);
97
+ }
98
+ sidebar.appendChild(tabBar);
99
+ const detailsPanel = document.createElement("div");
100
+ detailsPanel.className = "op-detail-panel";
101
+ tabPanels.push(detailsPanel);
102
+ sidebar.appendChild(detailsPanel);
103
+ const propsPane = document.createElement("div");
104
+ propsPane.className = "op-detail-panel op-props-pane";
105
+ propsPane.style.display = "none";
106
+ tabPanels.push(propsPane);
107
+ sidebar.appendChild(propsPane);
108
+ const propsPlaceholder = document.createElement("div");
109
+ propsPlaceholder.className = "op-props-placeholder";
110
+ propsPlaceholder.textContent = "Click an element to view its properties";
111
+ propsPane.appendChild(propsPlaceholder);
112
+ // Config panel (read-only: applyChange is a no-op)
113
+ let latestDefs = null;
114
+ const configPanel = createConfigPanelPlugin({
115
+ getDefinitions: () => latestDefs,
116
+ applyChange: () => { },
117
+ container: propsPane,
118
+ onPanelShow: () => {
119
+ propsPlaceholder.style.display = "none";
120
+ },
121
+ onPanelHide: () => {
122
+ propsPlaceholder.style.display = "";
123
+ },
124
+ });
125
+ const configPanelBpmn = createConfigPanelBpmnPlugin(configPanel);
126
+ const bridgePlugin = {
127
+ name: "op-select-bridge",
128
+ install(api) {
129
+ const emit = api.emit.bind(api);
130
+ api.on("element:click", (id) => emit("editor:select", [id]));
131
+ api.on("diagram:load", (defs) => {
132
+ latestDefs = defs;
133
+ });
134
+ },
135
+ };
136
+ const aiPanel = document.createElement("div");
137
+ aiPanel.className = "op-detail-panel";
138
+ aiPanel.style.display = "none";
139
+ tabPanels.push(aiPanel);
140
+ sidebar.appendChild(aiPanel);
141
+ // AI Assist panel content
142
+ const aiAssistDiv = document.createElement("div");
143
+ aiAssistDiv.className = "op-ai-assist-panel";
144
+ const aiIntro = document.createElement("p");
145
+ aiIntro.className = "op-ai-assist-intro";
146
+ aiIntro.textContent =
147
+ "Analyze this incident with AI to understand the root cause and get concrete remediation steps.";
148
+ aiAssistDiv.appendChild(aiIntro);
149
+ const analyzeBtn = document.createElement("button");
150
+ analyzeBtn.className = "op-action-btn op-action-btn--primary";
151
+ analyzeBtn.textContent = "Analyze with AI";
152
+ aiAssistDiv.appendChild(analyzeBtn);
153
+ const aiResponseEl = document.createElement("pre");
154
+ aiResponseEl.className = "op-ai-response";
155
+ aiResponseEl.style.display = "none";
156
+ aiAssistDiv.appendChild(aiResponseEl);
157
+ aiPanel.appendChild(aiAssistDiv);
158
+ analyzeBtn.addEventListener("click", () => {
159
+ analyzeBtn.disabled = true;
160
+ analyzeBtn.textContent = "Analyzing…";
161
+ aiResponseEl.textContent = "";
162
+ aiResponseEl.style.display = "none";
163
+ const headers = { "Content-Type": "application/json" };
164
+ if (cfg.profile)
165
+ headers["x-profile"] = cfg.profile;
166
+ fetch(`${cfg.proxyUrl}/operate/incident-assist`, {
167
+ method: "POST",
168
+ headers,
169
+ body: JSON.stringify({ incidentKey }),
170
+ })
171
+ .then((response) => {
172
+ if (!response.body)
173
+ throw new Error("No response body");
174
+ const reader = response.body.getReader();
175
+ const decoder = new TextDecoder();
176
+ let buffer = "";
177
+ function pump() {
178
+ return reader.read().then(({ done, value }) => {
179
+ if (done) {
180
+ analyzeBtn.disabled = false;
181
+ analyzeBtn.textContent = "Analyze with AI";
182
+ return;
183
+ }
184
+ buffer += decoder.decode(value, { stream: true });
185
+ const lines = buffer.split("\n");
186
+ buffer = lines.pop() ?? "";
187
+ for (const line of lines) {
188
+ if (!line.startsWith("data: "))
189
+ continue;
190
+ try {
191
+ const event = JSON.parse(line.slice(6));
192
+ if (event.type === "token" && event.text) {
193
+ if (aiResponseEl.style.display === "none") {
194
+ aiResponseEl.style.display = "";
195
+ }
196
+ aiResponseEl.textContent += event.text;
197
+ }
198
+ else if (event.type === "done") {
199
+ analyzeBtn.disabled = false;
200
+ analyzeBtn.textContent = "Analyze with AI";
201
+ }
202
+ else if (event.type === "error") {
203
+ analyzeBtn.disabled = false;
204
+ analyzeBtn.textContent = "Analyze with AI";
205
+ }
206
+ }
207
+ catch {
208
+ /* ignore malformed lines */
209
+ }
210
+ }
211
+ return pump();
212
+ });
213
+ }
214
+ return pump();
215
+ })
216
+ .catch(() => {
217
+ analyzeBtn.disabled = false;
218
+ analyzeBtn.textContent = "Analyze with AI";
219
+ });
220
+ });
221
+ // Canvas
222
+ const tokenHighlight = createTokenHighlightPlugin();
223
+ let canvas = null;
224
+ function loadCanvas(xml) {
225
+ canvas?.destroy();
226
+ canvasWrap.innerHTML = "";
227
+ canvas = new BpmnCanvas({
228
+ container: canvasWrap,
229
+ xml,
230
+ theme: cfg.theme,
231
+ plugins: [tokenHighlight, bridgePlugin, configPanel, configPanelBpmn],
232
+ });
233
+ }
234
+ // Render meta bar
235
+ function renderMeta(inc) {
236
+ meta.innerHTML = "";
237
+ const type = document.createElement("span");
238
+ type.className = "op-incident-type";
239
+ type.textContent = inc.errorType ?? "UNKNOWN";
240
+ meta.appendChild(type);
241
+ meta.appendChild(badge(inc.state ?? "UNKNOWN"));
242
+ const key = document.createElement("span");
243
+ key.className = "op-instance-key";
244
+ key.textContent = inc.incidentKey ?? incidentKey;
245
+ meta.appendChild(key);
246
+ const created = document.createElement("span");
247
+ created.className = "op-instance-time";
248
+ created.textContent = `Created ${relTime(inc.creationTime)}`;
249
+ meta.appendChild(created);
250
+ }
251
+ // Render process chain: fetches the instance hierarchy up to the root
252
+ function renderProcessChain(segments) {
253
+ processChainEl.innerHTML = "";
254
+ for (let i = 0; i < segments.length; i++) {
255
+ const seg = segments[i];
256
+ if (!seg)
257
+ continue;
258
+ if (i > 0) {
259
+ const sep = document.createElement("span");
260
+ sep.className = "op-process-chain-sep";
261
+ sep.textContent = " / ";
262
+ processChainEl.appendChild(sep);
263
+ }
264
+ const btn = document.createElement("button");
265
+ btn.className = "op-process-chain-link";
266
+ btn.textContent = seg.name;
267
+ if (cfg.navigate) {
268
+ const key = seg.instanceKey;
269
+ btn.addEventListener("click", () => cfg.navigate?.(`/instances/${key}`));
270
+ }
271
+ else {
272
+ btn.disabled = true;
273
+ }
274
+ processChainEl.appendChild(btn);
275
+ }
276
+ }
277
+ async function fetchProcessChain(inc) {
278
+ if (!inc.processInstanceKey)
279
+ return;
280
+ const headers = { accept: "application/json" };
281
+ if (cfg.profile)
282
+ headers["x-profile"] = cfg.profile;
283
+ // Walk up from current instance to root, collecting the chain
284
+ const chain = [];
285
+ let key = inc.processInstanceKey;
286
+ const seen = new Set();
287
+ while (key && !seen.has(key)) {
288
+ seen.add(key);
289
+ try {
290
+ const r = await fetch(`${cfg.proxyUrl}/api/process-instances/${key}`, { headers });
291
+ if (!r.ok)
292
+ break;
293
+ const inst = (await r.json());
294
+ chain.unshift({
295
+ name: inst.processDefinitionName ?? inst.processDefinitionId,
296
+ instanceKey: inst.processInstanceKey,
297
+ });
298
+ // Walk to parent (empty string = no parent)
299
+ key = inst.parentProcessInstanceKey || null;
300
+ }
301
+ catch {
302
+ break;
303
+ }
304
+ }
305
+ if (chain.length > 0)
306
+ renderProcessChain(chain);
307
+ }
308
+ // Render job details section (connector config, error code)
309
+ function renderJobDetails(job) {
310
+ const section = document.createElement("div");
311
+ section.className = "op-job-section";
312
+ const title = document.createElement("div");
313
+ title.className = "op-job-section-title";
314
+ title.textContent = "Job Details";
315
+ section.appendChild(title);
316
+ if (job.type)
317
+ section.appendChild(metaRow("Type", job.type));
318
+ if (job.errorCode)
319
+ section.appendChild(metaRow("Error Code", job.errorCode, true));
320
+ if (job.retries !== undefined)
321
+ section.appendChild(metaRow("Retries Left", String(job.retries)));
322
+ const headers = job.customHeaders;
323
+ if (headers && Object.keys(headers).length > 0) {
324
+ const hTitle = document.createElement("div");
325
+ hTitle.className = "op-job-section-title";
326
+ hTitle.style.marginTop = "12px";
327
+ hTitle.textContent = "Task Headers";
328
+ section.appendChild(hTitle);
329
+ const hPre = document.createElement("pre");
330
+ hPre.className = "op-job-headers";
331
+ hPre.textContent = Object.entries(headers)
332
+ .map(([k, v]) => `${k}: ${v}`)
333
+ .join("\n");
334
+ section.appendChild(hPre);
335
+ }
336
+ detailsPanel.appendChild(section);
337
+ }
338
+ async function fetchJobDetails(jobKey) {
339
+ const headers = { accept: "application/json" };
340
+ if (cfg.profile)
341
+ headers["x-profile"] = cfg.profile;
342
+ try {
343
+ const r = await fetch(`${cfg.proxyUrl}/api/jobs/${jobKey}`, { headers });
344
+ if (!r.ok)
345
+ return;
346
+ const job = (await r.json());
347
+ renderJobDetails(job);
348
+ }
349
+ catch {
350
+ /* ignore */
351
+ }
352
+ }
353
+ // Render details panel
354
+ function renderDetails(inc) {
355
+ detailsPanel.innerHTML = "";
356
+ const isActive = inc.state === "ACTIVE";
357
+ detailsPanel.appendChild(metaRow("Error Type", inc.errorType ?? "UNKNOWN", isActive));
358
+ const msgRow = document.createElement("div");
359
+ msgRow.className = "op-task-meta-row";
360
+ const msgLabel = document.createElement("span");
361
+ msgLabel.className = "op-task-meta-label";
362
+ msgLabel.textContent = "Error Message";
363
+ const msgVal = document.createElement("span");
364
+ msgVal.className = "op-task-meta-value";
365
+ msgVal.style.whiteSpace = "pre-wrap";
366
+ msgVal.style.wordBreak = "break-word";
367
+ msgVal.textContent = inc.errorMessage ?? "—";
368
+ msgRow.appendChild(msgLabel);
369
+ msgRow.appendChild(msgVal);
370
+ detailsPanel.appendChild(msgRow);
371
+ detailsPanel.appendChild(metaRow("Element ID", inc.elementId ?? "—"));
372
+ detailsPanel.appendChild(metaRow("Process", inc.processDefinitionId ?? "—"));
373
+ detailsPanel.appendChild(metaRow("Process Instance Key", inc.processInstanceKey ?? "—"));
374
+ detailsPanel.appendChild(metaRow("Element Instance Key", inc.elementInstanceKey ?? "—"));
375
+ detailsPanel.appendChild(metaRow("Job Key", inc.jobKey ?? "—"));
376
+ detailsPanel.appendChild(metaRow("Root Process Instance", inc.rootProcessInstanceKey ?? "—"));
377
+ detailsPanel.appendChild(metaRow("Tenant", inc.tenantId ?? "—"));
378
+ detailsPanel.appendChild(metaRow("Created", relTime(inc.creationTime)));
379
+ // Action buttons
380
+ const hasRetry = inc.errorType === "JOB_NO_RETRIES" && isActive;
381
+ const hasResolve = isActive;
382
+ if (hasRetry || hasResolve) {
383
+ const btnsDiv = document.createElement("div");
384
+ btnsDiv.className = "op-action-btns";
385
+ const feedbackEl = document.createElement("div");
386
+ feedbackEl.className = "op-action-feedback";
387
+ feedbackEl.style.display = "none";
388
+ function showFeedback(msg, ok) {
389
+ feedbackEl.textContent = msg;
390
+ feedbackEl.className = `op-action-feedback op-action-feedback--${ok ? "ok" : "err"}`;
391
+ feedbackEl.style.display = "";
392
+ }
393
+ if (hasRetry) {
394
+ const retryBtn = document.createElement("button");
395
+ retryBtn.className = "op-action-btn op-action-btn--primary";
396
+ retryBtn.textContent = "↻ Retry Job";
397
+ retryBtn.addEventListener("click", () => {
398
+ retryBtn.disabled = true;
399
+ const jobKey = inc.jobKey ?? "";
400
+ const h = { "Content-Type": "application/json" };
401
+ if (cfg.profile)
402
+ h["x-profile"] = cfg.profile;
403
+ fetch(`${cfg.proxyUrl}/api/jobs/${jobKey}/retries`, {
404
+ method: "PATCH",
405
+ headers: h,
406
+ body: JSON.stringify({ retries: 3 }),
407
+ })
408
+ .then((r) => {
409
+ if (r.ok)
410
+ showFeedback("Job retries updated.", true);
411
+ else
412
+ showFeedback(`Error: ${r.status}`, false);
413
+ })
414
+ .catch((err) => {
415
+ showFeedback(String(err), false);
416
+ });
417
+ });
418
+ btnsDiv.appendChild(retryBtn);
419
+ }
420
+ if (hasResolve) {
421
+ const resolveBtn = document.createElement("button");
422
+ resolveBtn.className = "op-action-btn";
423
+ resolveBtn.textContent = "✓ Resolve Incident";
424
+ resolveBtn.addEventListener("click", () => {
425
+ resolveBtn.disabled = true;
426
+ const iKey = inc.incidentKey ?? "";
427
+ const h = { "Content-Type": "application/json" };
428
+ if (cfg.profile)
429
+ h["x-profile"] = cfg.profile;
430
+ fetch(`${cfg.proxyUrl}/api/incidents/${iKey}/resolution`, {
431
+ method: "POST",
432
+ headers: h,
433
+ body: JSON.stringify({}),
434
+ })
435
+ .then((r) => {
436
+ if (r.ok)
437
+ showFeedback("Incident resolved.", true);
438
+ else
439
+ showFeedback(`Error: ${r.status}`, false);
440
+ })
441
+ .catch((err) => {
442
+ showFeedback(String(err), false);
443
+ });
444
+ });
445
+ btnsDiv.appendChild(resolveBtn);
446
+ }
447
+ detailsPanel.appendChild(btnsDiv);
448
+ detailsPanel.appendChild(feedbackEl);
449
+ }
450
+ // Fetch and append job details asynchronously (connector config, error code)
451
+ if (!cfg.mock && inc.jobKey) {
452
+ fetchJobDetails(inc.jobKey).catch(() => { });
453
+ }
454
+ }
455
+ // Incident lookup
456
+ function getIncident() {
457
+ return store.state.data?.items.find((i) => i.incidentKey === incidentKey) ?? null;
458
+ }
459
+ let storeUnsub;
460
+ if (cfg.mock) {
461
+ const mockInc = MOCK_INCIDENTS.find((i) => i.incidentKey === incidentKey) ?? null;
462
+ loadCanvas(MOCK_BPMN_XML);
463
+ if (mockInc) {
464
+ renderMeta(mockInc);
465
+ renderDetails(mockInc);
466
+ if (mockInc.elementId) {
467
+ setTimeout(() => tokenHighlight.api.setActive([mockInc.elementId ?? ""]), 100);
468
+ }
469
+ }
470
+ else {
471
+ meta.textContent = `Incident ${incidentKey}`;
472
+ }
473
+ storeUnsub = store.subscribe(() => {
474
+ const inc = getIncident();
475
+ if (inc) {
476
+ renderMeta(inc);
477
+ renderDetails(inc);
478
+ }
479
+ });
480
+ }
481
+ else {
482
+ let xmlStarted = false;
483
+ function startFetch(inc) {
484
+ if (xmlStarted)
485
+ return;
486
+ xmlStarted = true;
487
+ renderMeta(inc);
488
+ renderDetails(inc);
489
+ fetchProcessChain(inc).catch(() => { });
490
+ const pdKey = inc.processDefinitionKey;
491
+ if (pdKey) {
492
+ fetch(`${cfg.proxyUrl}/api/process-definitions/${pdKey}/xml`, {
493
+ headers: {
494
+ accept: "text/xml",
495
+ ...(cfg.profile ? { "x-profile": cfg.profile } : {}),
496
+ },
497
+ })
498
+ .then((r) => r.text())
499
+ .then((xml) => {
500
+ loadCanvas(xml);
501
+ if (inc.elementId) {
502
+ tokenHighlight.api.setActive([inc.elementId]);
503
+ }
504
+ })
505
+ .catch(() => {
506
+ // canvas not available
507
+ });
508
+ }
509
+ }
510
+ // Try to get incident from store immediately; fall back to direct fetch
511
+ const existing = getIncident();
512
+ if (existing) {
513
+ startFetch(existing);
514
+ }
515
+ else {
516
+ const headers = { accept: "application/json" };
517
+ if (cfg.profile)
518
+ headers["x-profile"] = cfg.profile;
519
+ fetch(`${cfg.proxyUrl}/api/incidents/${incidentKey}`, { headers })
520
+ .then((r) => r.json())
521
+ .then((inc) => startFetch(inc))
522
+ .catch(() => {
523
+ meta.textContent = `Incident ${incidentKey}`;
524
+ });
525
+ }
526
+ storeUnsub = store.subscribe(() => {
527
+ const inc = getIncident();
528
+ if (inc)
529
+ startFetch(inc);
530
+ });
531
+ }
532
+ return {
533
+ el,
534
+ destroy() {
535
+ canvas?.destroy();
536
+ storeUnsub();
537
+ },
538
+ };
539
+ }
540
+ //# sourceMappingURL=incident-detail.js.map
@@ -0,0 +1,7 @@
1
+ import type { IncidentsStore } from "../stores/incidents.js";
2
+ import type { IncidentResult } from "../types.js";
3
+ export declare function createIncidentsView(store: IncidentsStore, onSelect?: (inc: IncidentResult) => void): {
4
+ el: HTMLElement;
5
+ destroy(): void;
6
+ };
7
+ //# sourceMappingURL=incidents.d.ts.map
@@ -0,0 +1,108 @@
1
+ import { badge } from "../components/badge.js";
2
+ import { createFilterTable } from "../components/filter-table.js";
3
+ function relTime(iso) {
4
+ if (!iso)
5
+ return "—";
6
+ const diff = Date.now() - new Date(iso).getTime();
7
+ const m = Math.floor(diff / 60_000);
8
+ if (m < 1)
9
+ return "just now";
10
+ if (m < 60)
11
+ return `${m}m ago`;
12
+ const h = Math.floor(m / 60);
13
+ if (h < 24)
14
+ return `${h}h ago`;
15
+ return `${Math.floor(h / 24)}d ago`;
16
+ }
17
+ export function createIncidentsView(store, onSelect) {
18
+ const el = document.createElement("div");
19
+ el.className = "op-view";
20
+ let activeState = "";
21
+ let allItems = [];
22
+ const { el: tableEl, setRows } = createFilterTable({
23
+ columns: [
24
+ {
25
+ label: "Type",
26
+ width: "180px",
27
+ render: (row) => row.errorType ?? "UNKNOWN",
28
+ sortValue: (row) => row.errorType ?? "",
29
+ },
30
+ {
31
+ label: "Message",
32
+ render: (row) => {
33
+ const span = document.createElement("span");
34
+ span.className = "op-incident-msg-cell";
35
+ span.title = row.errorMessage ?? "";
36
+ span.textContent = row.errorMessage ?? "—";
37
+ return span;
38
+ },
39
+ },
40
+ {
41
+ label: "Process",
42
+ width: "180px",
43
+ render: (row) => row.processDefinitionId ?? "—",
44
+ sortValue: (row) => row.processDefinitionId ?? "",
45
+ },
46
+ {
47
+ label: "Instance",
48
+ width: "140px",
49
+ render: (row) => row.processInstanceKey ?? "—",
50
+ sortValue: (row) => row.processInstanceKey ?? "",
51
+ },
52
+ {
53
+ label: "State",
54
+ width: "100px",
55
+ render: (row) => badge(row.state ?? "UNKNOWN"),
56
+ sortValue: (row) => row.state ?? "",
57
+ },
58
+ {
59
+ label: "Created",
60
+ width: "100px",
61
+ render: (row) => relTime(row.creationTime),
62
+ sortValue: (row) => row.creationTime ?? "",
63
+ },
64
+ ],
65
+ searchFn: (row) => [row.errorType, row.errorMessage, row.processDefinitionId, row.processInstanceKey, row.state]
66
+ .filter(Boolean)
67
+ .join(" "),
68
+ onRowClick: onSelect,
69
+ emptyText: "No incidents",
70
+ });
71
+ function applyFilter() {
72
+ const rows = activeState ? allItems.filter((i) => i.state === activeState) : allItems;
73
+ setRows(rows);
74
+ }
75
+ const filterBar = document.createElement("div");
76
+ filterBar.className = "op-filter-bar";
77
+ const stateFilters = [
78
+ { label: "All", value: "" },
79
+ { label: "Active", value: "ACTIVE" },
80
+ { label: "Resolved", value: "RESOLVED" },
81
+ { label: "Migrated", value: "MIGRATED" },
82
+ { label: "Pending", value: "PENDING" },
83
+ ];
84
+ for (const f of stateFilters) {
85
+ const btn = document.createElement("button");
86
+ btn.className = `op-filter-btn${f.value === activeState ? " op-filter-btn--active" : ""}`;
87
+ btn.textContent = f.label;
88
+ btn.addEventListener("click", () => {
89
+ activeState = f.value;
90
+ for (const b of Array.from(filterBar.querySelectorAll(".op-filter-btn"))) {
91
+ b.classList.remove("op-filter-btn--active");
92
+ }
93
+ btn.classList.add("op-filter-btn--active");
94
+ applyFilter();
95
+ });
96
+ filterBar.appendChild(btn);
97
+ }
98
+ el.appendChild(filterBar);
99
+ el.appendChild(tableEl);
100
+ function render() {
101
+ allItems = store.state.data?.items ?? [];
102
+ applyFilter();
103
+ }
104
+ const unsub = store.subscribe(render);
105
+ render();
106
+ return { el, destroy: unsub };
107
+ }
108
+ //# sourceMappingURL=incidents.js.map
@@ -0,0 +1,16 @@
1
+ import type { InstancesStore } from "../stores/instances.js";
2
+ interface Config {
3
+ proxyUrl: string;
4
+ profile: string | null;
5
+ interval: number;
6
+ mock: boolean;
7
+ theme: "light" | "dark";
8
+ navigate?: (path: string) => void;
9
+ }
10
+ export declare function createInstanceDetailView(instanceKey: string, instancesStore: InstancesStore, cfg: Config, onBack: () => void): {
11
+ el: HTMLElement;
12
+ setTheme(t: "light" | "dark"): void;
13
+ destroy(): void;
14
+ };
15
+ export {};
16
+ //# sourceMappingURL=instance-detail.d.ts.map