@acorex-platform/module-system-insight-client 0.0.21

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.
@@ -0,0 +1,708 @@
1
+ import { TRACE_TIMELINE_PAGE_COMPONENT_KEY, TRACE_FLOW_PAGE_COMPONENT_KEY } from '@acorex-platform/module-system-insight-contracts';
2
+ import * as i0 from '@angular/core';
3
+ import { Injectable, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { convertArrayToDataSource } from '@acorex/cdk/common';
5
+ import * as i1 from '@acorex/components/badge';
6
+ import { AXBadgeModule } from '@acorex/components/badge';
7
+ import * as i2 from '@acorex/components/button';
8
+ import { AXButtonModule } from '@acorex/components/button';
9
+ import * as i3 from '@acorex/components/data-table';
10
+ import { AXDataTableModule } from '@acorex/components/data-table';
11
+ import * as i4 from '@acorex/components/loading';
12
+ import { AXLoadingModule } from '@acorex/components/loading';
13
+ import * as i5 from '@acorex/components/progress-bar';
14
+ import { AXProgressBarModule } from '@acorex/components/progress-bar';
15
+ import * as i7 from '@acorex/core/translation';
16
+ import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
17
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
18
+ import { AXPQueryExecutor } from '@acorex-platform/framework-client/runtime';
19
+ import { runtimeQueryPayloadData } from '@acorex-platform/framework-shared/core';
20
+ import * as i6 from '@angular/common';
21
+ import { CommonModule } from '@angular/common';
22
+ import { arrangeWorkflowDiagram, AXPWorkflowDesignerComponent } from '@acorex-platform/module-platform-management-client';
23
+
24
+ //#region ---- Trace page providers ----
25
+ class AXMTraceTimelinePageComponentProvider {
26
+ async components() {
27
+ return [
28
+ {
29
+ key: TRACE_TIMELINE_PAGE_COMPONENT_KEY,
30
+ loader: () => Promise.resolve().then(function () { return traceTimelinePage_component; }).then((m) => m.AXMTraceTimelinePageComponent),
31
+ },
32
+ {
33
+ key: TRACE_FLOW_PAGE_COMPONENT_KEY,
34
+ loader: () => Promise.resolve().then(function () { return traceFlowPage_component; }).then((m) => m.AXMTraceFlowPageComponent),
35
+ },
36
+ ];
37
+ }
38
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTraceTimelinePageComponentProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
39
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTraceTimelinePageComponentProvider }); }
40
+ }
41
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTraceTimelinePageComponentProvider, decorators: [{
42
+ type: Injectable
43
+ }] });
44
+
45
+ //#endregion
46
+ /**
47
+ * TraceEntry detail tab — same hosting pattern as workflow Activities / RuleSet Simulate.
48
+ * Chrome comes from entity details; content uses ACOREX controls only.
49
+ */
50
+ class AXMTraceTimelinePageComponent extends AXPPageLayoutBaseComponent {
51
+ #loadEffect;
52
+ constructor() {
53
+ super();
54
+ this.queryExecutor = inject(AXPQueryExecutor);
55
+ this.translationService = inject(AXTranslationService);
56
+ /** Entity record from TraceEntry detail host. */
57
+ this.rootContext = input(/* @ts-ignore */
58
+ ...(ngDevMode ? [undefined, { debugName: "rootContext" }] : /* istanbul ignore next */ []));
59
+ this.pageConfig = input(/* @ts-ignore */
60
+ ...(ngDevMode ? [undefined, { debugName: "pageConfig" }] : /* istanbul ignore next */ []));
61
+ this.loading = signal(true, /* @ts-ignore */
62
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
63
+ this.timeline = signal(null, /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "timeline" }] : /* istanbul ignore next */ []));
65
+ this.spanRows = signal([], /* @ts-ignore */
66
+ ...(ngDevMode ? [{ debugName: "spanRows" }] : /* istanbul ignore next */ []));
67
+ this.spansLabel = signal('Spans', /* @ts-ignore */
68
+ ...(ngDevMode ? [{ debugName: "spansLabel" }] : /* istanbul ignore next */ []));
69
+ this.spanCountBadge = computed(() => `${this.spanRows().length} ${this.spansLabel()}`, /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "spanCountBadge" }] : /* istanbul ignore next */ []));
71
+ this.dataSource = signal(convertArrayToDataSource([], { key: 'id', pageSize: 100 }), /* @ts-ignore */
72
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
73
+ this.traceId = computed(() => {
74
+ const ctx = this.rootContext() ?? {};
75
+ const fromTraceId = typeof ctx['traceId'] === 'string' ? ctx['traceId'].trim() : '';
76
+ if (fromTraceId) {
77
+ return fromTraceId;
78
+ }
79
+ const fromId = typeof ctx['id'] === 'string' ? ctx['id'].trim() : '';
80
+ return fromId;
81
+ }, /* @ts-ignore */
82
+ ...(ngDevMode ? [{ debugName: "traceId" }] : /* istanbul ignore next */ []));
83
+ this.#loadEffect = effect(() => {
84
+ const id = this.traceId();
85
+ void this.loadTimeline(id);
86
+ }, /* @ts-ignore */
87
+ ...(ngDevMode ? [{ debugName: "#loadEffect" }] : /* istanbul ignore next */ []));
88
+ void this.translationService
89
+ .translateAsync('@system-insight:pages.trace-timeline.spans')
90
+ .then((label) => this.spansLabel.set(label));
91
+ }
92
+ async getPageTitle() {
93
+ return this.translationService.translateAsync('@system-insight:pages.trace-timeline.title');
94
+ }
95
+ goToRelatedLogs() {
96
+ const traceId = this.timeline()?.traceId ?? this.traceId();
97
+ if (!traceId) {
98
+ return;
99
+ }
100
+ const app = this.sessionService.application?.name;
101
+ if (!app) {
102
+ return;
103
+ }
104
+ void this.router.navigate([`/${app}/m/SystemInsight/e/LogEntry/list`], {
105
+ queryParams: { traceId },
106
+ });
107
+ }
108
+ async loadTimeline(traceId) {
109
+ if (!traceId) {
110
+ this.timeline.set(null);
111
+ this.spanRows.set([]);
112
+ this.dataSource.set(convertArrayToDataSource([], { key: 'id', pageSize: 100 }));
113
+ this.loading.set(false);
114
+ return;
115
+ }
116
+ this.loading.set(true);
117
+ try {
118
+ const result = await this.queryExecutor.fetch('SystemInsight:TraceTimeline:Get', traceId);
119
+ const timeline = runtimeQueryPayloadData(result) ?? null;
120
+ const rows = this.buildSpanRows(timeline);
121
+ this.timeline.set(timeline);
122
+ this.spanRows.set(rows);
123
+ this.dataSource.set(convertArrayToDataSource(rows, { key: 'id', pageSize: 100 }));
124
+ this.recompute();
125
+ }
126
+ finally {
127
+ this.loading.set(false);
128
+ }
129
+ }
130
+ buildSpanRows(timeline) {
131
+ if (!timeline?.spans?.length) {
132
+ return [];
133
+ }
134
+ const spanStarts = timeline.spans
135
+ .map((span) => Date.parse(span.startTime))
136
+ .filter((value) => Number.isFinite(value));
137
+ const rootStart = spanStarts.length ? Math.min(...spanStarts) : Date.parse(timeline.startTime) || 0;
138
+ const maxEnd = Math.max(...timeline.spans.map((span) => {
139
+ const start = Date.parse(span.startTime);
140
+ const startMs = Number.isFinite(start) ? start : rootStart;
141
+ return startMs + (span.duration || 0);
142
+ }), rootStart + (timeline.duration || 0));
143
+ const total = Math.max(maxEnd - rootStart, timeline.duration || 1, 1);
144
+ return timeline.spans.map((span) => {
145
+ const start = Date.parse(span.startTime);
146
+ const offsetMs = Number.isFinite(start) ? Math.max(0, start - rootStart) : 0;
147
+ const widthPercent = Math.max(3, Math.min(100, ((span.duration || 0) / total) * 100));
148
+ const offsetPercent = Math.max(0, Math.min(100 - widthPercent, (offsetMs / total) * 100));
149
+ return {
150
+ ...span,
151
+ offsetPercent,
152
+ widthPercent,
153
+ durationShare: span.duration ?? 0,
154
+ };
155
+ });
156
+ }
157
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTraceTimelinePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
158
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTraceTimelinePageComponent, isStandalone: true, selector: "axm-trace-timeline-page", inputs: { rootContext: { classPropertyName: "rootContext", publicName: "rootContext", isSignal: true, isRequired: false, transformFunction: null }, pageConfig: { classPropertyName: "pageConfig", publicName: "pageConfig", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "axm-trace-timeline-page block w-full min-h-0" }, providers: [
159
+ {
160
+ provide: AXPPageLayoutBase,
161
+ useExisting: AXMTraceTimelinePageComponent,
162
+ },
163
+ ], usesInheritance: true, ngImport: i0, template: `
164
+ <div class="axm-trace-timeline flex flex-col gap-4 p-4 w-full min-h-0">
165
+ @if (loading()) {
166
+ <div class="flex justify-center py-10">
167
+ <ax-loading />
168
+ </div>
169
+ } @else if (timeline(); as data) {
170
+ <div class="flex flex-wrap items-center gap-2">
171
+ <ax-badge [text]="spanCountBadge()" color="primary" size="sm"></ax-badge>
172
+ @for (service of data.services; track service) {
173
+ <ax-badge [text]="service" color="secondary" size="sm"></ax-badge>
174
+ }
175
+ <span class="text-sm text-muted ms-auto">
176
+ {{ '@system-insight:pages.trace-timeline.duration' | translate | async }}:
177
+ {{ data.duration }} ms
178
+ </span>
179
+ <ax-button
180
+ look="outline"
181
+ color="primary"
182
+ [text]="('@system-insight:pages.trace-timeline.view-logs' | translate | async) || ''"
183
+ (onClick)="goToRelatedLogs()"
184
+ ></ax-button>
185
+ </div>
186
+
187
+ <ax-data-table
188
+ class="w-full border border-lightest rounded-md overflow-hidden"
189
+ [paging]="false"
190
+ [dataSource]="dataSource()"
191
+ >
192
+ <ax-text-column
193
+ dataField="operationName"
194
+ [caption]="'@system-insight:fields.operation' | translate | async"
195
+ [width]="'200px'"
196
+ [allowResizing]="true"
197
+ ></ax-text-column>
198
+ <ax-text-column
199
+ dataField="serviceName"
200
+ [caption]="'@system-insight:fields.service-name' | translate | async"
201
+ [width]="'140px'"
202
+ [allowResizing]="true"
203
+ ></ax-text-column>
204
+ <ax-text-column
205
+ dataField="status"
206
+ [caption]="'@system-insight:fields.status' | translate | async"
207
+ [width]="'100px'"
208
+ [cellTemplate]="statusTpl"
209
+ [allowResizing]="true"
210
+ ></ax-text-column>
211
+ <ax-text-column
212
+ dataField="duration"
213
+ [caption]="'@system-insight:fields.duration' | translate | async"
214
+ [width]="'110px'"
215
+ [cellTemplate]="durationTpl"
216
+ [allowResizing]="true"
217
+ ></ax-text-column>
218
+ <ax-text-column
219
+ dataField="durationShare"
220
+ [caption]="'@system-insight:pages.trace-timeline.timeline' | translate | async"
221
+ [width]="'280px'"
222
+ [cellTemplate]="timelineTpl"
223
+ [allowResizing]="true"
224
+ ></ax-text-column>
225
+ <ax-text-column
226
+ dataField="spanId"
227
+ [caption]="'@system-insight:fields.span-id' | translate | async"
228
+ [width]="'180px'"
229
+ [allowResizing]="true"
230
+ ></ax-text-column>
231
+ </ax-data-table>
232
+ } @else {
233
+ <p class="text-sm text-muted p-2">
234
+ {{ '@system-insight:pages.trace-timeline.not-found' | translate | async }}
235
+ </p>
236
+ }
237
+ </div>
238
+
239
+ <ng-template #statusTpl let-item>
240
+ <ax-badge
241
+ [text]="item.data?.status || 'ok'"
242
+ [color]="item.data?.status === 'error' ? 'danger' : 'success'"
243
+ size="sm"
244
+ ></ax-badge>
245
+ </ng-template>
246
+
247
+ <ng-template #durationTpl let-item>{{ item.data?.duration ?? 0 }} ms</ng-template>
248
+
249
+ <ng-template #timelineTpl let-item>
250
+ <div class="flex w-full items-center gap-0 py-1">
251
+ <div class="shrink-0" [style.width.%]="item.data?.offsetPercent ?? 0"></div>
252
+ <div class="min-w-0" [style.width.%]="item.data?.widthPercent ?? 3">
253
+ <ax-progress-bar
254
+ [progress]="100"
255
+ [color]="item.data?.status === 'error' ? 'danger' : 'primary'"
256
+ [style.height.px]="10"
257
+ ></ax-progress-bar>
258
+ </div>
259
+ </div>
260
+ </ng-template>
261
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i2.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i3.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i3.AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXProgressBarModule }, { kind: "component", type: i5.AXProgressBarComponent, selector: "ax-progress-bar", inputs: ["color", "mode", "progress", "height"], outputs: ["ValueChange", "sizeChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i6.AsyncPipe, name: "async" }, { kind: "pipe", type: i7.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
262
+ }
263
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTraceTimelinePageComponent, decorators: [{
264
+ type: Component,
265
+ args: [{
266
+ selector: 'axm-trace-timeline-page',
267
+ standalone: true,
268
+ imports: [
269
+ CommonModule,
270
+ AXBadgeModule,
271
+ AXButtonModule,
272
+ AXDataTableModule,
273
+ AXLoadingModule,
274
+ AXProgressBarModule,
275
+ AXTranslationModule,
276
+ ],
277
+ template: `
278
+ <div class="axm-trace-timeline flex flex-col gap-4 p-4 w-full min-h-0">
279
+ @if (loading()) {
280
+ <div class="flex justify-center py-10">
281
+ <ax-loading />
282
+ </div>
283
+ } @else if (timeline(); as data) {
284
+ <div class="flex flex-wrap items-center gap-2">
285
+ <ax-badge [text]="spanCountBadge()" color="primary" size="sm"></ax-badge>
286
+ @for (service of data.services; track service) {
287
+ <ax-badge [text]="service" color="secondary" size="sm"></ax-badge>
288
+ }
289
+ <span class="text-sm text-muted ms-auto">
290
+ {{ '@system-insight:pages.trace-timeline.duration' | translate | async }}:
291
+ {{ data.duration }} ms
292
+ </span>
293
+ <ax-button
294
+ look="outline"
295
+ color="primary"
296
+ [text]="('@system-insight:pages.trace-timeline.view-logs' | translate | async) || ''"
297
+ (onClick)="goToRelatedLogs()"
298
+ ></ax-button>
299
+ </div>
300
+
301
+ <ax-data-table
302
+ class="w-full border border-lightest rounded-md overflow-hidden"
303
+ [paging]="false"
304
+ [dataSource]="dataSource()"
305
+ >
306
+ <ax-text-column
307
+ dataField="operationName"
308
+ [caption]="'@system-insight:fields.operation' | translate | async"
309
+ [width]="'200px'"
310
+ [allowResizing]="true"
311
+ ></ax-text-column>
312
+ <ax-text-column
313
+ dataField="serviceName"
314
+ [caption]="'@system-insight:fields.service-name' | translate | async"
315
+ [width]="'140px'"
316
+ [allowResizing]="true"
317
+ ></ax-text-column>
318
+ <ax-text-column
319
+ dataField="status"
320
+ [caption]="'@system-insight:fields.status' | translate | async"
321
+ [width]="'100px'"
322
+ [cellTemplate]="statusTpl"
323
+ [allowResizing]="true"
324
+ ></ax-text-column>
325
+ <ax-text-column
326
+ dataField="duration"
327
+ [caption]="'@system-insight:fields.duration' | translate | async"
328
+ [width]="'110px'"
329
+ [cellTemplate]="durationTpl"
330
+ [allowResizing]="true"
331
+ ></ax-text-column>
332
+ <ax-text-column
333
+ dataField="durationShare"
334
+ [caption]="'@system-insight:pages.trace-timeline.timeline' | translate | async"
335
+ [width]="'280px'"
336
+ [cellTemplate]="timelineTpl"
337
+ [allowResizing]="true"
338
+ ></ax-text-column>
339
+ <ax-text-column
340
+ dataField="spanId"
341
+ [caption]="'@system-insight:fields.span-id' | translate | async"
342
+ [width]="'180px'"
343
+ [allowResizing]="true"
344
+ ></ax-text-column>
345
+ </ax-data-table>
346
+ } @else {
347
+ <p class="text-sm text-muted p-2">
348
+ {{ '@system-insight:pages.trace-timeline.not-found' | translate | async }}
349
+ </p>
350
+ }
351
+ </div>
352
+
353
+ <ng-template #statusTpl let-item>
354
+ <ax-badge
355
+ [text]="item.data?.status || 'ok'"
356
+ [color]="item.data?.status === 'error' ? 'danger' : 'success'"
357
+ size="sm"
358
+ ></ax-badge>
359
+ </ng-template>
360
+
361
+ <ng-template #durationTpl let-item>{{ item.data?.duration ?? 0 }} ms</ng-template>
362
+
363
+ <ng-template #timelineTpl let-item>
364
+ <div class="flex w-full items-center gap-0 py-1">
365
+ <div class="shrink-0" [style.width.%]="item.data?.offsetPercent ?? 0"></div>
366
+ <div class="min-w-0" [style.width.%]="item.data?.widthPercent ?? 3">
367
+ <ax-progress-bar
368
+ [progress]="100"
369
+ [color]="item.data?.status === 'error' ? 'danger' : 'primary'"
370
+ [style.height.px]="10"
371
+ ></ax-progress-bar>
372
+ </div>
373
+ </div>
374
+ </ng-template>
375
+ `,
376
+ changeDetection: ChangeDetectionStrategy.OnPush,
377
+ providers: [
378
+ {
379
+ provide: AXPPageLayoutBase,
380
+ useExisting: AXMTraceTimelinePageComponent,
381
+ },
382
+ ],
383
+ host: { class: 'axm-trace-timeline-page block w-full min-h-0' },
384
+ }]
385
+ }], ctorParameters: () => [], propDecorators: { rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }] } });
386
+
387
+ var traceTimelinePage_component = /*#__PURE__*/Object.freeze({
388
+ __proto__: null,
389
+ AXMTraceTimelinePageComponent: AXMTraceTimelinePageComponent
390
+ });
391
+
392
+ //#region ---- Imports ----
393
+ //#endregion
394
+ //#region ---- Constants ----
395
+ const DEFAULT_NODE_W = 220;
396
+ const DEFAULT_NODE_H = 88;
397
+ //#endregion
398
+ //#region ---- Helpers ----
399
+ function spanTitle(span) {
400
+ if (span.middlewareName) {
401
+ return span.middlewareName;
402
+ }
403
+ return span.operationName;
404
+ }
405
+ function spanSubtitle(span) {
406
+ const parts = [];
407
+ if (span.spanKind) {
408
+ parts.push(span.spanKind);
409
+ }
410
+ if (span.duration != null) {
411
+ parts.push(`${span.duration} ms`);
412
+ }
413
+ if (span.nextMiddleware) {
414
+ parts.push(`→ ${span.nextMiddleware}`);
415
+ }
416
+ return parts.join(' · ');
417
+ }
418
+ function statusColors(status) {
419
+ if (status === 'error') {
420
+ return { color: 'danger', borderColor: 'danger' };
421
+ }
422
+ if (status === 'running') {
423
+ return { color: 'warning', borderColor: 'warning' };
424
+ }
425
+ return { borderColor: 'success' };
426
+ }
427
+ function sortSpans(spans) {
428
+ return [...spans].sort((a, b) => {
429
+ const aStart = Date.parse(a.startTime) || 0;
430
+ const bStart = Date.parse(b.startTime) || 0;
431
+ if (aStart !== bStart) {
432
+ return aStart - bStart;
433
+ }
434
+ return (a.order ?? 0) - (b.order ?? 0);
435
+ });
436
+ }
437
+ //#endregion
438
+ //#region ---- Builder ----
439
+ function buildTraceFlowDiagram(timeline) {
440
+ if (!timeline?.spans?.length) {
441
+ return null;
442
+ }
443
+ const spans = sortSpans(timeline.spans);
444
+ const nodes = spans.map((span, index) => {
445
+ const colors = statusColors(span.status);
446
+ return {
447
+ id: span.spanId,
448
+ type: span.spanKind ?? 'span',
449
+ title: spanTitle(span),
450
+ icon: span.spanKind === 'middleware'
451
+ ? 'fa-light fa-layer-group'
452
+ : span.spanKind === 'backend'
453
+ ? 'fa-light fa-database'
454
+ : 'fa-light fa-play',
455
+ shape: 'rounded-rectangle',
456
+ position: { x: index * 280, y: 40 },
457
+ size: { width: DEFAULT_NODE_W, height: DEFAULT_NODE_H },
458
+ inboundPorts: [{ id: `${span.spanId}:in`, kind: 'inbound', side: 'left', ratio: 0.5 }],
459
+ outboundPorts: [{ id: `${span.spanId}:out`, kind: 'outbound', side: 'right', ratio: 0.5 }],
460
+ readonly: true,
461
+ color: colors.color,
462
+ borderColor: colors.borderColor,
463
+ data: {
464
+ subtitle: spanSubtitle(span),
465
+ span,
466
+ },
467
+ };
468
+ });
469
+ const spanIds = new Set(spans.map((span) => span.spanId));
470
+ const connectors = spans
471
+ .filter((span) => span.parentSpanId && spanIds.has(span.parentSpanId))
472
+ .map((span) => ({
473
+ id: `${span.parentSpanId}->${span.spanId}`,
474
+ sourceNodeId: span.parentSpanId,
475
+ sourcePortId: `${span.parentSpanId}:out`,
476
+ targetNodeId: span.spanId,
477
+ targetPortId: `${span.spanId}:in`,
478
+ readonly: true,
479
+ borderColor: (span.status === 'error' ? 'danger' : 'primary'),
480
+ }));
481
+ // Fallback linear chain when parent links are missing
482
+ if (!connectors.length && nodes.length > 1) {
483
+ for (let i = 0; i < nodes.length - 1; i += 1) {
484
+ connectors.push({
485
+ id: `${nodes[i].id}->${nodes[i + 1].id}`,
486
+ sourceNodeId: nodes[i].id,
487
+ sourcePortId: `${nodes[i].id}:out`,
488
+ targetNodeId: nodes[i + 1].id,
489
+ targetPortId: `${nodes[i + 1].id}:in`,
490
+ readonly: true,
491
+ borderColor: 'primary',
492
+ });
493
+ }
494
+ }
495
+ return arrangeWorkflowDiagram({
496
+ nodes,
497
+ connectors,
498
+ data: {
499
+ traceId: timeline.traceId,
500
+ kind: timeline.kind,
501
+ operation: timeline.operation,
502
+ },
503
+ }, { direction: 'horizontal' });
504
+ }
505
+ //#endregion
506
+
507
+ //#region ---- Trace flow display normalization ----
508
+ /**
509
+ * Prepares a value for the Trace Flow detail viewer:
510
+ * - plain string → unchanged
511
+ * - JSON string → parsed object/array (recursively)
512
+ * - object/array → nested JSON strings expanded
513
+ */
514
+ function normalizeTraceDisplayValue(value, depth = 0) {
515
+ if (depth > 8) {
516
+ return value;
517
+ }
518
+ if (typeof value === 'string') {
519
+ return tryParseJsonString(value, depth);
520
+ }
521
+ if (Array.isArray(value)) {
522
+ return value.map((entry) => normalizeTraceDisplayValue(entry, depth + 1));
523
+ }
524
+ if (value && typeof value === 'object') {
525
+ const result = {};
526
+ for (const [key, entry] of Object.entries(value)) {
527
+ result[key] = normalizeTraceDisplayValue(entry, depth + 1);
528
+ }
529
+ return result;
530
+ }
531
+ return value;
532
+ }
533
+ function tryParseJsonString(value, depth) {
534
+ const trimmed = value.trim();
535
+ if (!trimmed) {
536
+ return value;
537
+ }
538
+ const looksLikeJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
539
+ (trimmed.startsWith('[') && trimmed.endsWith(']')) ||
540
+ ((trimmed.startsWith('"') && trimmed.endsWith('"')) && trimmed.length > 1);
541
+ if (!looksLikeJson) {
542
+ return value;
543
+ }
544
+ try {
545
+ const parsed = JSON.parse(trimmed);
546
+ return normalizeTraceDisplayValue(parsed, depth + 1);
547
+ }
548
+ catch {
549
+ return value;
550
+ }
551
+ }
552
+ //#endregion
553
+
554
+ //#region ---- Imports ----
555
+ //#endregion
556
+ //#region ---- Component ----
557
+ /**
558
+ * TraceEntry detail tab — readonly flow chart of spans (middleware chain),
559
+ * same hosting pattern as Timeline / workflow history Visual.
560
+ */
561
+ class AXMTraceFlowPageComponent extends AXPPageLayoutBaseComponent {
562
+ constructor() {
563
+ super(...arguments);
564
+ //#region ---- Services & Dependencies ----
565
+ this.queryExecutor = inject(AXPQueryExecutor);
566
+ this.translationService = inject(AXTranslationService);
567
+ //#endregion
568
+ //#region ---- Inputs ----
569
+ /** Entity record from TraceEntry detail host. */
570
+ this.rootContext = input(/* @ts-ignore */
571
+ ...(ngDevMode ? [undefined, { debugName: "rootContext" }] : /* istanbul ignore next */ []));
572
+ this.pageConfig = input(/* @ts-ignore */
573
+ ...(ngDevMode ? [undefined, { debugName: "pageConfig" }] : /* istanbul ignore next */ []));
574
+ //#endregion
575
+ //#region ---- State Signals ----
576
+ this.loading = signal(true, /* @ts-ignore */
577
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
578
+ this.timeline = signal(null, /* @ts-ignore */
579
+ ...(ngDevMode ? [{ debugName: "timeline" }] : /* istanbul ignore next */ []));
580
+ this.selectedSpan = signal(null, /* @ts-ignore */
581
+ ...(ngDevMode ? [{ debugName: "selectedSpan" }] : /* istanbul ignore next */ []));
582
+ this.diagram = computed(() => buildTraceFlowDiagram(this.timeline()), /* @ts-ignore */
583
+ ...(ngDevMode ? [{ debugName: "diagram" }] : /* istanbul ignore next */ []));
584
+ this.highlightedNodeIds = computed(() => {
585
+ const id = this.selectedSpan()?.spanId;
586
+ return id ? [id] : [];
587
+ }, /* @ts-ignore */
588
+ ...(ngDevMode ? [{ debugName: "highlightedNodeIds" }] : /* istanbul ignore next */ []));
589
+ this.traceId = computed(() => {
590
+ const ctx = this.rootContext() ?? {};
591
+ const fromTraceId = typeof ctx['traceId'] === 'string' ? ctx['traceId'].trim() : '';
592
+ if (fromTraceId) {
593
+ return fromTraceId;
594
+ }
595
+ const fromId = typeof ctx['id'] === 'string' ? ctx['id'].trim() : '';
596
+ return fromId;
597
+ }, /* @ts-ignore */
598
+ ...(ngDevMode ? [{ debugName: "traceId" }] : /* istanbul ignore next */ []));
599
+ //#endregion
600
+ //#region ---- Lifecycle ----
601
+ this.#loadEffect = effect(() => {
602
+ const id = this.traceId();
603
+ void this.loadTimeline(id);
604
+ }, /* @ts-ignore */
605
+ ...(ngDevMode ? [{ debugName: "#loadEffect" }] : /* istanbul ignore next */ []));
606
+ }
607
+ //#endregion
608
+ //#region ---- Lifecycle ----
609
+ #loadEffect;
610
+ //#endregion
611
+ //#region ---- Page metadata ----
612
+ async getPageTitle() {
613
+ return this.translationService.translateAsync('@system-insight:pages.trace-flow.title');
614
+ }
615
+ //#endregion
616
+ //#region ---- Event Handlers ----
617
+ onNodeClick(event) {
618
+ const nodeId = event.node.id;
619
+ const fromData = event.node.data?.['span'];
620
+ const fromTimeline = this.timeline()?.spans?.find((span) => span.spanId === nodeId || span.id === nodeId);
621
+ this.selectedSpan.set(fromData ?? fromTimeline ?? null);
622
+ this.recompute();
623
+ }
624
+ hasValue(value) {
625
+ if (value === undefined || value === null) {
626
+ return false;
627
+ }
628
+ if (typeof value === 'string') {
629
+ return value.trim().length > 0;
630
+ }
631
+ if (Array.isArray(value)) {
632
+ return value.length > 0;
633
+ }
634
+ if (typeof value === 'object') {
635
+ return Object.keys(value).length > 0;
636
+ }
637
+ return true;
638
+ }
639
+ /**
640
+ * Display rules:
641
+ * - plain string → as-is
642
+ * - JSON string → parse, then pretty JSON
643
+ * - object/array → pretty JSON (nested JSON strings also expanded)
644
+ */
645
+ formatJson(value) {
646
+ const normalized = normalizeTraceDisplayValue(value);
647
+ if (typeof normalized === 'string') {
648
+ return normalized;
649
+ }
650
+ try {
651
+ return JSON.stringify(normalized, null, 2);
652
+ }
653
+ catch {
654
+ return String(value);
655
+ }
656
+ }
657
+ //#endregion
658
+ //#region ---- Data Loading ----
659
+ async loadTimeline(traceId) {
660
+ if (!traceId) {
661
+ this.timeline.set(null);
662
+ this.selectedSpan.set(null);
663
+ this.loading.set(false);
664
+ return;
665
+ }
666
+ this.loading.set(true);
667
+ try {
668
+ const result = await this.queryExecutor.fetch('SystemInsight:TraceTimeline:Get', traceId);
669
+ const timeline = runtimeQueryPayloadData(result) ?? null;
670
+ this.timeline.set(timeline);
671
+ this.selectedSpan.set(timeline?.spans?.[0] ?? null);
672
+ this.recompute();
673
+ }
674
+ finally {
675
+ this.loading.set(false);
676
+ }
677
+ }
678
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTraceFlowPageComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
679
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTraceFlowPageComponent, isStandalone: true, selector: "axm-trace-flow-page", inputs: { rootContext: { classPropertyName: "rootContext", publicName: "rootContext", isSignal: true, isRequired: false, transformFunction: null }, pageConfig: { classPropertyName: "pageConfig", publicName: "pageConfig", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "axm-trace-flow-page block w-full min-h-0" }, providers: [
680
+ {
681
+ provide: AXPPageLayoutBase,
682
+ useExisting: AXMTraceFlowPageComponent,
683
+ },
684
+ ], usesInheritance: true, ngImport: i0, template: "<!-- ----- Trace flow chart page ----- -->\n<div class=\"axm-trace-flow flex flex-col gap-4 p-4 w-full min-h-0 h-full\">\n @if (loading()) {\n <!-- ----- Loading indicator while timeline is fetched ----- -->\n <div class=\"flex justify-center py-10\">\n <ax-loading />\n </div>\n }\n <!-- ----- Empty state when timeline is missing ----- -->\n @else if (!timeline()) {\n <p class=\"typo-caption text-muted\">\n {{ '@system-insight:pages.trace-flow.not-found' | translate | async }}\n </p>\n }\n <!-- ----- Flow chart + span details ----- -->\n @else {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (timeline()?.kind; as kind) {\n <ax-badge [text]=\"kind\" color=\"primary\" size=\"sm\"></ax-badge>\n }\n @if (timeline()?.operation; as operation) {\n <span class=\"text-sm\">{{ operation }}</span>\n }\n <span class=\"text-sm text-muted ms-auto\">\n {{ '@system-insight:pages.trace-flow.duration' | translate | async }}:\n {{ timeline()?.duration }} ms\n </span>\n </div>\n\n <div class=\"axm-trace-flow__canvas border border-lightest rounded-md overflow-hidden min-h-[360px] flex-1\">\n @if (diagram(); as model) {\n <!-- ----- Readonly span flow chart ----- -->\n <axp-workflow-designer\n [model]=\"model\"\n [readonly]=\"true\"\n [showPortLabels]=\"false\"\n [highlightedNodeIds]=\"highlightedNodeIds()\"\n (nodeClick)=\"onNodeClick($event)\"\n />\n }\n <!-- ----- Empty diagram fallback ----- -->\n @else {\n <p class=\"typo-caption text-muted p-4\">\n {{ '@system-insight:pages.trace-flow.empty' | translate | async }}\n </p>\n }\n </div>\n\n @if (selectedSpan(); as span) {\n <!-- ----- Selected span details (metadata + IO) ----- -->\n <div class=\"border border-lightest rounded-md p-4 flex flex-col gap-4\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-base\">{{ span.operationName }}</strong>\n @if (span.spanKind) {\n <ax-badge [text]=\"span.spanKind\" color=\"primary\" size=\"sm\"></ax-badge>\n }\n @if (span.middlewareName) {\n <ax-badge [text]=\"span.middlewareName\" color=\"secondary\" size=\"sm\"></ax-badge>\n }\n @if (span.status) {\n <ax-badge\n [text]=\"span.status\"\n [color]=\"span.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n </div>\n\n <!-- ----- Span metadata grid ----- -->\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 text-sm\">\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.span-id' | translate | async }}\n </div>\n <div class=\"font-mono break-all\">{{ span.spanId }}</div>\n </div>\n @if (span.parentSpanId) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.parent-span-id' | translate | async }}\n </div>\n <div class=\"font-mono break-all\">{{ span.parentSpanId }}</div>\n </div>\n }\n @if (span.serviceName) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:fields.service-name' | translate | async }}\n </div>\n <div>{{ span.serviceName }}</div>\n </div>\n }\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.duration' | translate | async }}\n </div>\n <div>{{ span.duration ?? 0 }} ms</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:fields.start-time' | translate | async }}\n </div>\n <div>{{ span.startTime }}</div>\n </div>\n @if (span.order != null) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:fields.order' | translate | async }}\n </div>\n <div>{{ span.order }}</div>\n </div>\n }\n @if (span.nextMiddleware) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.next-middleware' | translate | async }}\n </div>\n <div>{{ span.nextMiddleware }}</div>\n </div>\n }\n </div>\n\n @if (span.error) {\n <!-- ----- Span error ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1 text-danger\">\n {{ '@system-insight:fields.error' | translate | async }}\n </div>\n <pre class=\"text-sm whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-40\">{{\n span.error\n }}</pre>\n </div>\n }\n\n <!-- ----- Input snapshot (always visible) ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.input' | translate | async }}\n </div>\n @if (hasValue(span.input)) {\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.input)\n }}</pre>\n }\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@system-insight:pages.trace-flow.no-input' | translate | async }}\n </p>\n }\n </div>\n\n <!-- ----- Output snapshot (always visible) ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.output' | translate | async }}\n </div>\n @if (hasValue(span.output)) {\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.output)\n }}</pre>\n }\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@system-insight:pages.trace-flow.no-output' | translate | async }}\n </p>\n }\n </div>\n\n @if (hasValue(span.tags)) {\n <!-- ----- Span tags / attributes ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.attributes' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.tags)\n }}</pre>\n </div>\n }\n\n @if (span.logs?.length) {\n <!-- ----- Span logs / events ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.events' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.logs)\n }}</pre>\n </div>\n }\n </div>\n }\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPWorkflowDesignerComponent, selector: "axp-workflow-designer", inputs: ["model", "readonly", "highlightedNodeIds", "gridSize", "snapToGrid", "connectionPreviewStyle", "backgroundPattern", "canConnect", "paletteItems", "showPortLabels", "nodeBodyTemplate"], outputs: ["modelChange", "zoomChange", "designerPointerDown", "designerClick", "designerDoubleClick", "nodePointerDown", "nodeClick", "nodeDoubleClick", "nodeContextMenu", "nodeDragStart", "nodeDragEnd", "portPointerDown", "linkDragStart", "linkDragMove", "linkDragEnd", "connectorClick", "connectorDoubleClick", "connectionCreated", "connectionRejected", "activationChange"] }, { kind: "pipe", type: i6.AsyncPipe, name: "async" }, { kind: "pipe", type: i7.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
685
+ }
686
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTraceFlowPageComponent, decorators: [{
687
+ type: Component,
688
+ args: [{ selector: 'axm-trace-flow-page', standalone: true, imports: [
689
+ CommonModule,
690
+ AXBadgeModule,
691
+ AXLoadingModule,
692
+ AXTranslationModule,
693
+ AXPWorkflowDesignerComponent,
694
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
695
+ {
696
+ provide: AXPPageLayoutBase,
697
+ useExisting: AXMTraceFlowPageComponent,
698
+ },
699
+ ], host: { class: 'axm-trace-flow-page block w-full min-h-0' }, template: "<!-- ----- Trace flow chart page ----- -->\n<div class=\"axm-trace-flow flex flex-col gap-4 p-4 w-full min-h-0 h-full\">\n @if (loading()) {\n <!-- ----- Loading indicator while timeline is fetched ----- -->\n <div class=\"flex justify-center py-10\">\n <ax-loading />\n </div>\n }\n <!-- ----- Empty state when timeline is missing ----- -->\n @else if (!timeline()) {\n <p class=\"typo-caption text-muted\">\n {{ '@system-insight:pages.trace-flow.not-found' | translate | async }}\n </p>\n }\n <!-- ----- Flow chart + span details ----- -->\n @else {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (timeline()?.kind; as kind) {\n <ax-badge [text]=\"kind\" color=\"primary\" size=\"sm\"></ax-badge>\n }\n @if (timeline()?.operation; as operation) {\n <span class=\"text-sm\">{{ operation }}</span>\n }\n <span class=\"text-sm text-muted ms-auto\">\n {{ '@system-insight:pages.trace-flow.duration' | translate | async }}:\n {{ timeline()?.duration }} ms\n </span>\n </div>\n\n <div class=\"axm-trace-flow__canvas border border-lightest rounded-md overflow-hidden min-h-[360px] flex-1\">\n @if (diagram(); as model) {\n <!-- ----- Readonly span flow chart ----- -->\n <axp-workflow-designer\n [model]=\"model\"\n [readonly]=\"true\"\n [showPortLabels]=\"false\"\n [highlightedNodeIds]=\"highlightedNodeIds()\"\n (nodeClick)=\"onNodeClick($event)\"\n />\n }\n <!-- ----- Empty diagram fallback ----- -->\n @else {\n <p class=\"typo-caption text-muted p-4\">\n {{ '@system-insight:pages.trace-flow.empty' | translate | async }}\n </p>\n }\n </div>\n\n @if (selectedSpan(); as span) {\n <!-- ----- Selected span details (metadata + IO) ----- -->\n <div class=\"border border-lightest rounded-md p-4 flex flex-col gap-4\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-base\">{{ span.operationName }}</strong>\n @if (span.spanKind) {\n <ax-badge [text]=\"span.spanKind\" color=\"primary\" size=\"sm\"></ax-badge>\n }\n @if (span.middlewareName) {\n <ax-badge [text]=\"span.middlewareName\" color=\"secondary\" size=\"sm\"></ax-badge>\n }\n @if (span.status) {\n <ax-badge\n [text]=\"span.status\"\n [color]=\"span.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n </div>\n\n <!-- ----- Span metadata grid ----- -->\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 text-sm\">\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.span-id' | translate | async }}\n </div>\n <div class=\"font-mono break-all\">{{ span.spanId }}</div>\n </div>\n @if (span.parentSpanId) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.parent-span-id' | translate | async }}\n </div>\n <div class=\"font-mono break-all\">{{ span.parentSpanId }}</div>\n </div>\n }\n @if (span.serviceName) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:fields.service-name' | translate | async }}\n </div>\n <div>{{ span.serviceName }}</div>\n </div>\n }\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.duration' | translate | async }}\n </div>\n <div>{{ span.duration ?? 0 }} ms</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:fields.start-time' | translate | async }}\n </div>\n <div>{{ span.startTime }}</div>\n </div>\n @if (span.order != null) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:fields.order' | translate | async }}\n </div>\n <div>{{ span.order }}</div>\n </div>\n }\n @if (span.nextMiddleware) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@system-insight:pages.trace-flow.next-middleware' | translate | async }}\n </div>\n <div>{{ span.nextMiddleware }}</div>\n </div>\n }\n </div>\n\n @if (span.error) {\n <!-- ----- Span error ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1 text-danger\">\n {{ '@system-insight:fields.error' | translate | async }}\n </div>\n <pre class=\"text-sm whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-40\">{{\n span.error\n }}</pre>\n </div>\n }\n\n <!-- ----- Input snapshot (always visible) ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.input' | translate | async }}\n </div>\n @if (hasValue(span.input)) {\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.input)\n }}</pre>\n }\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@system-insight:pages.trace-flow.no-input' | translate | async }}\n </p>\n }\n </div>\n\n <!-- ----- Output snapshot (always visible) ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.output' | translate | async }}\n </div>\n @if (hasValue(span.output)) {\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.output)\n }}</pre>\n }\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@system-insight:pages.trace-flow.no-output' | translate | async }}\n </p>\n }\n </div>\n\n @if (hasValue(span.tags)) {\n <!-- ----- Span tags / attributes ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.attributes' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.tags)\n }}</pre>\n </div>\n }\n\n @if (span.logs?.length) {\n <!-- ----- Span logs / events ----- -->\n <div>\n <div class=\"text-sm font-medium mb-1\">\n {{ '@system-insight:pages.trace-flow.events' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(span.logs)\n }}</pre>\n </div>\n }\n </div>\n }\n }\n</div>\n" }]
700
+ }], propDecorators: { rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }] } });
701
+
702
+ var traceFlowPage_component = /*#__PURE__*/Object.freeze({
703
+ __proto__: null,
704
+ AXMTraceFlowPageComponent: AXMTraceFlowPageComponent
705
+ });
706
+
707
+ export { AXMTraceFlowPageComponent, AXMTraceTimelinePageComponent, AXMTraceTimelinePageComponentProvider, buildTraceFlowDiagram, normalizeTraceDisplayValue };
708
+ //# sourceMappingURL=acorex-platform-module-system-insight-client-index-D-Sy2f4K.mjs.map