@juice-js/observability 1.0.0-alpha.6 → 1.0.0-alpha.8
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/README.md +3 -0
- package/fesm2022/{juice-js-observability-alert.page-BHKAUvR-.mjs → juice-js-observability-alert.page-D4_SWIUp.mjs} +2 -2
- package/fesm2022/{juice-js-observability-alert.page-BHKAUvR-.mjs.map → juice-js-observability-alert.page-D4_SWIUp.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-alerts.page-C2tXzLQl.mjs → juice-js-observability-alerts.page-JKYZvFNx.mjs} +2 -2
- package/fesm2022/{juice-js-observability-alerts.page-C2tXzLQl.mjs.map → juice-js-observability-alerts.page-JKYZvFNx.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-logs.page--j6W1Ej-.mjs → juice-js-observability-logs.page-CDxYz7V0.mjs} +2 -2
- package/fesm2022/{juice-js-observability-logs.page--j6W1Ej-.mjs.map → juice-js-observability-logs.page-CDxYz7V0.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-metrics.page-DR6qZtfl.mjs → juice-js-observability-metrics.page-B9bNEGFX.mjs} +2 -2
- package/fesm2022/{juice-js-observability-metrics.page-DR6qZtfl.mjs.map → juice-js-observability-metrics.page-B9bNEGFX.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-overview.page-DU0GxSXQ.mjs → juice-js-observability-overview.page-DRsqxm7f.mjs} +2 -2
- package/fesm2022/{juice-js-observability-overview.page-DU0GxSXQ.mjs.map → juice-js-observability-overview.page-DRsqxm7f.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-resource.page-D6qpvo_f.mjs → juice-js-observability-resource.page-BwnjrE7Z.mjs} +2 -2
- package/fesm2022/{juice-js-observability-resource.page-D6qpvo_f.mjs.map → juice-js-observability-resource.page-BwnjrE7Z.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-resources.page-CR7-DFsh.mjs → juice-js-observability-resources.page-BoEADhVE.mjs} +2 -2
- package/fesm2022/{juice-js-observability-resources.page-CR7-DFsh.mjs.map → juice-js-observability-resources.page-BoEADhVE.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-rules.page-fq-wO5I_.mjs → juice-js-observability-rules.page-BGdNA_XR.mjs} +2 -2
- package/fesm2022/{juice-js-observability-rules.page-fq-wO5I_.mjs.map → juice-js-observability-rules.page-BGdNA_XR.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-service.page-Qc6FYChB.mjs → juice-js-observability-service.page-JstbjLbF.mjs} +2 -2
- package/fesm2022/{juice-js-observability-service.page-Qc6FYChB.mjs.map → juice-js-observability-service.page-JstbjLbF.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-services.page-CX6S7sUu.mjs → juice-js-observability-services.page-RdDusSXl.mjs} +2 -2
- package/fesm2022/{juice-js-observability-services.page-CX6S7sUu.mjs.map → juice-js-observability-services.page-RdDusSXl.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-telemetry-D9EYxkdr.mjs → juice-js-observability-telemetry-gYN3UogH.mjs} +6 -3
- package/fesm2022/juice-js-observability-telemetry-gYN3UogH.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-topology.page-CLz7J5pq.mjs → juice-js-observability-topology.page-CChcM0n-.mjs} +2 -2
- package/fesm2022/{juice-js-observability-topology.page-CLz7J5pq.mjs.map → juice-js-observability-topology.page-CChcM0n-.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-trace.page-DccvH1SV.mjs → juice-js-observability-trace.page-DE84DHal.mjs} +13 -3
- package/fesm2022/juice-js-observability-trace.page-DE84DHal.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-traces.page-B1vYmz_K.mjs → juice-js-observability-traces.page-C-i5ZJqJ.mjs} +2 -2
- package/fesm2022/{juice-js-observability-traces.page-B1vYmz_K.mjs.map → juice-js-observability-traces.page-C-i5ZJqJ.mjs.map} +1 -1
- package/fesm2022/juice-js-observability.mjs +25 -16
- package/fesm2022/juice-js-observability.mjs.map +1 -1
- package/package.json +1 -1
- package/types/juice-js-observability.d.ts +13 -1
- package/fesm2022/juice-js-observability-telemetry-D9EYxkdr.mjs.map +0 -1
- package/fesm2022/juice-js-observability-trace.page-DccvH1SV.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiService, ContextService, LinkPipe } from "./juice-js-observability.mjs";
|
|
2
|
-
import { _ as ReasonPipe, a as problemText, l as ContextBar, m as AgoPipe, s as screenPages, u as HealthBadge, x as apiResource } from "./juice-js-observability-telemetry-
|
|
2
|
+
import { _ as ReasonPipe, a as problemText, l as ContextBar, m as AgoPipe, s as screenPages, u as HealthBadge, x as apiResource } from "./juice-js-observability-telemetry-gYN3UogH.mjs";
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from "@angular/core";
|
|
5
5
|
import { forkJoin, map } from "rxjs";
|
|
@@ -248,4 +248,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
248
248
|
});
|
|
249
249
|
export { ServicesPage };
|
|
250
250
|
|
|
251
|
-
//# sourceMappingURL=juice-js-observability-services.page-
|
|
251
|
+
//# sourceMappingURL=juice-js-observability-services.page-RdDusSXl.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"juice-js-observability-services.page-
|
|
1
|
+
{"version":3,"file":"juice-js-observability-services.page-RdDusSXl.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/services.page.ts"],"sourcesContent":["import { LinkPipe } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { RouterLink } from '@angular/router';\nimport { SearchablePage } from '@juice-js/layout';\nimport { forkJoin, map } from 'rxjs';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Health, HealthState } from '../core/models';\nimport { AgoPipe, ReasonPipe } from '../shared/format';\nimport { ContextBar, HealthBadge } from '../shared/ui';\nimport { problemText } from '../shared/telemetry';\nimport { screenPages } from '../shared/paging';\n\nconst SEVERITY: HealthState[] = ['Critical', 'Unhealthy', 'Degraded', 'Offline', 'Unknown', 'Healthy'];\n\n/**\n * The service catalog (Console requirements §8): one row per service, environment-independent, with its health in\n * each environment. The toolbar search box filters it.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, MatPaginatorModule, MatProgressBarModule, ContextBar, HealthBadge, AgoPipe],\n template: `\n <jc-context-bar><h1>Services</h1>\n @if (state()) { <span class=\"jc-chip\">{{ state() }} <a [routerLink]=\"['services'] | jcLink\" [queryParams]=\"ctx.keep()\">×</a></span> }\n </jc-context-bar>\n @if (data.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (data.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n @if (capped()) { <p class=\"jc-notice\">Showing the first 5,000 only. Narrow the list with the search box or a filter.</p> }\n <table class=\"jc-table\">\n <thead><tr><th>Service</th><th>Health by environment</th><th>Why</th><th>Last seen</th></tr></thead>\n <tbody>\n @for (s of pages.visible(); track s.id) {\n <tr>\n <td><a [routerLink]=\"['services', s.id] | jcLink\" [queryParams]=\"ctx.keep()\">{{ s.key }}</a></td>\n <td class=\"jc-envs\">\n @for (h of s.health; track h.environment) {\n <span><span class=\"jc-muted jc-small\">{{ h.environment ?? '—' }}</span> <jc-health [state]=\"h.state\" [stale]=\"h.stale\" /></span>\n } @empty { <jc-health [state]=\"null\" /> }\n </td>\n <td class=\"jc-small\">{{ s.worst?.reasons?.slice(0, 2)?.map(reason)?.join(' · ') }}</td>\n <td>{{ s.lastSeen | ago }}</td>\n </tr>\n } @empty { <tr><td colspan=\"4\" class=\"jc-muted\">No services{{ filter() ? ' match \"' + filter() + '\"' : '' }}.</td></tr> }\n </tbody>\n </table>\n @if (pages.needed()) {\n <mat-paginator [length]=\"rows().length\" [pageSize]=\"pages.size()\" [pageIndex]=\"pages.index()\" [pageSizeOptions]=\"pages.sizes\"\n (page)=\"pages.change($event)\" aria-label=\"Pages\" />\n }`,\n})\nexport class ServicesPage implements SearchablePage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n readonly state = input<string>();\n protected readonly filter = signal('');\n private readonly reasonPipe = new ReasonPipe();\n protected readonly reason = (r: string) => this.reasonPipe.transform(r);\n\n protected readonly data = apiResource({\n stream: () => forkJoin({\n services: this.api.resources('service'),\n health: this.api.health({ subjectType: 'service' }),\n }).pipe(map(({ services, health }) => ({\n services: services.items, health: health.items, capped: services.nextCursor !== null || health.nextCursor !== null,\n }))),\n });\n protected readonly capped = computed(() => this.data.value()?.capped ?? false);\n protected readonly error = computed(() => problemText(this.data.error()));\n\n protected readonly rows = computed(() => {\n const data = this.data.value();\n if (!data) return [];\n const env = this.ctx.environment();\n const byService = new Map<string, Health[]>();\n for (const h of data.health) byService.set(h.subjectId, [...(byService.get(h.subjectId) ?? []), h]);\n const q = this.filter().toLowerCase();\n return data.services\n .map(s => {\n const health = (byService.get(s.id) ?? []).filter(h => !env || h.environment === env);\n const worst = [...health].sort((a, b) => SEVERITY.indexOf(a.state) - SEVERITY.indexOf(b.state))[0];\n return { ...s, health, worst };\n })\n // With an environment chosen, only the services seen in it.\n .filter(s => !env || s.health.length > 0)\n .filter(s => !q || s.key.toLowerCase().includes(q))\n .filter(s => !this.state() || s.health.some(h => h.state === this.state()))\n .sort((a, b) => SEVERITY.indexOf(a.worst?.state ?? 'Unknown') - SEVERITY.indexOf(b.worst?.state ?? 'Unknown') || a.key.localeCompare(b.key));\n });\n\n protected readonly pages = screenPages(this.rows);\n\n onSearch(query: string) { this.filter.set(query); }\n}\n"],"mappings":";;;;;;;;;;AAgBA,MAAM,WAA0B;CAAC;CAAY;CAAa;CAAY;CAAW;CAAW;AAAS;AAsCrG,IAAa,eAAb,MAAa,aAAY;CACJ,MAAM,OAAO,cAAc;CAC7B,MAAM,OAAO,UAAU;CAC/B,QAAQ,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CACb,SAAS,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CACrB,aAAa,IAAI,WAAU;CACzB,UAAU,MAAc,KAAK,WAAW,UAAU,CAAC;CAEnD,OAAO,YAAY,EACpC,cAAc,SAAS;EACrB,UAAU,KAAK,IAAI,UAAU,SAAS;EACtC,QAAQ,KAAK,IAAI,OAAO,EAAE,aAAa,UAAS,CAAE;CACnD,CAAA,CAAC,CAAC,KAAK,KAAK,EAAE,UAAU,cAAc;EACrC,UAAU,SAAS;EAAO,QAAQ,OAAO;EAAO,QAAQ,SAAS,eAAe,QAAQ,OAAO,eAAe;CAC/G,EAAC,CAAC,EACJ,CAAA;CACkB,SAAS,eAAe,KAAK,KAAK,MAAK,CAAE,EAAE,UAAU,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAC3D,QAAQ,eAAe,YAAY,KAAK,KAAK,MAAK,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEtD,OAAO,eAAc;EACtC,MAAM,OAAO,KAAK,KAAK,MAAK;EAC5B,IAAI,CAAC,MAAM,OAAO,CAAA;EAClB,MAAM,MAAM,KAAK,IAAI,YAAW;EAChC,MAAM,4BAAY,IAAI,IAAG;EACzB,KAAK,MAAM,KAAK,KAAK,QAAQ,UAAU,IAAI,EAAE,WAAW,CAAC,GAAI,UAAU,IAAI,EAAE,SAAS,KAAK,CAAA,GAAK,CAAC,CAAC;EAClG,MAAM,IAAI,KAAK,OAAM,CAAE,CAAC,YAAW;EACnC,OAAO,KAAK,SACT,KAAI,MAAI;GACP,MAAM,UAAU,UAAU,IAAI,EAAE,EAAE,KAAK,CAAA,EAAA,CAAI,QAAO,MAAK,CAAC,OAAO,EAAE,gBAAgB,GAAG;GACpF,MAAM,QAAQ,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,SAAS,QAAQ,EAAE,KAAK,IAAI,SAAS,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;GAChG,OAAO;IAAE,GAAG;IAAG;IAAQ;GAAK;EAC9B,CAAC,CAAC,CAED,QAAO,MAAK,CAAC,OAAO,EAAE,OAAO,SAAS,CAAC,CAAC,CACxC,QAAO,MAAK,CAAC,KAAK,EAAE,IAAI,YAAW,CAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAClD,QAAO,MAAK,CAAC,KAAK,MAAK,KAAM,EAAE,OAAO,MAAK,MAAK,EAAE,UAAU,KAAK,MAAK,CAAE,CAAC,CAAC,CAC1E,MAAM,GAAG,MAAM,SAAS,QAAQ,EAAE,OAAO,SAAS,SAAS,IAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,SAAS,KAAK,EAAE,IAAI,cAAc,EAAE,GAAG,CAAC;CAC/I,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,QAAQ,YAAY,KAAK,IAAI;CAEhD,SAAS,OAAa;EAAI,KAAK,OAAO,IAAI,KAAK;CAAG;CAzCvC,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAY,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,OAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UA7Bb;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BN,UAAA;EAAA,cAAA;GAAA;IAAA,MAAA;IAAA,MA5BgB;IAAU,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;GAAkB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,MAAA;IAAA,UAAA,CAAA,cAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;GAAoB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,cAAA;IAAA,UAAA,CAAA,gBAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAU,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAW,UAAA;IAAA,QAAA,CAAA,SAAA,OAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAvF;IAAQ,MAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAiF;IAAO,MAAA;GAAA;EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AA8B/F,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAY,YAAA,CAAA;EAhCxB,MAAA;EAAU,MAAA,CAAA;GACT,iBAAiB,wBAAwB;GACzC,SAAS;IAAC;IAAU;IAAY;IAAoB;IAAsB;IAAY;IAAa;GAAO;GAC1G,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BX,CAAA"}
|
|
@@ -961,6 +961,9 @@ var TraceTable = class TraceTable {
|
|
|
961
961
|
}), (q) => this.api.traces(q));
|
|
962
962
|
rows = this.traces.items;
|
|
963
963
|
longest = computed(() => Math.max(1, ...this.rows().map((t) => t.durationMs)), ...ngDevMode ? [{ debugName: "longest" }] : /* istanbul ignore next */ []);
|
|
964
|
+
barWidth(ms) {
|
|
965
|
+
return Math.max(2, 80 * ms / this.longest());
|
|
966
|
+
}
|
|
964
967
|
error = computed(() => problemText(this.traces.first.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
|
|
965
968
|
moreError = computed(() => problemText(this.traces.moreError()), ...ngDevMode ? [{ debugName: "moreError" }] : /* istanbul ignore next */ []);
|
|
966
969
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
@@ -1008,7 +1011,7 @@ var TraceTable = class TraceTable {
|
|
|
1008
1011
|
<td class="jc-nowrap">{{ t.startedAt | date: 'MMM d, HH:mm:ss.SSS' }}</td>
|
|
1009
1012
|
<td><a [routerLink]="['traces', t.traceId] | jcLink" [queryParams]="ctx.keep()">{{ t.rootService }} · {{ t.rootOperation }}</a>
|
|
1010
1013
|
@if (t.services.length > 1) { <div class="jc-muted jc-small">{{ t.services.join(' → ') }}</div> }</td>
|
|
1011
|
-
<td class="jc-nowrap"><span class="jc-bar" [style.width
|
|
1014
|
+
<td class="jc-nowrap"><span class="jc-bar" [style.width.px]="barWidth(t.durationMs)"></span>{{ t.durationMs | duration }}</td>
|
|
1012
1015
|
<td>{{ t.spanCount }}</td>
|
|
1013
1016
|
<td>@if (t.errorCount > 0) { <span class="jc-badge jc-health-unhealthy">{{ t.errorCount }} error{{ t.errorCount > 1 ? 's' : '' }}</span> }
|
|
1014
1017
|
@else { <span class="jc-badge jc-health-healthy">ok</span> }</td>
|
|
@@ -1126,7 +1129,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1126
1129
|
<td class="jc-nowrap">{{ t.startedAt | date: 'MMM d, HH:mm:ss.SSS' }}</td>
|
|
1127
1130
|
<td><a [routerLink]="['traces', t.traceId] | jcLink" [queryParams]="ctx.keep()">{{ t.rootService }} · {{ t.rootOperation }}</a>
|
|
1128
1131
|
@if (t.services.length > 1) { <div class="jc-muted jc-small">{{ t.services.join(' → ') }}</div> }</td>
|
|
1129
|
-
<td class="jc-nowrap"><span class="jc-bar" [style.width
|
|
1132
|
+
<td class="jc-nowrap"><span class="jc-bar" [style.width.px]="barWidth(t.durationMs)"></span>{{ t.durationMs | duration }}</td>
|
|
1130
1133
|
<td>{{ t.spanCount }}</td>
|
|
1131
1134
|
<td>@if (t.errorCount > 0) { <span class="jc-badge jc-health-unhealthy">{{ t.errorCount }} error{{ t.errorCount > 1 ? 's' : '' }}</span> }
|
|
1132
1135
|
@else { <span class="jc-badge jc-health-healthy">ok</span> }</td>
|
|
@@ -1780,4 +1783,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1780
1783
|
});
|
|
1781
1784
|
export { ReasonPipe as _, problemText as a, preciseTime as b, ConfidenceBadge as c, KeyValueTable as d, onThemeChange as f, PercentPipe as g, DurationPipe as h, TraceTable as i, ContextBar as l, AgoPipe as m, MetricExplorer as n, cursorList as o, themeColor as p, ServiceStatsCharts as r, screenPages as s, LogTable as t, HealthBadge as u, formatDuration as v, apiResource as x, plainTraceId as y };
|
|
1782
1785
|
|
|
1783
|
-
//# sourceMappingURL=juice-js-observability-telemetry-
|
|
1786
|
+
//# sourceMappingURL=juice-js-observability-telemetry-gYN3UogH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juice-js-observability-telemetry-gYN3UogH.mjs","names":["i1","i2","i3"],"sources":["../../../../projects/juice-js/observability/src/lib/shared/resource.ts","../../../../projects/juice-js/observability/src/lib/shared/format.ts","../../../../projects/juice-js/observability/src/lib/shared/theme.ts","../../../../projects/juice-js/observability/src/lib/shared/ui.ts","../../../../projects/juice-js/observability/src/lib/shared/paging.ts","../../../../projects/juice-js/observability/src/lib/shared/telemetry.ts"],"sourcesContent":["import { ResourceRef, Signal, computed, isSignal } from '@angular/core';\nimport { RxResourceOptions, rxResource } from '@angular/core/rxjs-interop';\n\n/** A resource whose `value()` is `undefined` while its request failed, instead of throwing. */\nexport type ApiResource<T> = Omit<ResourceRef<T | undefined>, 'value'> & { readonly value: Signal<T | undefined> };\n\n/**\n * An `rxResource` for an API read. Angular's `value()` throws while the resource is in error, so a failed request (a\n * trace not found, a 500, the API unreachable) would break the page's rendering, and with it every overlay such as a\n * select's options. Here `value()` reads `undefined` then, and `error()` says why.\n */\nexport function apiResource<T, P>(options: RxResourceOptions<T, P>): ApiResource<T> {\n const ref = rxResource<T, P>(options);\n const value = computed(() => (ref.hasValue() ? ref.value() : undefined));\n return new Proxy(ref, {\n get(target, prop) {\n if (prop === 'value') return value;\n const member = Reflect.get(target, prop, target);\n return typeof member === 'function' && !isSignal(member) ? member.bind(target) : member;\n },\n }) as unknown as ApiResource<T>;\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n/** Milliseconds since the epoch of an ISO 8601 time, keeping the sub-millisecond digits that Date.parse drops. */\nexport function preciseTime(iso: string): number {\n const fraction = /\\.\\d{3}(\\d+)/.exec(iso)?.[1];\n return Date.parse(iso) + (fraction ? Number(`0.${fraction}`) : 0);\n}\n\n/** The 32-hex trace id of a W3C traceparent (\"00-<trace id>-<span id>-01\"); any other text unchanged but trimmed and lower-cased. */\nexport function plainTraceId(text: string): string {\n const lower = text.trim().toLowerCase();\n return /^[0-9a-f]{2}-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/.exec(lower)?.[1] ?? lower;\n}\n\n/** 0.8 ms, 120 ms, 1.42 s, 2 m 05 s. */\nexport function formatDuration(ms: number | null | undefined): string {\n if (ms === null || ms === undefined || Number.isNaN(ms)) return '—';\n if (ms < 1) return `${ms.toFixed(2)} ms`;\n if (ms < 1000) return `${Math.round(ms * 10) / 10} ms`;\n if (ms < 60_000) return `${(ms / 1000).toFixed(2)} s`;\n const s = Math.round(ms / 1000);\n return `${Math.floor(s / 60)} m ${String(s % 60).padStart(2, '0')} s`;\n}\n\nexport function formatPercent(rate: number | null | undefined): string {\n if (rate === null || rate === undefined) return '—';\n const p = rate * 100;\n return `${p === 0 ? '0' : p < 0.1 ? '<0.1' : p.toFixed(p < 10 ? 1 : 0)} %`;\n}\n\nexport function formatAgo(iso: string | null | undefined): string {\n if (!iso) return '—';\n const s = Math.round((Date.now() - new Date(iso).getTime()) / 1000);\n if (s < 60) return `${Math.max(s, 0)} s ago`;\n if (s < 3600) return `${Math.round(s / 60)} min ago`;\n if (s < 86_400) return `${Math.round(s / 3600)} h ago`;\n return `${Math.round(s / 86_400)} d ago`;\n}\n\n@Pipe({ name: 'duration' })\nexport class DurationPipe implements PipeTransform {\n transform(ms: number | null | undefined) { return formatDuration(ms); }\n}\n\n@Pipe({ name: 'percent2' })\nexport class PercentPipe implements PipeTransform {\n transform(rate: number | null | undefined) { return formatPercent(rate); }\n}\n\n@Pipe({ name: 'ago' })\nexport class AgoPipe implements PipeTransform {\n transform(iso: string | null | undefined) { return formatAgo(iso); }\n}\n\n/** A reason code such as `dependency_failed:shop/payments` as words. */\n@Pipe({ name: 'reason' })\nexport class ReasonPipe implements PipeTransform {\n transform(code: string) {\n const [what, subject] = code.split(/:(.*)/s);\n const text = what.replace(/_/g, ' ');\n return subject ? `${text}: ${subject}` : text;\n }\n}\n","/**\n * The app's colours, as `observability.colors($theme)` wrote them in `--jc-*` custom properties, for what CSS cannot\n * colour itself: the charts (ECharts) and the dependency graph (Cytoscape) draw on their own.\n */\nexport function themeColor(name: string, element: Element = document.body): string {\n return getComputedStyle(element).getPropertyValue(`--jc-${name}`).trim();\n}\n\n/** The categorical colours (`$series`): services in a trace, lines in a chart. */\nexport function seriesColors(element: Element = document.body): string[] {\n const count = Number(themeColor('series-count', element)) || 0;\n return Array.from({ length: count }, (_, i) => themeColor(`series-${i + 1}`, element)).filter(Boolean);\n}\n\n/**\n * Calls back when the app switches theme: a class change on `<html>` or `<body>`, as juice-layout's dark-mode toggle\n * makes. Returns the function that stops listening.\n */\nexport function onThemeChange(callback: () => void): () => void {\n const observer = new MutationObserver(callback);\n for (const element of [document.documentElement, document.body]) {\n observer.observe(element, { attributes: true, attributeFilter: ['class'] });\n }\n return () => observer.disconnect();\n}\n","import { ObservabilityLinks } from '../core/options';\nimport {\n ChangeDetectionStrategy, Component, ElementRef, OnDestroy, afterNextRender, computed, effect, inject, input, signal, viewChild,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatSelectModule } from '@angular/material/select';\nimport { Router } from '@angular/router';\nimport { catchError, map, of } from 'rxjs';\nimport type { ECharts, EChartsOption } from 'echarts';\nimport { ApiService } from '../core/api.service';\nimport { ContextService, RANGES } from '../core/context.service';\nimport { HealthState } from '../core/models';\nimport { onThemeChange, seriesColors, themeColor } from './theme';\n\n/** A health state, with \"no data\" for a missing one (Console requirements §10: missing data is not zero). */\n@Component({\n selector: 'jc-health',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `<span class=\"jc-badge\" [class]=\"'jc-health-' + (state() ?? 'none').toLowerCase()\"\n [attr.title]=\"stale() ? 'Evaluation has stopped: this state may be out of date' : null\">\n {{ state() ?? 'No data' }}@if (stale()) {<span class=\"jc-stale\"> · stale</span>}</span>`,\n})\nexport class HealthBadge {\n readonly state = input<HealthState | null | undefined>();\n readonly stale = input(false);\n}\n\n/** Observed or inferred dependency evidence (Console requirements §9): inferred is never shown as confirmed. */\n@Component({\n selector: 'jc-confidence',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `<span class=\"jc-badge\" [class.jc-observed]=\"observed()\" [class.jc-inferred]=\"!observed()\"\n [attr.title]=\"observed() ? 'Seen in traces: a call from one service to the other' : 'Inferred from span attributes, not a confirmed call'\">\n {{ observed() ? 'observed' : 'inferred' }}</span>`,\n})\nexport class ConfidenceBadge {\n readonly confidence = input.required<string>();\n protected readonly observed = computed(() => this.confidence().toLowerCase() === 'observed');\n}\n\n/** Attributes as a key/value table, sorted, values wrapped. */\n@Component({\n selector: 'jc-kv',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (entries().length === 0) { <p class=\"jc-muted\">{{ empty() }}</p> }\n @else {\n <table class=\"jc-kv\"><tbody>\n @for (e of entries(); track e[0]) { <tr><th>{{ e[0] }}</th><td>{{ e[1] }}</td></tr> }\n </tbody></table>\n }`,\n})\nexport class KeyValueTable {\n readonly values = input<Record<string, string | null | undefined>>({});\n readonly empty = input('None');\n protected readonly entries = computed(() =>\n Object.entries(this.values()).filter((e): e is [string, string] => e[1] !== null && e[1] !== undefined && e[1] !== '')\n .sort((a, b) => a[0].localeCompare(b[0])));\n}\n\n/**\n * The context bar on every page (Console requirements §13, §17): environment, time range, and a global lookup for a\n * trace id, a span id or a service name.\n */\n@Component({\n selector: 'jc-context-bar',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FormsModule, MatSelectModule, MatIconModule, MatButtonModule],\n template: `\n <div class=\"jc-context\">\n <ng-content />\n <span class=\"jc-spacer\"></span>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\">\n <mat-label>Environment</mat-label>\n <mat-select [value]=\"ctx.environment()\" (valueChange)=\"ctx.set({ env: $event || null })\">\n <mat-option value=\"\">All</mat-option>\n @for (e of environments(); track e.id) { <mat-option [value]=\"e.key\">{{ e.key }}</mat-option> }\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\">\n <mat-label>Time range</mat-label>\n <mat-select [value]=\"ctx.range()\" (valueChange)=\"ctx.set({ range: $event })\">\n @for (r of ranges; track r) { <mat-option [value]=\"r\">Last {{ r }}</mat-option> }\n @if (ctx.range() === 'custom') { <mat-option value=\"custom\" disabled>Custom window</mat-option> }\n </mat-select>\n </mat-form-field>\n <form class=\"jc-lookup\" (ngSubmit)=\"lookup()\">\n <mat-icon>search</mat-icon>\n <input name=\"q\" [(ngModel)]=\"query\" placeholder=\"Trace ID, span ID or service\" aria-label=\"Go to trace, span or service\">\n </form>\n </div>\n @if (message()) { <p class=\"jc-muted jc-lookup-message\">{{ message() }}</p> }`,\n})\nexport class ContextBar {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n private readonly links = inject(ObservabilityLinks);\n protected readonly ranges = Object.keys(RANGES);\n protected readonly environments = toSignal(this.api.environments().pipe(map(p => p.items), catchError(() => of([]))), { initialValue: [] });\n protected query = '';\n protected readonly message = signal('');\n\n lookup() {\n const q = this.query.trim();\n if (!q) return;\n this.message.set('Searching…');\n this.api.search(q).subscribe({\n next: page => {\n const hit = page.items[0];\n if (!hit) { this.message.set(`Nothing found for \"${q}\".`); return; }\n this.message.set(page.items.length > 1 ? `${page.items.length} matches; showing the first.` : '');\n const extra = hit.kind === 'span' ? { span: hit.id } : {};\n void (hit.kind === 'service'\n ? this.router.navigate(this.links.to('services', hit.id), { queryParams: this.ctx.keep() })\n : this.router.navigate(this.links.to('traces', hit.traceId), { queryParams: this.ctx.keep(extra) }));\n },\n error: () => this.message.set('Search failed.'),\n });\n }\n}\n\nexport interface ChartSeries { name: string; points: { at: string; value: number | null }[]; unit?: string; }\n\n/**\n * The API returns no point for a bucket without data; give each missing bucket a null so the chart shows a gap\n * instead of drawing a line across it (no data is not zero).\n */\nexport function withGaps(points: { at: string; value: number | null }[], from: string, to: string, stepSeconds: number) {\n const step = stepSeconds * 1000;\n const byTime = new Map(points.map(p => [new Date(p.at).getTime(), p.value]));\n const out: { at: string; value: number | null }[] = [];\n for (let t = Math.floor(new Date(from).getTime() / step) * step; t < new Date(to).getTime(); t += step) {\n out.push({ at: new Date(t).toISOString(), value: byTime.get(t) ?? null });\n }\n return out;\n}\n\n/**\n * A time series chart (Apache ECharts, loaded on first use). A missing bucket is a gap in the line, never zero.\n */\n@Component({\n selector: 'jc-chart',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `<div class=\"jc-chart-title\">{{ title() }}</div>\n @if (empty()) { <div class=\"jc-chart jc-chart-empty\">No data in this window</div> }\n <div #host class=\"jc-chart\" [hidden]=\"empty()\"></div>`,\n})\nexport class Chart implements OnDestroy {\n readonly title = input('');\n readonly series = input<ChartSeries[]>([]);\n readonly format = input<(v: number) => string>(v => String(Math.round(v * 100) / 100));\n private readonly host = viewChild.required<ElementRef<HTMLDivElement>>('host');\n protected readonly empty = computed(() => this.series().every(s => s.points.every(p => p.value === null)));\n private chart?: ECharts;\n private observer?: ResizeObserver;\n private stopThemeWatch?: () => void;\n\n constructor() {\n afterNextRender(async () => {\n // Only what a line chart needs: the full library is three times the size.\n const [echarts, charts, components, renderers] = await Promise.all([\n import('echarts/core'), import('echarts/charts'), import('echarts/components'), import('echarts/renderers')]);\n echarts.use([charts.LineChart, components.GridComponent, components.TooltipComponent, components.LegendComponent, renderers.SVGRenderer]);\n this.chart = echarts.init(this.host().nativeElement, undefined, { renderer: 'svg' });\n this.observer = new ResizeObserver(() => this.chart?.resize());\n this.observer.observe(this.host().nativeElement);\n this.stopThemeWatch = onThemeChange(() => this.render());\n this.render();\n });\n effect(() => { this.series(); this.render(); });\n }\n\n private render() {\n if (!this.chart) return;\n const format = this.format();\n // The app's theme colours (observability.colors), read at each render so a theme switch repaints.\n const host = this.host().nativeElement;\n const [text, muted, border, colors] = [themeColor('text', host), themeColor('muted', host), themeColor('border', host), seriesColors(host)];\n const option: EChartsOption = {\n animation: false,\n color: colors.length ? colors : undefined,\n grid: { left: 56, right: 16, top: 16, bottom: 28 },\n tooltip: { trigger: 'axis', valueFormatter: v => (typeof v === 'number' ? format(v) : '—') },\n legend: this.series().length > 1 ? { top: 0, textStyle: { color: text } } : undefined,\n xAxis: { type: 'time', axisLabel: { color: muted }, axisLine: { lineStyle: { color: border } } },\n yAxis: { type: 'value', axisLabel: { color: muted, formatter: (v: number) => format(v) },\n splitLine: { lineStyle: { color: border } } },\n series: this.series().map(s => ({\n name: s.name, type: 'line', showSymbol: false, connectNulls: false,\n data: s.points.map(p => [p.at, p.value]),\n })),\n };\n this.chart.setOption(option, true);\n }\n\n ngOnDestroy() {\n this.observer?.disconnect();\n this.stopThemeWatch?.();\n this.chart?.dispose();\n }\n}\n","import { DestroyRef, Signal, computed, inject, linkedSignal, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { apiResource } from './resource';\nimport { PageEvent } from '@angular/material/paginator';\nimport { Observable, map } from 'rxjs';\nimport { Query } from '../core/api.service';\n\n/** A page from an API list that pages with a cursor: alerts, traces, logs. */\nexport interface CursorPage<T> { items: T[]; nextCursor?: string | null; }\n\n/**\n * A newest-first list read page by page with the API's cursor. The first page loads whenever `params` change; `more()`\n * appends the next page, asked with exactly the query of the first one (its time window included), so the list grows\n * without shifting, skipping or repeating rows. Call it in an injection context, such as a field initializer.\n */\nexport function cursorList<P, T>(params: () => P, query: (params: P) => Query, fetch: (query: Query) => Observable<CursorPage<T>>) {\n const destroyRef = inject(DestroyRef);\n const first = apiResource({\n params,\n stream: ({ params }) => {\n const q = query(params);\n return fetch(q).pipe(map(page => ({ page, query: q })));\n },\n });\n // Pages after the first; reset whenever the first page reloads.\n const rest = linkedSignal({ source: first.value, computation: () => ({ items: [] as T[], cursor: undefined as string | null | undefined }) });\n const loadingMore = signal(false);\n const moreError = signal<unknown>(null);\n const items = computed(() => [...(first.value()?.page.items ?? []), ...rest().items]);\n const cursor = computed(() => rest().cursor !== undefined ? rest().cursor! : first.value()?.page.nextCursor ?? null);\n\n function more() {\n const current = first.value();\n const next = cursor();\n if (!current || !next || loadingMore()) return;\n loadingMore.set(true);\n moreError.set(null);\n fetch({ ...current.query, cursor: next }).pipe(takeUntilDestroyed(destroyRef)).subscribe({\n next: page => rest.update(r => ({ items: [...r.items, ...page.items], cursor: page.nextCursor ?? null })),\n error: e => { moreError.set(e); loadingMore.set(false); },\n complete: () => loadingMore.set(false),\n });\n }\n\n return { first, items, hasMore: computed(() => cursor() !== null), loadingMore, moreError: moreError.asReadonly(), more };\n}\n\n/**\n * Pages a list already in memory for display (the inventory lists, which the Console sorts and filters itself). Goes\n * back to the first page whenever the rows change, for example when a filter narrows them.\n */\nexport function screenPages<T>(rows: Signal<T[]>, sizes = [50, 100, 200]) {\n const size = signal(sizes[0]);\n const index = linkedSignal({ source: rows, computation: () => 0 });\n return {\n sizes,\n size: size.asReadonly(),\n index: index.asReadonly(),\n visible: computed(() => rows().slice(index() * size(), (index() + 1) * size())),\n /** More rows than one page: show the paginator. */\n needed: computed(() => rows().length > sizes[0]),\n change(event: PageEvent) { size.set(event.pageSize); index.set(event.pageIndex); },\n };\n}\n","import { LinkPipe } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input, linkedSignal } from '@angular/core';\nimport { apiResource } from './resource';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { MatSelectModule } from '@angular/material/select';\nimport { RouterLink } from '@angular/router';\nimport { ApiService, Query } from '../core/api.service';\nimport { ContextService, MAX_SEARCH_MS } from '../core/context.service';\nimport { DurationPipe } from './format';\nimport { cursorList } from './paging';\nimport { Chart, withGaps } from './ui';\n\n/** Shows a request's failure as a short line instead of a broken page. */\nexport function problemText(error: unknown): string {\n const e = error as { status?: number; error?: { title?: string; detail?: string } } | undefined;\n if (!e) return '';\n if (e.status === 0) return 'The Juice API cannot be reached.';\n return [e.error?.title, e.error?.detail].filter(Boolean).join(': ') || `Request failed (${e.status}).`;\n}\n\n/**\n * Traces matching a query in the current context, newest first (Console requirements §5): enough per row to pick a\n * useful one, and one click to its waterfall.\n */\n@Component({\n selector: 'jc-trace-table',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, DatePipe, DurationPipe, MatProgressBarModule, MatButtonModule],\n template: `\n @if (traces.first.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (traces.first.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n @if (traces.first.hasValue()) {\n @if (rows().length === 0) { <p class=\"jc-muted\">No traces match in this window.</p> }\n @else {\n <table class=\"jc-table\">\n <thead><tr><th>Start</th><th>Root operation</th><th>Duration</th><th>Spans</th><th>Status</th><th>Instance</th></tr></thead>\n <tbody>\n @for (t of rows(); track t.traceId) {\n <tr>\n <td class=\"jc-nowrap\">{{ t.startedAt | date: 'MMM d, HH:mm:ss.SSS' }}</td>\n <td><a [routerLink]=\"['traces', t.traceId] | jcLink\" [queryParams]=\"ctx.keep()\">{{ t.rootService }} · {{ t.rootOperation }}</a>\n @if (t.services.length > 1) { <div class=\"jc-muted jc-small\">{{ t.services.join(' → ') }}</div> }</td>\n <td class=\"jc-nowrap\"><span class=\"jc-bar\" [style.width.px]=\"barWidth(t.durationMs)\"></span>{{ t.durationMs | duration }}</td>\n <td>{{ t.spanCount }}</td>\n <td>@if (t.errorCount > 0) { <span class=\"jc-badge jc-health-unhealthy\">{{ t.errorCount }} error{{ t.errorCount > 1 ? 's' : '' }}</span> }\n @else { <span class=\"jc-badge jc-health-healthy\">ok</span> }</td>\n <td class=\"jc-muted jc-small\">{{ t.rootInstance ?? t.rootHost ?? '—' }}</td>\n </tr>\n }\n </tbody>\n </table>\n <div class=\"jc-more\">\n <span class=\"jc-muted\">{{ rows().length }} trace{{ rows().length === 1 ? '' : 's' }}{{ traces.hasMore() ? ', newest first' : '' }}</span>\n @if (traces.hasMore()) {\n <button mat-button (click)=\"traces.more()\" [disabled]=\"traces.loadingMore()\">{{ traces.loadingMore() ? 'Loading…' : 'Older traces' }}</button>\n }\n @if (traces.moreError()) { <span class=\"jc-error\">{{ moreError() }}</span> }\n </div>\n }\n }`,\n})\nexport class TraceTable {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n readonly query = input<Query>({});\n readonly limit = input(50);\n\n /** \"Older traces\" appends the next page of the same search, over the same window. */\n protected readonly traces = cursorList(\n () => ({ query: this.query(), env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute(), limit: this.limit() }),\n p => ({ environment: p.env, ...this.ctx.window(MAX_SEARCH_MS), limit: p.limit, ...p.query }),\n q => this.api.traces(q));\n protected readonly rows = this.traces.items;\n protected readonly longest = computed(() => Math.max(1, ...this.rows().map(t => t.durationMs)));\n /** Pixels, not a percentage: an auto-sized cell ignores a percentage width, so a full bar pushed the time into the next column. */\n protected barWidth(ms: number) { return Math.max(2, 80 * ms / this.longest()); }\n protected readonly error = computed(() => problemText(this.traces.first.error()));\n protected readonly moreError = computed(() => problemText(this.traces.moreError()));\n}\n\n/**\n * Log records for a query in the current context (Console requirements §7): each with its trace and span, when the\n * record carries them.\n */\n@Component({\n selector: 'jc-log-table',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, DatePipe, MatProgressBarModule, MatButtonModule],\n template: `\n @if (logs.first.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (logs.first.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n @if (logs.first.hasValue()) {\n @if (rows().length === 0) { <p class=\"jc-muted\">No log records match in this window.</p> }\n @else {\n <table class=\"jc-table jc-logs\">\n <thead><tr><th>Time</th><th>Severity</th>@if (!compact()) { <th>Service</th> }<th>Message</th>@if (!compact()) { <th>Trace</th> }</tr></thead>\n <tbody>\n @for (l of rows(); track $index) {\n <tr>\n <td class=\"jc-nowrap\">{{ l.timestamp | date: 'MMM d, HH:mm:ss.SSS' }}</td>\n <td><span class=\"jc-badge\" [class]=\"severityClass(l.severityNumber)\">{{ l.severity || l.severityNumber }}</span></td>\n @if (!compact()) { <td class=\"jc-nowrap\">{{ l.service }}@if (l.instance) {<div class=\"jc-muted jc-small\">{{ l.instance }}</div>}</td> }\n <td><pre class=\"jc-body\">{{ l.body }}@if (l.bodyTruncated) {…}</pre></td>\n @if (!compact()) { <td class=\"jc-nowrap\">\n @if (l.traceId) {\n <a [routerLink]=\"['traces', l.traceId] | jcLink\" [queryParams]=\"ctx.keep(l.spanId ? { span: l.spanId } : {})\">{{ l.spanId ? 'span' : 'trace' }} ↗</a>\n } @else { <span class=\"jc-muted\">—</span> }\n </td> }\n </tr>\n }\n </tbody>\n </table>\n <div class=\"jc-more\">\n <span class=\"jc-muted\">{{ rows().length }} record{{ rows().length === 1 ? '' : 's' }}{{ logs.hasMore() ? ', newest first' : '' }}</span>\n @if (logs.hasMore()) {\n <button mat-button (click)=\"logs.more()\" [disabled]=\"logs.loadingMore()\">{{ logs.loadingMore() ? 'Loading…' : 'Older logs' }}</button>\n }\n @if (logs.moreError()) { <span class=\"jc-error\">{{ moreError() }}</span> }\n </div>\n }\n }`,\n})\nexport class LogTable {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n readonly query = input<Query>({});\n /** Inside a span: its service and trace are already on screen, so only time, severity and message. */\n readonly compact = input(false);\n\n /** \"Older logs\" appends the next page of the same search, over the same window. */\n protected readonly logs = cursorList(\n () => ({ query: this.query(), env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() }),\n p => ({ environment: p.env, ...this.ctx.window(MAX_SEARCH_MS), limit: 200, ...p.query }),\n q => this.api.logs(q));\n protected readonly rows = this.logs.items;\n protected readonly error = computed(() => problemText(this.logs.first.error()));\n protected readonly moreError = computed(() => problemText(this.logs.moreError()));\n\n severityClass(n: number) {\n return n >= 17 ? 'jc-health-unhealthy' : n >= 13 ? 'jc-health-degraded' : n >= 9 ? 'jc-health-healthy' : 'jc-health-none';\n }\n}\n\n/**\n * The metrics reported for a scope (service, instance or host) and a chart of the one selected (Console requirements\n * §7): a counter as a rate, a gauge or histogram as an average.\n */\n@Component({\n selector: 'jc-metric-explorer',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FormsModule, MatSelectModule, MatProgressBarModule, Chart],\n template: `\n @if (metrics.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (metrics.error()) { <p class=\"jc-error\">{{ metricsError() }}</p> }\n @if (metrics.hasValue()) {\n @if (names().length === 0) { <p class=\"jc-muted\">No metrics reported in this window.</p> }\n @else {\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\">\n <mat-label>Metric</mat-label>\n <mat-select [value]=\"selected()\" (valueChange)=\"selected.set($event)\">\n @for (m of names(); track m.name) { <mat-option [value]=\"m.name\">{{ m.name }} <span class=\"jc-muted\">({{ m.type }}{{ m.unit ? ', ' + m.unit : '' }})</span></mat-option> }\n </mat-select>\n </mat-form-field>\n @if (series.value(); as s) {\n <jc-chart [title]=\"title()\" [series]=\"[{ name: s.name, points: points() }]\" />\n }\n }\n }`,\n})\nexport class MetricExplorer {\n private readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n readonly scope = input<Query>({});\n readonly initial = input<string | null>(null);\n\n protected readonly metrics = apiResource({\n params: () => ({ scope: this.scope(), env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() }),\n stream: ({ params }) => this.api.metrics({ environment: params.env, ...this.ctx.window(), ...params.scope }),\n });\n protected readonly names = computed(() => this.metrics.value()?.items ?? []);\n protected readonly selected = linkedSignal(() => {\n const names = this.names();\n return names.find(m => m.name === this.initial())?.name ?? names[0]?.name ?? null;\n });\n protected readonly series = apiResource({\n // Idle until a metric is chosen.\n params: () => this.selected() ? { name: this.selected()!, scope: this.scope(), env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() } : undefined,\n stream: ({ params }) => {\n this.window = this.ctx.window();\n return this.api.series({ name: params.name, environment: params.env, ...this.window, ...params.scope });\n },\n });\n private window = { from: '', to: '' };\n protected readonly points = computed(() => {\n const s = this.series.value();\n return s ? withGaps(s.points, this.window.from, this.window.to, s.stepSeconds) : [];\n });\n protected readonly title = computed(() => {\n const s = this.series.value();\n if (!s) return '';\n const what = s.aggregation === 'rate' ? 'rate per second' : 'average';\n return `${s.name} — ${what}${s.unit ? ` (${s.unit}${s.aggregation === 'rate' ? '/s' : ''})` : ''}`;\n });\n protected readonly metricsError = computed(() => problemText(this.metrics.error()));\n}\n\n/** Requests, error rate and p95 of a service (or one operation) over the window. */\n@Component({\n selector: 'jc-service-stats',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [Chart, MatProgressBarModule],\n template: `\n @if (stats.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (stats.hasValue()) {\n <div class=\"jc-grid-3\">\n <jc-chart title=\"Requests per second\" [series]=\"[{ name: 'requests/s', points: pick('requestsPerSecond') }]\" />\n <jc-chart title=\"Error rate\" [series]=\"[{ name: 'errors', points: pick('errorRate') }]\" [format]=\"percent\" />\n <jc-chart title=\"p95 latency\" [series]=\"[{ name: 'p95', points: pick('p95Ms') }]\" [format]=\"ms\" />\n </div>\n }`,\n})\nexport class ServiceStatsCharts {\n private readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n readonly query = input.required<Query>();\n private window = { from: '', to: '' };\n protected readonly percent = (v: number) => `${Math.round(v * 1000) / 10} %`;\n protected readonly ms = (v: number) => `${Math.round(v)} ms`;\n\n protected readonly stats = apiResource({\n params: () => ({ query: this.query(), env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() }),\n stream: ({ params }) => {\n this.window = this.ctx.window();\n return this.api.serviceStats({ environment: params.env, ...this.window, ...params.query });\n },\n });\n\n pick(field: 'requestsPerSecond' | 'errorRate' | 'p95Ms') {\n const s = this.stats.value();\n return s ? withGaps(s.points.map(p => ({ at: p.at, value: p[field] })), this.window.from, this.window.to, s.stepSeconds) : [];\n }\n}\n\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiService, ContextService, LinkPipe, ObservabilityLinks } from "./juice-js-observability.mjs";
|
|
2
|
-
import { a as problemText, c as ConfidenceBadge, f as onThemeChange, g as PercentPipe, h as DurationPipe, l as ContextBar, m as AgoPipe, p as themeColor, u as HealthBadge, x as apiResource } from "./juice-js-observability-telemetry-
|
|
2
|
+
import { a as problemText, c as ConfidenceBadge, f as onThemeChange, g as PercentPipe, h as DurationPipe, l as ContextBar, m as AgoPipe, p as themeColor, u as HealthBadge, x as apiResource } from "./juice-js-observability-telemetry-gYN3UogH.mjs";
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
import { ChangeDetectionStrategy, Component, afterNextRender, computed, effect, inject, input, viewChild } from "@angular/core";
|
|
5
5
|
import { Router, RouterLink } from "@angular/router";
|
|
@@ -480,4 +480,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
480
480
|
});
|
|
481
481
|
export { TopologyPage };
|
|
482
482
|
|
|
483
|
-
//# sourceMappingURL=juice-js-observability-topology.page-
|
|
483
|
+
//# sourceMappingURL=juice-js-observability-topology.page-CChcM0n-.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"juice-js-observability-topology.page-CLz7J5pq.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/topology.page.ts"],"sourcesContent":["import { LinkPipe, ObservabilityLinks } from '../core/options';\nimport {\n ChangeDetectionStrategy, Component, ElementRef, OnDestroy, afterNextRender, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { Router, RouterLink } from '@angular/router';\nimport type { Core, StylesheetJson } from 'cytoscape';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Topology } from '../core/models';\nimport { AgoPipe, DurationPipe, PercentPipe } from '../shared/format';\nimport { ConfidenceBadge, ContextBar, HealthBadge } from '../shared/ui';\nimport { problemText } from '../shared/telemetry';\nimport { onThemeChange, themeColor } from '../shared/theme';\n\n/** A service's health → its --jc-* colour from the app's theme (observability.colors). */\nconst HEALTH_TOKENS: Record<string, string> = {\n Healthy: 'ok', Degraded: 'warn', Unhealthy: 'bad', Critical: 'crit', Offline: 'off', Unknown: 'none',\n};\n\n/**\n * Dependency topology (Console requirements §9): services, databases and destinations; zoom, pan and selection.\n * Observed edges are solid, inferred ones dashed and labelled, so an inference never reads as a confirmed call. A\n * selected edge shows its evidence and representative traces; the selection is in the URL.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, MatButtonModule, MatIconModule, MatProgressBarModule, ContextBar, HealthBadge, ConfidenceBadge,\n AgoPipe, DurationPipe, PercentPipe],\n template: `\n <jc-context-bar><h1>Dependencies</h1></jc-context-bar>\n @if (topology.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (topology.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n <div class=\"jc-topology\">\n <div class=\"jc-graph-wrap\">\n <div #graph class=\"jc-graph\" aria-label=\"Dependency graph\"></div>\n <div class=\"jc-graph-legend\">\n <span><i class=\"jc-line\"></i> observed</span><span><i class=\"jc-line jc-dashed\"></i> inferred</span>\n <span>■ service ◆ database ⬣ destination</span>\n <button mat-button (click)=\"fit()\">Fit</button>\n </div>\n @if (topology.value()?.truncated) { <p class=\"jc-muted\">Showing the most recent edges only.</p> }\n @if (topology.hasValue() && topology.value()!.nodes.length === 0) { <p class=\"jc-muted\">No dependencies seen{{ ctx.environment() ? ' in ' + ctx.environment() : '' }}.</p> }\n </div>\n\n <aside class=\"jc-card jc-side\">\n @if (selectedEdge(); as e) {\n <h2>{{ name(e.source) }} → {{ name(e.target) }}</h2>\n <p>{{ e.relation }} <jc-confidence [confidence]=\"e.confidence\" /></p>\n @if (e.confidence.toLowerCase() !== 'observed') {\n <p class=\"jc-muted jc-small\">Inferred from span attributes (for example db.system.name or messaging.destination.name), not a call\n seen between two instrumented services.</p>\n }\n @if (evidence.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (evidence.value(); as ev) {\n <dl class=\"jc-facts\">\n <dt>Calls in window</dt><dd>{{ ev.count }}</dd>\n <dt>Error rate</dt><dd [class.jc-bad]=\"ev.errorRate >= 0.05\">{{ ev.count ? (ev.errorRate | percent2) : 'no data' }}</dd>\n <dt>p95</dt><dd>{{ ev.p95Ms | duration }}</dd>\n <dt>Last seen</dt><dd>{{ ev.lastSeen ?? e.lastSeen | ago }}</dd>\n </dl>\n <h3>Representative traces</h3>\n @for (t of ev.errorTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-badge jc-health-unhealthy\">error</span></div> }\n @for (t of ev.slowestTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-muted jc-small\">slow</span></div> }\n @if (!ev.errorTraces.length && !ev.slowestTraces.length) { <p class=\"jc-muted\">No calls in this window.</p> }\n <a mat-stroked-button [routerLink]=\"['traces'] | jcLink\" [queryParams]=\"ctx.keep({ dependency: e.id })\">All traces through this edge</a>\n }\n <p><a [routerLink]=\"link(e.source)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.source) }}</a> ·\n <a [routerLink]=\"link(e.target)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.target) }}</a></p>\n } @else if (selectedNode(); as n) {\n <h2>{{ n.displayName }}</h2>\n <p>{{ n.type }} <jc-health [state]=\"n.health\" /></p>\n <a mat-stroked-button [routerLink]=\"link(n.id)\" [queryParams]=\"ctx.keep()\">Open</a>\n } @else {\n <p class=\"jc-muted\">Select a node or an edge. Scroll to zoom, drag to pan.</p>\n }\n </aside>\n </div>`,\n})\nexport class TopologyPage implements OnDestroy {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n private readonly links = inject(ObservabilityLinks);\n\n readonly edge = input<string>();\n readonly node = input<string>();\n\n private readonly graph = viewChild.required<ElementRef<HTMLDivElement>>('graph');\n private cy?: Core;\n private stopThemeWatch?: () => void;\n\n protected readonly topology = apiResource({ params: () => this.ctx.environment(), stream: ({ params }) => this.api.topology(params || null) });\n protected readonly error = computed(() => problemText(this.topology.error()));\n private readonly nodes = computed(() => new Map((this.topology.value()?.nodes ?? []).map(n => [n.id, n])));\n protected readonly selectedEdge = computed(() => this.topology.value()?.edges.find(e => e.id === this.edge()) ?? null);\n protected readonly selectedNode = computed(() => (this.node() ? this.nodes().get(this.node()!) : null) ?? null);\n protected readonly evidence = apiResource({\n // No edge selected: undefined params leave the resource idle.\n params: () => this.edge() ? { edge: this.edge()!, env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() } : undefined,\n stream: ({ params }) => this.api.dependency(params.edge, { environment: params.env, ...this.ctx.window() }),\n });\n\n constructor() {\n afterNextRender(async () => {\n const cytoscape = (await import('cytoscape')).default;\n this.cy = cytoscape({\n container: this.graph().nativeElement,\n wheelSensitivity: 0.3,\n style: this.graphStyle(),\n });\n // The app switched theme (light/dark): restyle and recolour the nodes.\n this.stopThemeWatch = onThemeChange(() => {\n this.cy?.style(this.graphStyle());\n this.draw(this.topology.value());\n });\n this.cy.on('tap', 'edge', e => this.choose({ edge: e.target.id(), node: null }));\n this.cy.on('tap', 'node', e => this.choose({ node: e.target.id(), edge: null }));\n this.cy.on('tap', e => { if (e.target === this.cy) this.choose({ node: null, edge: null }); });\n this.draw(this.topology.value());\n });\n effect(() => this.draw(this.topology.value()));\n effect(() => {\n const id = this.edge() ?? this.node();\n this.cy?.elements().unselect();\n if (id) this.cy?.getElementById(id).select();\n });\n }\n\n private draw(topology: Topology | undefined) {\n if (!this.cy || !topology) return;\n this.cy.elements().remove();\n this.cy.add([\n ...topology.nodes.map(n => ({ data: {\n id: n.id, label: n.displayName, type: n.type,\n color: this.color(n.type === 'service' ? HEALTH_TOKENS[n.health ?? 'Unknown'] ?? 'none' : 'muted') } })),\n ...topology.edges.filter(e => this.nodes().has(e.source) && this.nodes().has(e.target)).map(e => ({ data: {\n id: e.id, source: e.source, target: e.target, confidence: e.confidence.toLowerCase(),\n label: e.confidence.toLowerCase() === 'inferred' ? `${e.relation} (inferred)` : e.relation } })),\n ]);\n this.cy.layout({ name: 'breadthfirst', directed: true, padding: 30, spacingFactor: 1.2 }).run();\n const id = this.edge() ?? this.node();\n if (id) this.cy.getElementById(id).select();\n }\n\n private color(token: string) { return themeColor(token, this.graph().nativeElement); }\n\n /** The graph's look, in the app's theme colours. */\n private graphStyle() {\n const [text, muted, line, surface, selected] = [this.color('text'), this.color('muted'), this.color('border'),\n this.color('surface'), this.color('accent')];\n return [\n { selector: 'node', style: {\n label: 'data(label)', 'font-size': 11, color: text, 'text-valign': 'bottom', 'text-margin-y': 4,\n 'background-color': 'data(color)', width: 26, height: 26, 'border-width': 2, 'border-color': surface } },\n { selector: 'node[type = \"database\"]', style: { shape: 'diamond' } },\n { selector: 'node[type = \"messaging-destination\"]', style: { shape: 'hexagon' } },\n { selector: 'node[type = \"service\"]', style: { shape: 'round-rectangle' } },\n { selector: 'edge', style: {\n width: 2, 'curve-style': 'bezier', 'target-arrow-shape': 'triangle', 'line-color': muted,\n 'target-arrow-color': muted, label: 'data(label)', 'font-size': 9, color: muted, 'text-rotation': 'autorotate',\n 'text-background-color': surface, 'text-background-opacity': 0.8, 'text-background-padding': '1px' } },\n { selector: 'edge[confidence = \"inferred\"]', style: { 'line-style': 'dashed', 'line-color': line, 'target-arrow-color': line } },\n { selector: ':selected', style: { 'line-color': selected, 'target-arrow-color': selected, 'border-color': selected, 'border-width': 3 } },\n ] as StylesheetJson;\n }\n\n fit() { this.cy?.fit(undefined, 30); }\n\n private choose(selection: { edge?: string | null; node?: string | null }) {\n void this.router.navigate([], { queryParams: selection, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n name(id: string) { return this.nodes().get(id)?.displayName ?? id; }\n\n link(id: string) { return this.links.to(this.nodes().get(id)?.type === 'service' ? 'services' : 'resources', id); }\n\n ngOnDestroy() {\n this.stopThemeWatch?.();\n this.cy?.destroy();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"juice-js-observability-topology.page-CChcM0n-.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/topology.page.ts"],"sourcesContent":["import { LinkPipe, ObservabilityLinks } from '../core/options';\nimport {\n ChangeDetectionStrategy, Component, ElementRef, OnDestroy, afterNextRender, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { Router, RouterLink } from '@angular/router';\nimport type { Core, StylesheetJson } from 'cytoscape';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Topology } from '../core/models';\nimport { AgoPipe, DurationPipe, PercentPipe } from '../shared/format';\nimport { ConfidenceBadge, ContextBar, HealthBadge } from '../shared/ui';\nimport { problemText } from '../shared/telemetry';\nimport { onThemeChange, themeColor } from '../shared/theme';\n\n/** A service's health → its --jc-* colour from the app's theme (observability.colors). */\nconst HEALTH_TOKENS: Record<string, string> = {\n Healthy: 'ok', Degraded: 'warn', Unhealthy: 'bad', Critical: 'crit', Offline: 'off', Unknown: 'none',\n};\n\n/**\n * Dependency topology (Console requirements §9): services, databases and destinations; zoom, pan and selection.\n * Observed edges are solid, inferred ones dashed and labelled, so an inference never reads as a confirmed call. A\n * selected edge shows its evidence and representative traces; the selection is in the URL.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, MatButtonModule, MatIconModule, MatProgressBarModule, ContextBar, HealthBadge, ConfidenceBadge,\n AgoPipe, DurationPipe, PercentPipe],\n template: `\n <jc-context-bar><h1>Dependencies</h1></jc-context-bar>\n @if (topology.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (topology.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n <div class=\"jc-topology\">\n <div class=\"jc-graph-wrap\">\n <div #graph class=\"jc-graph\" aria-label=\"Dependency graph\"></div>\n <div class=\"jc-graph-legend\">\n <span><i class=\"jc-line\"></i> observed</span><span><i class=\"jc-line jc-dashed\"></i> inferred</span>\n <span>■ service ◆ database ⬣ destination</span>\n <button mat-button (click)=\"fit()\">Fit</button>\n </div>\n @if (topology.value()?.truncated) { <p class=\"jc-muted\">Showing the most recent edges only.</p> }\n @if (topology.hasValue() && topology.value()!.nodes.length === 0) { <p class=\"jc-muted\">No dependencies seen{{ ctx.environment() ? ' in ' + ctx.environment() : '' }}.</p> }\n </div>\n\n <aside class=\"jc-card jc-side\">\n @if (selectedEdge(); as e) {\n <h2>{{ name(e.source) }} → {{ name(e.target) }}</h2>\n <p>{{ e.relation }} <jc-confidence [confidence]=\"e.confidence\" /></p>\n @if (e.confidence.toLowerCase() !== 'observed') {\n <p class=\"jc-muted jc-small\">Inferred from span attributes (for example db.system.name or messaging.destination.name), not a call\n seen between two instrumented services.</p>\n }\n @if (evidence.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (evidence.value(); as ev) {\n <dl class=\"jc-facts\">\n <dt>Calls in window</dt><dd>{{ ev.count }}</dd>\n <dt>Error rate</dt><dd [class.jc-bad]=\"ev.errorRate >= 0.05\">{{ ev.count ? (ev.errorRate | percent2) : 'no data' }}</dd>\n <dt>p95</dt><dd>{{ ev.p95Ms | duration }}</dd>\n <dt>Last seen</dt><dd>{{ ev.lastSeen ?? e.lastSeen | ago }}</dd>\n </dl>\n <h3>Representative traces</h3>\n @for (t of ev.errorTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-badge jc-health-unhealthy\">error</span></div> }\n @for (t of ev.slowestTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-muted jc-small\">slow</span></div> }\n @if (!ev.errorTraces.length && !ev.slowestTraces.length) { <p class=\"jc-muted\">No calls in this window.</p> }\n <a mat-stroked-button [routerLink]=\"['traces'] | jcLink\" [queryParams]=\"ctx.keep({ dependency: e.id })\">All traces through this edge</a>\n }\n <p><a [routerLink]=\"link(e.source)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.source) }}</a> ·\n <a [routerLink]=\"link(e.target)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.target) }}</a></p>\n } @else if (selectedNode(); as n) {\n <h2>{{ n.displayName }}</h2>\n <p>{{ n.type }} <jc-health [state]=\"n.health\" /></p>\n <a mat-stroked-button [routerLink]=\"link(n.id)\" [queryParams]=\"ctx.keep()\">Open</a>\n } @else {\n <p class=\"jc-muted\">Select a node or an edge. Scroll to zoom, drag to pan.</p>\n }\n </aside>\n </div>`,\n})\nexport class TopologyPage implements OnDestroy {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n private readonly links = inject(ObservabilityLinks);\n\n readonly edge = input<string>();\n readonly node = input<string>();\n\n private readonly graph = viewChild.required<ElementRef<HTMLDivElement>>('graph');\n private cy?: Core;\n private stopThemeWatch?: () => void;\n\n protected readonly topology = apiResource({ params: () => this.ctx.environment(), stream: ({ params }) => this.api.topology(params || null) });\n protected readonly error = computed(() => problemText(this.topology.error()));\n private readonly nodes = computed(() => new Map((this.topology.value()?.nodes ?? []).map(n => [n.id, n])));\n protected readonly selectedEdge = computed(() => this.topology.value()?.edges.find(e => e.id === this.edge()) ?? null);\n protected readonly selectedNode = computed(() => (this.node() ? this.nodes().get(this.node()!) : null) ?? null);\n protected readonly evidence = apiResource({\n // No edge selected: undefined params leave the resource idle.\n params: () => this.edge() ? { edge: this.edge()!, env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() } : undefined,\n stream: ({ params }) => this.api.dependency(params.edge, { environment: params.env, ...this.ctx.window() }),\n });\n\n constructor() {\n afterNextRender(async () => {\n const cytoscape = (await import('cytoscape')).default;\n this.cy = cytoscape({\n container: this.graph().nativeElement,\n wheelSensitivity: 0.3,\n style: this.graphStyle(),\n });\n // The app switched theme (light/dark): restyle and recolour the nodes.\n this.stopThemeWatch = onThemeChange(() => {\n this.cy?.style(this.graphStyle());\n this.draw(this.topology.value());\n });\n this.cy.on('tap', 'edge', e => this.choose({ edge: e.target.id(), node: null }));\n this.cy.on('tap', 'node', e => this.choose({ node: e.target.id(), edge: null }));\n this.cy.on('tap', e => { if (e.target === this.cy) this.choose({ node: null, edge: null }); });\n this.draw(this.topology.value());\n });\n effect(() => this.draw(this.topology.value()));\n effect(() => {\n const id = this.edge() ?? this.node();\n this.cy?.elements().unselect();\n if (id) this.cy?.getElementById(id).select();\n });\n }\n\n private draw(topology: Topology | undefined) {\n if (!this.cy || !topology) return;\n this.cy.elements().remove();\n this.cy.add([\n ...topology.nodes.map(n => ({ data: {\n id: n.id, label: n.displayName, type: n.type,\n color: this.color(n.type === 'service' ? HEALTH_TOKENS[n.health ?? 'Unknown'] ?? 'none' : 'muted') } })),\n ...topology.edges.filter(e => this.nodes().has(e.source) && this.nodes().has(e.target)).map(e => ({ data: {\n id: e.id, source: e.source, target: e.target, confidence: e.confidence.toLowerCase(),\n label: e.confidence.toLowerCase() === 'inferred' ? `${e.relation} (inferred)` : e.relation } })),\n ]);\n this.cy.layout({ name: 'breadthfirst', directed: true, padding: 30, spacingFactor: 1.2 }).run();\n const id = this.edge() ?? this.node();\n if (id) this.cy.getElementById(id).select();\n }\n\n private color(token: string) { return themeColor(token, this.graph().nativeElement); }\n\n /** The graph's look, in the app's theme colours. */\n private graphStyle() {\n const [text, muted, line, surface, selected] = [this.color('text'), this.color('muted'), this.color('border'),\n this.color('surface'), this.color('accent')];\n return [\n { selector: 'node', style: {\n label: 'data(label)', 'font-size': 11, color: text, 'text-valign': 'bottom', 'text-margin-y': 4,\n 'background-color': 'data(color)', width: 26, height: 26, 'border-width': 2, 'border-color': surface } },\n { selector: 'node[type = \"database\"]', style: { shape: 'diamond' } },\n { selector: 'node[type = \"messaging-destination\"]', style: { shape: 'hexagon' } },\n { selector: 'node[type = \"service\"]', style: { shape: 'round-rectangle' } },\n { selector: 'edge', style: {\n width: 2, 'curve-style': 'bezier', 'target-arrow-shape': 'triangle', 'line-color': muted,\n 'target-arrow-color': muted, label: 'data(label)', 'font-size': 9, color: muted, 'text-rotation': 'autorotate',\n 'text-background-color': surface, 'text-background-opacity': 0.8, 'text-background-padding': '1px' } },\n { selector: 'edge[confidence = \"inferred\"]', style: { 'line-style': 'dashed', 'line-color': line, 'target-arrow-color': line } },\n { selector: ':selected', style: { 'line-color': selected, 'target-arrow-color': selected, 'border-color': selected, 'border-width': 3 } },\n ] as StylesheetJson;\n }\n\n fit() { this.cy?.fit(undefined, 30); }\n\n private choose(selection: { edge?: string | null; node?: string | null }) {\n void this.router.navigate([], { queryParams: selection, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n name(id: string) { return this.nodes().get(id)?.displayName ?? id; }\n\n link(id: string) { return this.links.to(this.nodes().get(id)?.type === 'service' ? 'services' : 'resources', id); }\n\n ngOnDestroy() {\n this.stopThemeWatch?.();\n this.cy?.destroy();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ApiService, ContextService, LinkPipe } from "./juice-js-observability.mjs";
|
|
2
|
-
import { a as problemText, b as preciseTime, d as KeyValueTable, h as DurationPipe, l as ContextBar, p as themeColor, t as LogTable, v as formatDuration, x as apiResource, y as plainTraceId } from "./juice-js-observability-telemetry-
|
|
1
|
+
import { ApiService, ContextService, LinkPipe, ObservabilityLinks } from "./juice-js-observability.mjs";
|
|
2
|
+
import { a as problemText, b as preciseTime, d as KeyValueTable, h as DurationPipe, l as ContextBar, p as themeColor, t as LogTable, v as formatDuration, x as apiResource, y as plainTraceId } from "./juice-js-observability-telemetry-gYN3UogH.mjs";
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from "@angular/core";
|
|
5
5
|
import { Router, RouterLink } from "@angular/router";
|
|
@@ -14,6 +14,7 @@ var TracePage = class TracePage {
|
|
|
14
14
|
ctx = inject(ContextService);
|
|
15
15
|
api = inject(ApiService);
|
|
16
16
|
router = inject(Router);
|
|
17
|
+
links = inject(ObservabilityLinks);
|
|
17
18
|
traceId = input.required(...ngDevMode ? [{ debugName: "traceId" }] : /* istanbul ignore next */ []);
|
|
18
19
|
span = input(...ngDevMode ? [void 0, { debugName: "span" }] : /* istanbul ignore next */ []);
|
|
19
20
|
id = computed(() => plainTraceId(this.traceId()), ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
|
|
@@ -22,6 +23,7 @@ var TracePage = class TracePage {
|
|
|
22
23
|
stream: ({ params }) => this.api.trace(params)
|
|
23
24
|
});
|
|
24
25
|
error = computed(() => problemText(this.trace.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
|
|
26
|
+
auditLink = computed(() => this.links.audit(this.id()), ...ngDevMode ? [{ debugName: "auditLink" }] : /* istanbul ignore next */ []);
|
|
25
27
|
collapsed = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []);
|
|
26
28
|
showErrorsOnly = signal(false, ...ngDevMode ? [{ debugName: "showErrorsOnly" }] : /* istanbul ignore next */ []);
|
|
27
29
|
selectedId = computed(() => this.span() ?? null, ...ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []);
|
|
@@ -182,6 +184,10 @@ var TracePage = class TracePage {
|
|
|
182
184
|
@if (errors() > 0) { <span class="jc-badge jc-health-unhealthy">{{ errors() }} failed</span> }
|
|
183
185
|
<button mat-button (click)="showErrorsOnly.set(!showErrorsOnly())">{{ showErrorsOnly() ? 'Show all spans' : 'Error paths only' }}</button>
|
|
184
186
|
<a mat-button [routerLink]="['logs'] | jcLink" [queryParams]="ctx.keep({ trace: id() })">Logs of this trace</a>
|
|
187
|
+
@if (auditLink(); as link) {
|
|
188
|
+
@if ('route' in link) { <a mat-button [routerLink]="link.route" data-testid="audit-link">Audit records ↗</a> }
|
|
189
|
+
@else { <a mat-button [href]="link.href" target="_blank" rel="noopener" data-testid="audit-link">Audit records ↗</a> }
|
|
190
|
+
}
|
|
185
191
|
@if (t.truncated) { <span class="jc-muted">Showing the first {{ t.spans.length }} spans.</span> }
|
|
186
192
|
</div>
|
|
187
193
|
<div class="jc-legend">@for (s of services(); track s) { <span><i [style.background]="color(s)"></i>{{ s }}</span> }</div>
|
|
@@ -411,6 +417,10 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
411
417
|
@if (errors() > 0) { <span class="jc-badge jc-health-unhealthy">{{ errors() }} failed</span> }
|
|
412
418
|
<button mat-button (click)="showErrorsOnly.set(!showErrorsOnly())">{{ showErrorsOnly() ? 'Show all spans' : 'Error paths only' }}</button>
|
|
413
419
|
<a mat-button [routerLink]="['logs'] | jcLink" [queryParams]="ctx.keep({ trace: id() })">Logs of this trace</a>
|
|
420
|
+
@if (auditLink(); as link) {
|
|
421
|
+
@if ('route' in link) { <a mat-button [routerLink]="link.route" data-testid="audit-link">Audit records ↗</a> }
|
|
422
|
+
@else { <a mat-button [href]="link.href" target="_blank" rel="noopener" data-testid="audit-link">Audit records ↗</a> }
|
|
423
|
+
}
|
|
414
424
|
@if (t.truncated) { <span class="jc-muted">Showing the first {{ t.spans.length }} spans.</span> }
|
|
415
425
|
</div>
|
|
416
426
|
<div class="jc-legend">@for (s of services(); track s) { <span><i [style.background]="color(s)"></i>{{ s }}</span> }</div>
|
|
@@ -515,4 +525,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
515
525
|
});
|
|
516
526
|
export { TracePage };
|
|
517
527
|
|
|
518
|
-
//# sourceMappingURL=juice-js-observability-trace.page-
|
|
528
|
+
//# sourceMappingURL=juice-js-observability-trace.page-DE84DHal.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juice-js-observability-trace.page-DE84DHal.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/trace.page.ts"],"sourcesContent":["import { LinkPipe, ObservabilityLinks } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { DatePipe } from '@angular/common';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { Router, RouterLink } from '@angular/router';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Span } from '../core/models';\nimport { DurationPipe, formatDuration, plainTraceId, preciseTime } from '../shared/format';\nimport { ContextBar, KeyValueTable } from '../shared/ui';\nimport { LogTable, problemText } from '../shared/telemetry';\nimport { themeColor } from '../shared/theme';\n\ninterface Row { span: Span; depth: number; children: number; offset: number; selfError: boolean; slowest: boolean; }\n\n/** A stable colour per service, from a small palette that reads in both themes. */\n\n/**\n * A trace as a waterfall (Console requirements §6): the span hierarchy with timing relative to the trace start, so\n * parallel calls show side by side; errors and the slowest spans marked; subtrees collapsible. The selected span is\n * in the URL (`span`), with its details, its logs, and links to its service, its dependency, and the metrics and logs\n * around it (§7).\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n // Fills the window; the waterfall and the span scroll on their own (styles.scss).\n host: { class: 'jc-fill-page' },\n imports: [LinkPipe, RouterLink, DatePipe, MatButtonModule, MatIconModule, MatProgressBarModule, ContextBar, KeyValueTable, LogTable, DurationPipe],\n template: `\n <jc-context-bar>\n <a [routerLink]=\"['traces'] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-crumb\">Traces</a>\n <span class=\"jc-crumb-sep\">›</span>\n <h1 class=\"jc-mono\">{{ id() }}</h1>\n </jc-context-bar>\n @if (trace.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (trace.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n\n @if (trace.value(); as t) {\n <div class=\"jc-summary\">\n <span><strong>{{ root()?.service }}</strong> · {{ root()?.name }}</span>\n <span>{{ start() | date: 'MMM d, HH:mm:ss.SSS' }}</span>\n <span>{{ total() | duration }}</span>\n <span>{{ t.spans.length }} spans · {{ services().length }} services</span>\n @if (errors() > 0) { <span class=\"jc-badge jc-health-unhealthy\">{{ errors() }} failed</span> }\n <button mat-button (click)=\"showErrorsOnly.set(!showErrorsOnly())\">{{ showErrorsOnly() ? 'Show all spans' : 'Error paths only' }}</button>\n <a mat-button [routerLink]=\"['logs'] | jcLink\" [queryParams]=\"ctx.keep({ trace: id() })\">Logs of this trace</a>\n @if (auditLink(); as link) {\n @if ('route' in link) { <a mat-button [routerLink]=\"link.route\" data-testid=\"audit-link\">Audit records ↗</a> }\n @else { <a mat-button [href]=\"link.href\" target=\"_blank\" rel=\"noopener\" data-testid=\"audit-link\">Audit records ↗</a> }\n }\n @if (t.truncated) { <span class=\"jc-muted\">Showing the first {{ t.spans.length }} spans.</span> }\n </div>\n <div class=\"jc-legend\">@for (s of services(); track s) { <span><i [style.background]=\"color(s)\"></i>{{ s }}</span> }</div>\n\n <div class=\"jc-trace\" [class.jc-with-detail]=\"selected()\">\n <div class=\"jc-waterfall\" role=\"tree\">\n <div class=\"jc-wf-head\"><span>Span</span><span class=\"jc-wf-scale\">\n @for (tick of ticks(); track tick.at) { <span [style.left.%]=\"tick.at\">{{ tick.label }}</span> }</span></div>\n @for (r of visible(); track r.span.spanId) {\n <div class=\"jc-wf-row\" role=\"treeitem\" [class.jc-selected]=\"r.span.spanId === selectedId()\"\n [class.jc-wf-error]=\"r.selfError\" (click)=\"select(r.span.spanId)\" tabindex=\"0\" (keydown.enter)=\"select(r.span.spanId)\">\n <span class=\"jc-wf-name\" [style.padding-left.px]=\"r.depth * 14\">\n @if (r.children > 0) {\n <button class=\"jc-wf-toggle\" (click)=\"toggle(r.span.spanId, $event)\" [attr.aria-label]=\"collapsed().has(r.span.spanId) ? 'Expand' : 'Collapse'\">\n {{ collapsed().has(r.span.spanId) ? '▸' : '▾' }}</button>\n } @else { <span class=\"jc-wf-toggle\"></span> }\n <i class=\"jc-dot\" [style.background]=\"color(r.span.service)\"></i>\n <span class=\"jc-wf-service\">{{ r.span.service }}</span> <span>{{ r.span.name }}</span>\n @if (r.selfError) { <mat-icon class=\"jc-wf-icon jc-bad\" title=\"Failed\">error</mat-icon> }\n @if (r.slowest) { <mat-icon class=\"jc-wf-icon\" title=\"Among the slowest spans\">schedule</mat-icon> }\n @if (collapsed().has(r.span.spanId)) { <span class=\"jc-muted jc-small\">+{{ r.children }}</span> }\n </span>\n <span class=\"jc-wf-track\">\n <span class=\"jc-wf-bar\" [style.left.%]=\"r.offset\" [style.width.%]=\"width(r.span)\" [style.background]=\"color(r.span.service)\"></span>\n <span class=\"jc-wf-time\" [style.left.%]=\"r.offset\">{{ r.span.durationMs | duration }}</span>\n </span>\n </div>\n }\n </div>\n\n @if (selected(); as s) {\n <aside class=\"jc-card jc-span-detail\">\n <div class=\"jc-row\"><h2>{{ s.name }}</h2><button mat-icon-button (click)=\"select(null)\" aria-label=\"Close\"><mat-icon>close</mat-icon></button></div>\n <div class=\"jc-span-facts\">\n <span class=\"jc-badge\" [class.jc-health-unhealthy]=\"s.status === 'Error'\" [class.jc-health-healthy]=\"s.status === 'Ok'\">{{ s.status }}</span>\n <span>{{ s.kind }}</span><span>{{ s.durationMs | duration }}</span>\n <span>at +{{ offsetMs(s) | duration }}</span>\n </div>\n @if (s.statusMessage) { <p class=\"jc-error\">{{ s.statusMessage }}</p> }\n\n <nav class=\"jc-actions\">\n @if (s.serviceId) { <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"ctx.keep()\">Service {{ s.service }}</a> }\n @if (s.target; as target) {\n <a mat-stroked-button [routerLink]=\"(target.kind === 'service' ? ['services', target.id] : ['resources', target.id]) | jcLink\" [queryParams]=\"ctx.keep()\">\n {{ target.kind === 'service' ? 'Calls' : 'Uses' }} {{ target.name }}</a>\n }\n @if (s.serviceId) {\n <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"aroundSpan(s, { tab: 'metrics' })\">Metrics around</a>\n <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"aroundSpan(s, { tab: 'logs' })\">Service logs around</a>\n <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"ctx.keep({ tab: 'traces', op: s.name })\">Other traces of {{ s.name }}</a>\n }\n </nav>\n\n <h3>Logs of this span</h3>\n <jc-log-table [query]=\"spanLogQuery()\" [compact]=\"true\" />\n\n <h3>Span</h3>\n <jc-kv [values]=\"{ 'service': s.service, 'environment': s.environment, 'instance': s.instance, 'host': s.host,\n 'trace id': id(), 'span id': s.spanId, 'parent span id': s.parentSpanId, 'started': s.startedAt,\n 'instrumentation scope': s.scope ? s.scope + (s.scopeVersion ? ' ' + s.scopeVersion : '') : null }\" />\n <h3>Attributes</h3>\n <jc-kv [values]=\"s.attributes\" empty=\"No attributes.\" />\n @if (s.events.length) {\n <h3>Events</h3>\n @for (e of s.events; track $index) {\n <div class=\"jc-event\" [class.jc-wf-error]=\"e.name === 'exception'\">\n <strong>{{ e.name }}</strong> <span class=\"jc-muted\">+{{ eventOffset(s, e.at) | duration }}</span>\n <jc-kv [values]=\"e.attributes\" empty=\"\" />\n </div>\n }\n }\n @if (s.links.length) {\n <h3>Links</h3>\n @for (l of s.links; track l.spanId) {\n <a [routerLink]=\"['traces', l.traceId] | jcLink\" [queryParams]=\"ctx.keep({ span: l.spanId })\" class=\"jc-mono\">{{ l.traceId }} / {{ l.spanId }}</a>\n }\n }\n </aside>\n }\n </div>\n }`,\n})\nexport class TracePage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n private readonly links = inject(ObservabilityLinks);\n\n /** A trace id, or a W3C traceparent such as a Juice.Audit record before 10.4 holds; links from either open the trace. */\n readonly traceId = input.required<string>();\n readonly span = input<string>();\n\n protected readonly id = computed(() => plainTraceId(this.traceId()));\n protected readonly trace = apiResource({ params: () => this.id(), stream: ({ params }) => this.api.trace(params) });\n protected readonly error = computed(() => problemText(this.trace.error()));\n /** The host's audit records of this trace (`auditLink`): the requests it served and the data they changed. */\n protected readonly auditLink = computed(() => this.links.audit(this.id()));\n protected readonly collapsed = signal(new Set<string>());\n protected readonly showErrorsOnly = signal(false);\n protected readonly selectedId = computed(() => this.span() ?? null);\n\n private readonly spans = computed(() => this.trace.value()?.spans ?? []);\n // Sub-millisecond precision: spans of a fast in-process flow start microseconds apart.\n protected readonly start = computed(() => Math.min(...this.spans().map(s => preciseTime(s.startedAt))));\n protected readonly total = computed(() =>\n Math.max(0.001, Math.max(...this.spans().map(s => preciseTime(s.startedAt) + s.durationMs)) - this.start()));\n protected readonly root = computed(() => this.spans().find(s => !s.parentSpanId) ?? this.spans()[0]);\n protected readonly services = computed(() => [...new Set(this.spans().map(s => s.service))]);\n protected readonly errors = computed(() => this.spans().filter(s => s.status === 'Error').length);\n protected readonly selected = computed(() => this.spans().find(s => s.spanId === this.selectedId()) ?? null);\n protected readonly spanLogQuery = computed(() => {\n const s = this.selected();\n return s ? { spanId: s.spanId, ...this.windowAround(s) } : {};\n });\n\n /** Depth-first order with depth; spans whose parent is missing are roots. Children start-ordered. */\n private readonly rows = computed<Row[]>(() => {\n const spans = this.spans();\n const ids = new Set(spans.map(s => s.spanId));\n const byParent = new Map<string | null, Span[]>();\n for (const s of spans) {\n const parent = s.parentSpanId && ids.has(s.parentSpanId) ? s.parentSpanId : null;\n byParent.set(parent, [...(byParent.get(parent) ?? []), s]);\n }\n const slowest = new Set([...spans].sort((a, b) => b.durationMs - a.durationMs).slice(0, Math.max(1, Math.ceil(spans.length * 0.05))).map(s => s.spanId));\n const rows: Row[] = [];\n const walk = (parent: string | null, depth: number) => {\n for (const s of (byParent.get(parent) ?? []).sort((a, b) => a.startedAt.localeCompare(b.startedAt))) {\n rows.push({\n span: s, depth, children: byParent.get(s.spanId)?.length ?? 0,\n offset: 100 * (preciseTime(s.startedAt) - this.start()) / this.total(),\n selfError: s.status === 'Error', slowest: slowest.has(s.spanId) && spans.length > 3,\n });\n walk(s.spanId, depth + 1);\n }\n };\n walk(null, 0);\n return rows;\n });\n\n /** Hides collapsed subtrees; \"error paths only\" keeps failed spans and their ancestors. */\n protected readonly visible = computed(() => {\n const rows = this.rows();\n const parentOf = new Map(this.spans().map(s => [s.spanId, s.parentSpanId]));\n let keep: Set<string> | null = null;\n if (this.showErrorsOnly()) {\n keep = new Set<string>();\n for (const r of rows.filter(r => r.selfError)) {\n for (let id: string | null | undefined = r.span.spanId; id; id = parentOf.get(id)) keep.add(id);\n }\n }\n const out: Row[] = [];\n let hiddenBelow: number | null = null;\n for (const r of rows) {\n if (hiddenBelow !== null && r.depth > hiddenBelow) continue;\n hiddenBelow = null;\n if (keep && !keep.has(r.span.spanId)) continue;\n out.push(r);\n if (this.collapsed().has(r.span.spanId)) hiddenBelow = r.depth;\n }\n return out;\n });\n\n protected readonly ticks = computed(() => [0, 25, 50, 75].map(at => ({ at, label: formatDuration(this.total() * at / 100) })));\n\n /** The app's categorical colours (observability.colors $series), as CSS variables: a theme switch repaints them. */\n private readonly seriesCount = Math.max(1, Number(themeColor('series-count')) || 1);\n\n color(service: string) {\n return `var(--jc-series-${this.services().indexOf(service) % this.seriesCount + 1})`;\n }\n\n width(span: Span) {\n return Math.max(0.3, 100 * span.durationMs / this.total());\n }\n\n offsetMs(span: Span) {\n return preciseTime(span.startedAt) - this.start();\n }\n\n eventOffset(span: Span, at: string) {\n return preciseTime(at) - preciseTime(span.startedAt);\n }\n\n toggle(spanId: string, event: Event) {\n event.stopPropagation();\n const next = new Set(this.collapsed());\n if (!next.delete(spanId)) next.add(spanId);\n this.collapsed.set(next);\n }\n\n select(spanId: string | null) {\n void this.router.navigate([], { queryParams: { span: spanId }, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n private windowAround(span: Span) {\n const start = new Date(span.startedAt).getTime();\n return { from: new Date(start - 60_000).toISOString(), to: new Date(start + span.durationMs + 60_000).toISOString() };\n }\n\n /** The service page over a window around this span, so its metrics and logs line up with it. */\n aroundSpan(span: Span, extra: Record<string, string>) {\n return { ...this.ctx.around(span.startedAt, span.durationMs), ...extra };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiService, ContextService } from "./juice-js-observability.mjs";
|
|
2
|
-
import { i as TraceTable, l as ContextBar, x as apiResource } from "./juice-js-observability-telemetry-
|
|
2
|
+
import { i as TraceTable, l as ContextBar, x as apiResource } from "./juice-js-observability-telemetry-gYN3UogH.mjs";
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
import { ChangeDetectionStrategy, Component, computed, inject, input } from "@angular/core";
|
|
5
5
|
import { map } from "rxjs";
|
|
@@ -517,4 +517,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
517
517
|
});
|
|
518
518
|
export { TracesPage };
|
|
519
519
|
|
|
520
|
-
//# sourceMappingURL=juice-js-observability-traces.page-
|
|
520
|
+
//# sourceMappingURL=juice-js-observability-traces.page-C-i5ZJqJ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"juice-js-observability-traces.page-B1vYmz_K.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/traces.page.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { Router } from '@angular/router';\nimport { map } from 'rxjs';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { ContextBar } from '../shared/ui';\nimport { TraceTable } from '../shared/telemetry';\n\n/**\n * Trace search (Console requirements §5): narrow from a service and time window to one trace by operation, status,\n * duration, instance, resource, dependency, trace or span id, and span attributes such as `http.route=/orders` or\n * `messaging.destination.name=orders`. Every filter is a URL parameter, so a search is a shareable link.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FormsModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatSelectModule, ContextBar, TraceTable],\n template: `\n <jc-context-bar><h1>Traces</h1></jc-context-bar>\n <form class=\"jc-filter-form\" (ngSubmit)=\"apply(f.value)\" #f=\"ngForm\">\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-label>Service</mat-label>\n <mat-select name=\"service\" [ngModel]=\"service() ?? ''\">\n <mat-option value=\"\">Any</mat-option>\n @for (s of services.value() ?? []; track s.id) { <mat-option [value]=\"s.id\">{{ s.key }}</mat-option> }\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Operation</mat-label><input matInput name=\"op\" [ngModel]=\"op() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\">\n <mat-label>Status</mat-label>\n <mat-select name=\"status\" [ngModel]=\"status() ?? ''\">\n <mat-option value=\"\">Any</mat-option><mat-option value=\"error\">Error</mat-option><mat-option value=\"ok\">No error</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-md\"><mat-label>Min duration (ms)</mat-label><input matInput type=\"number\" min=\"0\" name=\"min\" [ngModel]=\"min() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-md\"><mat-label>Max duration (ms)</mat-label><input matInput type=\"number\" min=\"0\" name=\"max\" [ngModel]=\"max() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Trace ID</mat-label><input matInput name=\"trace\" [ngModel]=\"trace() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Span ID</mat-label><input matInput name=\"spanId\" [ngModel]=\"spanId() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\">\n <mat-label>Attributes</mat-label>\n <input matInput name=\"attr\" [ngModel]=\"attributes().join(', ')\" placeholder=\"http.route=/orders, messaging.destination.name=orders\">\n </mat-form-field>\n <button mat-flat-button type=\"submit\">Search</button>\n <button mat-button type=\"button\" (click)=\"clear()\">Clear</button>\n </form>\n @if (instance() || resource() || dependency()) {\n <p class=\"jc-muted\">Also filtered by {{ scopeText() }}.\n <a href=\"\" (click)=\"$event.preventDefault(); apply({ instance: null, resource: null, dependency: null })\">Remove</a></p>\n }\n <jc-trace-table [query]=\"query()\" [limit]=\"100\" />`,\n})\nexport class TracesPage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n\n readonly service = input<string>();\n readonly op = input<string>();\n readonly status = input<string>();\n readonly min = input<string>();\n readonly max = input<string>();\n readonly trace = input<string>();\n readonly spanId = input<string>();\n readonly attr = input<string | string[]>();\n readonly instance = input<string>();\n readonly resource = input<string>();\n readonly dependency = input<string>();\n\n protected readonly services = apiResource({ stream: () => this.api.resources('service').pipe(map(p => p.items)) });\n protected readonly scopeText = computed(() =>\n [this.instance() && 'an instance', this.resource() && 'a resource', this.dependency() && 'a dependency'].filter(x => !!x).join(', '));\n protected readonly attributes = computed(() => {\n const a = this.attr();\n return a === undefined ? [] : Array.isArray(a) ? a : [a];\n });\n protected readonly query = computed(() => ({\n serviceId: this.service(), operation: this.op(), status: this.status(), minDurationMs: this.min(), maxDurationMs: this.max(),\n traceId: this.trace(), spanId: this.spanId(), attribute: this.attributes(), instanceId: this.instance(),\n resourceId: this.resource(), dependencyId: this.dependency(),\n }));\n\n apply(value: Record<string, unknown>) {\n const queryParams: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(value)) queryParams[key] = v === '' || v === undefined ? null : v;\n if (typeof value['attr'] === 'string') {\n queryParams['attr'] = (value['attr'] as string).split(',').map(a => a.trim()).filter(Boolean);\n }\n void this.router.navigate([], { queryParams, queryParamsHandling: 'merge' });\n }\n\n clear() {\n void this.router.navigate([], { queryParams: this.ctx.keep() });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"juice-js-observability-traces.page-C-i5ZJqJ.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/traces.page.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { Router } from '@angular/router';\nimport { map } from 'rxjs';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { ContextBar } from '../shared/ui';\nimport { TraceTable } from '../shared/telemetry';\n\n/**\n * Trace search (Console requirements §5): narrow from a service and time window to one trace by operation, status,\n * duration, instance, resource, dependency, trace or span id, and span attributes such as `http.route=/orders` or\n * `messaging.destination.name=orders`. Every filter is a URL parameter, so a search is a shareable link.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FormsModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatSelectModule, ContextBar, TraceTable],\n template: `\n <jc-context-bar><h1>Traces</h1></jc-context-bar>\n <form class=\"jc-filter-form\" (ngSubmit)=\"apply(f.value)\" #f=\"ngForm\">\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-label>Service</mat-label>\n <mat-select name=\"service\" [ngModel]=\"service() ?? ''\">\n <mat-option value=\"\">Any</mat-option>\n @for (s of services.value() ?? []; track s.id) { <mat-option [value]=\"s.id\">{{ s.key }}</mat-option> }\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Operation</mat-label><input matInput name=\"op\" [ngModel]=\"op() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\">\n <mat-label>Status</mat-label>\n <mat-select name=\"status\" [ngModel]=\"status() ?? ''\">\n <mat-option value=\"\">Any</mat-option><mat-option value=\"error\">Error</mat-option><mat-option value=\"ok\">No error</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-md\"><mat-label>Min duration (ms)</mat-label><input matInput type=\"number\" min=\"0\" name=\"min\" [ngModel]=\"min() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-md\"><mat-label>Max duration (ms)</mat-label><input matInput type=\"number\" min=\"0\" name=\"max\" [ngModel]=\"max() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Trace ID</mat-label><input matInput name=\"trace\" [ngModel]=\"trace() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Span ID</mat-label><input matInput name=\"spanId\" [ngModel]=\"spanId() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\">\n <mat-label>Attributes</mat-label>\n <input matInput name=\"attr\" [ngModel]=\"attributes().join(', ')\" placeholder=\"http.route=/orders, messaging.destination.name=orders\">\n </mat-form-field>\n <button mat-flat-button type=\"submit\">Search</button>\n <button mat-button type=\"button\" (click)=\"clear()\">Clear</button>\n </form>\n @if (instance() || resource() || dependency()) {\n <p class=\"jc-muted\">Also filtered by {{ scopeText() }}.\n <a href=\"\" (click)=\"$event.preventDefault(); apply({ instance: null, resource: null, dependency: null })\">Remove</a></p>\n }\n <jc-trace-table [query]=\"query()\" [limit]=\"100\" />`,\n})\nexport class TracesPage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n\n readonly service = input<string>();\n readonly op = input<string>();\n readonly status = input<string>();\n readonly min = input<string>();\n readonly max = input<string>();\n readonly trace = input<string>();\n readonly spanId = input<string>();\n readonly attr = input<string | string[]>();\n readonly instance = input<string>();\n readonly resource = input<string>();\n readonly dependency = input<string>();\n\n protected readonly services = apiResource({ stream: () => this.api.resources('service').pipe(map(p => p.items)) });\n protected readonly scopeText = computed(() =>\n [this.instance() && 'an instance', this.resource() && 'a resource', this.dependency() && 'a dependency'].filter(x => !!x).join(', '));\n protected readonly attributes = computed(() => {\n const a = this.attr();\n return a === undefined ? [] : Array.isArray(a) ? a : [a];\n });\n protected readonly query = computed(() => ({\n serviceId: this.service(), operation: this.op(), status: this.status(), minDurationMs: this.min(), maxDurationMs: this.max(),\n traceId: this.trace(), spanId: this.spanId(), attribute: this.attributes(), instanceId: this.instance(),\n resourceId: this.resource(), dependencyId: this.dependency(),\n }));\n\n apply(value: Record<string, unknown>) {\n const queryParams: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(value)) queryParams[key] = v === '' || v === undefined ? null : v;\n if (typeof value['attr'] === 'string') {\n queryParams['attr'] = (value['attr'] as string).split(',').map(a => a.trim()).filter(Boolean);\n }\n void this.router.navigate([], { queryParams, queryParamsHandling: 'merge' });\n }\n\n clear() {\n void this.router.navigate([], { queryParams: this.ctx.keep() });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAwDA,IAAa,aAAb,MAAa,WAAU;CACF,MAAM,OAAO,cAAc;CAC7B,MAAM,OAAO,UAAU;CACvB,SAAS,OAAO,MAAM;CAE9B,UAAU,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACzB,KAAK,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,KAAA,CAAA,+BAAA,CAAA,CAAA;CACpB,SAAS,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CACxB,MAAM,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,MAAA,CAAA,+BAAA,CAAA,CAAA;CACrB,MAAM,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,MAAA,CAAA,+BAAA,CAAA,CAAA;CACrB,QAAQ,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CACvB,SAAS,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CACxB,OAAO,MAA0B,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CACjC,WAAW,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAC1B,WAAW,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAC1B,aAAa,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAElB,WAAW,YAAY,EAAE,cAAc,KAAK,IAAI,UAAU,SAAS,CAAC,CAAC,KAAK,KAAI,MAAK,EAAE,KAAK,CAAC,EAAC,CAAE;CAC9F,YAAY,eAC7B;EAAC,KAAK,SAAQ,KAAM;EAAe,KAAK,SAAQ,KAAM;EAAc,KAAK,WAAU,KAAM;CAAc,CAAC,CAAC,QAAO,MAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CACpH,aAAa,eAAc;EAC5C,MAAM,IAAI,KAAK,KAAI;EACnB,OAAO,MAAM,SAAY,CAAA,IAAK,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC;CACzD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CACiB,QAAQ,gBAAgB;EACzC,WAAW,KAAK,QAAO;EAAI,WAAW,KAAK,GAAE;EAAI,QAAQ,KAAK,OAAM;EAAI,eAAe,KAAK,IAAG;EAAI,eAAe,KAAK,IAAG;EAC1H,SAAS,KAAK,MAAK;EAAI,QAAQ,KAAK,OAAM;EAAI,WAAW,KAAK,WAAU;EAAI,YAAY,KAAK,SAAQ;EACrG,YAAY,KAAK,SAAQ;EAAI,cAAc,KAAK,WAAU;CAC3D,IAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEH,MAAM,OAA8B;EAClC,MAAM,cAAuC,CAAA;EAC7C,KAAK,MAAM,CAAC,KAAK,MAAM,OAAO,QAAQ,KAAK,GAAG,YAAY,OAAO,MAAM,MAAM,MAAM,SAAY,OAAO;EACtG,IAAI,OAAO,MAAM,YAAY,UAC3B,YAAY,UAAW,MAAM,OAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,KAAI,MAAK,EAAE,KAAI,CAAE,CAAC,CAAC,OAAO,OAAO;EAE9F,AAAK,KAAK,OAAO,SAAS,CAAA,GAAI;GAAE;GAAa,qBAAqB;EAAO,CAAE;CAC7E;CAEA,QAAK;EACH,AAAK,KAAK,OAAO,SAAS,CAAA,GAAI,EAAE,aAAa,KAAK,IAAI,KAAI,EAAE,CAAE;CAChE;CAzCW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAU,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAV,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAU,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,IAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,KAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,KAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,UAAA;EAAA,UAlCX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgC2C,UAAA;EAAA,cAAA;GAAA;IAAA,MAAA;IAAA,MAjC3C;GAAW;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA,CAAA,KAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,eAAA;IAAA,UAAA,CAAA,SAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA,CAAA,eAAA;IAAA,SAAA,CAAA,UAAA;IAAA,UAAA,CAAA,QAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;GAAe;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA,CAAA,WAAA;IAAA,UAAA,CAAA,aAAA,WAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;GAAkB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,UAAA,CAAA,cAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;GAAc;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,UAAA,CAAA,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;GAAe;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,UAAA,CAAA,WAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,mBAAA;IAAA,UAAA,CAAA,WAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAU,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAU,UAAA;IAAA,QAAA,CAAA,SAAA,OAAA;GAAA;EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAmCxG,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAU,YAAA,CAAA;EArCtB,MAAA;EAAU,MAAA,CAAA;GACT,iBAAiB,wBAAwB;GACzC,SAAS;IAAC;IAAa;IAAiB;IAAoB;IAAgB;IAAiB;IAAY;GAAU;GACnH,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCX,CAAA"}
|