@juice-js/observability 0.0.0-stage → 1.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +60 -2
  2. package/fesm2022/juice-js-observability-alert.page-BHKAUvR-.mjs +404 -0
  3. package/fesm2022/juice-js-observability-alert.page-BHKAUvR-.mjs.map +1 -0
  4. package/fesm2022/juice-js-observability-alerts.page-C2tXzLQl.mjs +208 -0
  5. package/fesm2022/juice-js-observability-alerts.page-C2tXzLQl.mjs.map +1 -0
  6. package/fesm2022/juice-js-observability-logs.page--j6W1Ej-.mjs +413 -0
  7. package/fesm2022/juice-js-observability-logs.page--j6W1Ej-.mjs.map +1 -0
  8. package/fesm2022/juice-js-observability-metrics.page-DR6qZtfl.mjs +267 -0
  9. package/fesm2022/juice-js-observability-metrics.page-DR6qZtfl.mjs.map +1 -0
  10. package/fesm2022/juice-js-observability-overview.page-DU0GxSXQ.mjs +243 -0
  11. package/fesm2022/juice-js-observability-overview.page-DU0GxSXQ.mjs.map +1 -0
  12. package/fesm2022/juice-js-observability-resource.page-D6qpvo_f.mjs +274 -0
  13. package/fesm2022/juice-js-observability-resource.page-D6qpvo_f.mjs.map +1 -0
  14. package/fesm2022/juice-js-observability-resources.page-CR7-DFsh.mjs +209 -0
  15. package/fesm2022/juice-js-observability-resources.page-CR7-DFsh.mjs.map +1 -0
  16. package/fesm2022/juice-js-observability-rules.page-fq-wO5I_.mjs +175 -0
  17. package/fesm2022/juice-js-observability-rules.page-fq-wO5I_.mjs.map +1 -0
  18. package/fesm2022/juice-js-observability-service.page-Qc6FYChB.mjs +726 -0
  19. package/fesm2022/juice-js-observability-service.page-Qc6FYChB.mjs.map +1 -0
  20. package/fesm2022/juice-js-observability-services.page-CX6S7sUu.mjs +251 -0
  21. package/fesm2022/juice-js-observability-services.page-CX6S7sUu.mjs.map +1 -0
  22. package/fesm2022/juice-js-observability-telemetry-D9EYxkdr.mjs +1783 -0
  23. package/fesm2022/juice-js-observability-telemetry-D9EYxkdr.mjs.map +1 -0
  24. package/fesm2022/juice-js-observability-topology.page-CLz7J5pq.mjs +483 -0
  25. package/fesm2022/juice-js-observability-topology.page-CLz7J5pq.mjs.map +1 -0
  26. package/fesm2022/juice-js-observability-trace.page-DccvH1SV.mjs +518 -0
  27. package/fesm2022/juice-js-observability-trace.page-DccvH1SV.mjs.map +1 -0
  28. package/fesm2022/juice-js-observability-traces.page-BZxnac4Y.mjs +520 -0
  29. package/fesm2022/juice-js-observability-traces.page-BZxnac4Y.mjs.map +1 -0
  30. package/fesm2022/juice-js-observability.mjs +396 -0
  31. package/fesm2022/juice-js-observability.mjs.map +1 -0
  32. package/package.json +48 -4
  33. package/styles/_observability.scss +216 -0
  34. package/types/juice-js-observability.d.ts +387 -0
@@ -0,0 +1,1783 @@
1
+ import { ApiService, ContextService, LinkPipe, MAX_SEARCH_MS, ObservabilityLinks, RANGES } from "./juice-js-observability.mjs";
2
+ import * as i0 from "@angular/core";
3
+ import { ChangeDetectionStrategy, Component, DestroyRef, Pipe, afterNextRender, computed, effect, inject, input, isSignal, linkedSignal, signal, viewChild } from "@angular/core";
4
+ import { catchError, map, of } from "rxjs";
5
+ import { rxResource, takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
6
+ import { Router, RouterLink } from "@angular/router";
7
+ import { DatePipe } from "@angular/common";
8
+ import * as i1$1 from "@angular/forms";
9
+ import { FormsModule } from "@angular/forms";
10
+ import * as i2 from "@angular/material/button";
11
+ import { MatButtonModule } from "@angular/material/button";
12
+ import * as i1 from "@angular/material/progress-bar";
13
+ import { MatProgressBarModule } from "@angular/material/progress-bar";
14
+ import * as i3 from "@angular/material/icon";
15
+ import { MatIconModule } from "@angular/material/icon";
16
+ import * as i2$1 from "@angular/material/select";
17
+ import { MatSelectModule } from "@angular/material/select";
18
+ function apiResource(options) {
19
+ const ref = rxResource(options);
20
+ const value = computed(() => ref.hasValue() ? ref.value() : void 0, ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
21
+ return new Proxy(ref, { get(target, prop) {
22
+ if (prop === "value") return value;
23
+ const member = Reflect.get(target, prop, target);
24
+ return typeof member === "function" && !isSignal(member) ? member.bind(target) : member;
25
+ } });
26
+ }
27
+ function preciseTime(iso) {
28
+ const fraction = /\.\d{3}(\d+)/.exec(iso)?.[1];
29
+ return Date.parse(iso) + (fraction ? Number(`0.${fraction}`) : 0);
30
+ }
31
+ function plainTraceId(text) {
32
+ const lower = text.trim().toLowerCase();
33
+ return /^[0-9a-f]{2}-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/.exec(lower)?.[1] ?? lower;
34
+ }
35
+ function formatDuration(ms) {
36
+ if (ms === null || ms === void 0 || Number.isNaN(ms)) return "—";
37
+ if (ms < 1) return `${ms.toFixed(2)} ms`;
38
+ if (ms < 1e3) return `${Math.round(ms * 10) / 10} ms`;
39
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(2)} s`;
40
+ const s = Math.round(ms / 1e3);
41
+ return `${Math.floor(s / 60)} m ${String(s % 60).padStart(2, "0")} s`;
42
+ }
43
+ function formatPercent(rate) {
44
+ if (rate === null || rate === void 0) return "—";
45
+ const p = rate * 100;
46
+ return `${p === 0 ? "0" : p < .1 ? "<0.1" : p.toFixed(p < 10 ? 1 : 0)} %`;
47
+ }
48
+ function formatAgo(iso) {
49
+ if (!iso) return "—";
50
+ const s = Math.round((Date.now() - new Date(iso).getTime()) / 1e3);
51
+ if (s < 60) return `${Math.max(s, 0)} s ago`;
52
+ if (s < 3600) return `${Math.round(s / 60)} min ago`;
53
+ if (s < 86400) return `${Math.round(s / 3600)} h ago`;
54
+ return `${Math.round(s / 86400)} d ago`;
55
+ }
56
+ var DurationPipe = class DurationPipe {
57
+ transform(ms) {
58
+ return formatDuration(ms);
59
+ }
60
+ static ɵfac = i0.ɵɵngDeclareFactory({
61
+ minVersion: "12.0.0",
62
+ version: "22.2.4",
63
+ ngImport: i0,
64
+ type: DurationPipe,
65
+ deps: [],
66
+ target: i0.ɵɵFactoryTarget.Pipe
67
+ });
68
+ static ɵpipe = i0.ɵɵngDeclarePipe({
69
+ minVersion: "14.0.0",
70
+ version: "22.2.4",
71
+ ngImport: i0,
72
+ type: DurationPipe,
73
+ isStandalone: true,
74
+ name: "duration"
75
+ });
76
+ };
77
+ i0.ɵɵngDeclareClassMetadata({
78
+ minVersion: "12.0.0",
79
+ version: "22.2.4",
80
+ ngImport: i0,
81
+ type: DurationPipe,
82
+ decorators: [{
83
+ type: Pipe,
84
+ args: [{ name: "duration" }]
85
+ }]
86
+ });
87
+ var PercentPipe = class PercentPipe {
88
+ transform(rate) {
89
+ return formatPercent(rate);
90
+ }
91
+ static ɵfac = i0.ɵɵngDeclareFactory({
92
+ minVersion: "12.0.0",
93
+ version: "22.2.4",
94
+ ngImport: i0,
95
+ type: PercentPipe,
96
+ deps: [],
97
+ target: i0.ɵɵFactoryTarget.Pipe
98
+ });
99
+ static ɵpipe = i0.ɵɵngDeclarePipe({
100
+ minVersion: "14.0.0",
101
+ version: "22.2.4",
102
+ ngImport: i0,
103
+ type: PercentPipe,
104
+ isStandalone: true,
105
+ name: "percent2"
106
+ });
107
+ };
108
+ i0.ɵɵngDeclareClassMetadata({
109
+ minVersion: "12.0.0",
110
+ version: "22.2.4",
111
+ ngImport: i0,
112
+ type: PercentPipe,
113
+ decorators: [{
114
+ type: Pipe,
115
+ args: [{ name: "percent2" }]
116
+ }]
117
+ });
118
+ var AgoPipe = class AgoPipe {
119
+ transform(iso) {
120
+ return formatAgo(iso);
121
+ }
122
+ static ɵfac = i0.ɵɵngDeclareFactory({
123
+ minVersion: "12.0.0",
124
+ version: "22.2.4",
125
+ ngImport: i0,
126
+ type: AgoPipe,
127
+ deps: [],
128
+ target: i0.ɵɵFactoryTarget.Pipe
129
+ });
130
+ static ɵpipe = i0.ɵɵngDeclarePipe({
131
+ minVersion: "14.0.0",
132
+ version: "22.2.4",
133
+ ngImport: i0,
134
+ type: AgoPipe,
135
+ isStandalone: true,
136
+ name: "ago"
137
+ });
138
+ };
139
+ i0.ɵɵngDeclareClassMetadata({
140
+ minVersion: "12.0.0",
141
+ version: "22.2.4",
142
+ ngImport: i0,
143
+ type: AgoPipe,
144
+ decorators: [{
145
+ type: Pipe,
146
+ args: [{ name: "ago" }]
147
+ }]
148
+ });
149
+ var ReasonPipe = class ReasonPipe {
150
+ transform(code) {
151
+ const [what, subject] = code.split(/:(.*)/s);
152
+ const text = what.replace(/_/g, " ");
153
+ return subject ? `${text}: ${subject}` : text;
154
+ }
155
+ static ɵfac = i0.ɵɵngDeclareFactory({
156
+ minVersion: "12.0.0",
157
+ version: "22.2.4",
158
+ ngImport: i0,
159
+ type: ReasonPipe,
160
+ deps: [],
161
+ target: i0.ɵɵFactoryTarget.Pipe
162
+ });
163
+ static ɵpipe = i0.ɵɵngDeclarePipe({
164
+ minVersion: "14.0.0",
165
+ version: "22.2.4",
166
+ ngImport: i0,
167
+ type: ReasonPipe,
168
+ isStandalone: true,
169
+ name: "reason"
170
+ });
171
+ };
172
+ i0.ɵɵngDeclareClassMetadata({
173
+ minVersion: "12.0.0",
174
+ version: "22.2.4",
175
+ ngImport: i0,
176
+ type: ReasonPipe,
177
+ decorators: [{
178
+ type: Pipe,
179
+ args: [{ name: "reason" }]
180
+ }]
181
+ });
182
+ function themeColor(name, element = document.body) {
183
+ return getComputedStyle(element).getPropertyValue(`--jc-${name}`).trim();
184
+ }
185
+ function seriesColors(element = document.body) {
186
+ const count = Number(themeColor("series-count", element)) || 0;
187
+ return Array.from({ length: count }, (_, i) => themeColor(`series-${i + 1}`, element)).filter(Boolean);
188
+ }
189
+ function onThemeChange(callback) {
190
+ const observer = new MutationObserver(callback);
191
+ for (const element of [document.documentElement, document.body]) observer.observe(element, {
192
+ attributes: true,
193
+ attributeFilter: ["class"]
194
+ });
195
+ return () => observer.disconnect();
196
+ }
197
+ var HealthBadge = class HealthBadge {
198
+ state = input(...ngDevMode ? [void 0, { debugName: "state" }] : /* istanbul ignore next */ []);
199
+ stale = input(false, ...ngDevMode ? [{ debugName: "stale" }] : /* istanbul ignore next */ []);
200
+ static ɵfac = i0.ɵɵngDeclareFactory({
201
+ minVersion: "12.0.0",
202
+ version: "22.2.4",
203
+ ngImport: i0,
204
+ type: HealthBadge,
205
+ deps: [],
206
+ target: i0.ɵɵFactoryTarget.Component
207
+ });
208
+ static ɵcmp = i0.ɵɵngDeclareComponent({
209
+ minVersion: "17.0.0",
210
+ version: "22.2.4",
211
+ type: HealthBadge,
212
+ isStandalone: true,
213
+ selector: "jc-health",
214
+ inputs: {
215
+ state: {
216
+ classPropertyName: "state",
217
+ publicName: "state",
218
+ isSignal: true,
219
+ isRequired: false,
220
+ transformFunction: null
221
+ },
222
+ stale: {
223
+ classPropertyName: "stale",
224
+ publicName: "stale",
225
+ isSignal: true,
226
+ isRequired: false,
227
+ transformFunction: null
228
+ }
229
+ },
230
+ ngImport: i0,
231
+ template: `<span class="jc-badge" [class]="'jc-health-' + (state() ?? 'none').toLowerCase()"
232
+ [attr.title]="stale() ? 'Evaluation has stopped: this state may be out of date' : null">
233
+ {{ state() ?? 'No data' }}@if (stale()) {<span class="jc-stale"> · stale</span>}</span>`,
234
+ isInline: true,
235
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
236
+ });
237
+ };
238
+ i0.ɵɵngDeclareClassMetadata({
239
+ minVersion: "12.0.0",
240
+ version: "22.2.4",
241
+ ngImport: i0,
242
+ type: HealthBadge,
243
+ decorators: [{
244
+ type: Component,
245
+ args: [{
246
+ selector: "jc-health",
247
+ changeDetection: ChangeDetectionStrategy.OnPush,
248
+ template: `<span class="jc-badge" [class]="'jc-health-' + (state() ?? 'none').toLowerCase()"
249
+ [attr.title]="stale() ? 'Evaluation has stopped: this state may be out of date' : null">
250
+ {{ state() ?? 'No data' }}@if (stale()) {<span class="jc-stale"> · stale</span>}</span>`
251
+ }]
252
+ }],
253
+ propDecorators: {
254
+ state: [{
255
+ type: i0.Input,
256
+ args: [{
257
+ isSignal: true,
258
+ alias: "state",
259
+ required: false
260
+ }]
261
+ }],
262
+ stale: [{
263
+ type: i0.Input,
264
+ args: [{
265
+ isSignal: true,
266
+ alias: "stale",
267
+ required: false
268
+ }]
269
+ }]
270
+ }
271
+ });
272
+ var ConfidenceBadge = class ConfidenceBadge {
273
+ confidence = input.required(...ngDevMode ? [{ debugName: "confidence" }] : /* istanbul ignore next */ []);
274
+ observed = computed(() => this.confidence().toLowerCase() === "observed", ...ngDevMode ? [{ debugName: "observed" }] : /* istanbul ignore next */ []);
275
+ static ɵfac = i0.ɵɵngDeclareFactory({
276
+ minVersion: "12.0.0",
277
+ version: "22.2.4",
278
+ ngImport: i0,
279
+ type: ConfidenceBadge,
280
+ deps: [],
281
+ target: i0.ɵɵFactoryTarget.Component
282
+ });
283
+ static ɵcmp = i0.ɵɵngDeclareComponent({
284
+ minVersion: "17.1.0",
285
+ version: "22.2.4",
286
+ type: ConfidenceBadge,
287
+ isStandalone: true,
288
+ selector: "jc-confidence",
289
+ inputs: { confidence: {
290
+ classPropertyName: "confidence",
291
+ publicName: "confidence",
292
+ isSignal: true,
293
+ isRequired: true,
294
+ transformFunction: null
295
+ } },
296
+ ngImport: i0,
297
+ template: `<span class="jc-badge" [class.jc-observed]="observed()" [class.jc-inferred]="!observed()"
298
+ [attr.title]="observed() ? 'Seen in traces: a call from one service to the other' : 'Inferred from span attributes, not a confirmed call'">
299
+ {{ observed() ? 'observed' : 'inferred' }}</span>`,
300
+ isInline: true,
301
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
302
+ });
303
+ };
304
+ i0.ɵɵngDeclareClassMetadata({
305
+ minVersion: "12.0.0",
306
+ version: "22.2.4",
307
+ ngImport: i0,
308
+ type: ConfidenceBadge,
309
+ decorators: [{
310
+ type: Component,
311
+ args: [{
312
+ selector: "jc-confidence",
313
+ changeDetection: ChangeDetectionStrategy.OnPush,
314
+ template: `<span class="jc-badge" [class.jc-observed]="observed()" [class.jc-inferred]="!observed()"
315
+ [attr.title]="observed() ? 'Seen in traces: a call from one service to the other' : 'Inferred from span attributes, not a confirmed call'">
316
+ {{ observed() ? 'observed' : 'inferred' }}</span>`
317
+ }]
318
+ }],
319
+ propDecorators: { confidence: [{
320
+ type: i0.Input,
321
+ args: [{
322
+ isSignal: true,
323
+ alias: "confidence",
324
+ required: true
325
+ }]
326
+ }] }
327
+ });
328
+ var KeyValueTable = class KeyValueTable {
329
+ values = input({}, ...ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []);
330
+ empty = input("None", ...ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []);
331
+ entries = computed(() => Object.entries(this.values()).filter((e) => e[1] !== null && e[1] !== void 0 && e[1] !== "").sort((a, b) => a[0].localeCompare(b[0])), ...ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []);
332
+ static ɵfac = i0.ɵɵngDeclareFactory({
333
+ minVersion: "12.0.0",
334
+ version: "22.2.4",
335
+ ngImport: i0,
336
+ type: KeyValueTable,
337
+ deps: [],
338
+ target: i0.ɵɵFactoryTarget.Component
339
+ });
340
+ static ɵcmp = i0.ɵɵngDeclareComponent({
341
+ minVersion: "17.0.0",
342
+ version: "22.2.4",
343
+ type: KeyValueTable,
344
+ isStandalone: true,
345
+ selector: "jc-kv",
346
+ inputs: {
347
+ values: {
348
+ classPropertyName: "values",
349
+ publicName: "values",
350
+ isSignal: true,
351
+ isRequired: false,
352
+ transformFunction: null
353
+ },
354
+ empty: {
355
+ classPropertyName: "empty",
356
+ publicName: "empty",
357
+ isSignal: true,
358
+ isRequired: false,
359
+ transformFunction: null
360
+ }
361
+ },
362
+ ngImport: i0,
363
+ template: `
364
+ @if (entries().length === 0) { <p class="jc-muted">{{ empty() }}</p> }
365
+ @else {
366
+ <table class="jc-kv"><tbody>
367
+ @for (e of entries(); track e[0]) { <tr><th>{{ e[0] }}</th><td>{{ e[1] }}</td></tr> }
368
+ </tbody></table>
369
+ }`,
370
+ isInline: true,
371
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
372
+ });
373
+ };
374
+ i0.ɵɵngDeclareClassMetadata({
375
+ minVersion: "12.0.0",
376
+ version: "22.2.4",
377
+ ngImport: i0,
378
+ type: KeyValueTable,
379
+ decorators: [{
380
+ type: Component,
381
+ args: [{
382
+ selector: "jc-kv",
383
+ changeDetection: ChangeDetectionStrategy.OnPush,
384
+ template: `
385
+ @if (entries().length === 0) { <p class="jc-muted">{{ empty() }}</p> }
386
+ @else {
387
+ <table class="jc-kv"><tbody>
388
+ @for (e of entries(); track e[0]) { <tr><th>{{ e[0] }}</th><td>{{ e[1] }}</td></tr> }
389
+ </tbody></table>
390
+ }`
391
+ }]
392
+ }],
393
+ propDecorators: {
394
+ values: [{
395
+ type: i0.Input,
396
+ args: [{
397
+ isSignal: true,
398
+ alias: "values",
399
+ required: false
400
+ }]
401
+ }],
402
+ empty: [{
403
+ type: i0.Input,
404
+ args: [{
405
+ isSignal: true,
406
+ alias: "empty",
407
+ required: false
408
+ }]
409
+ }]
410
+ }
411
+ });
412
+ var ContextBar = class ContextBar {
413
+ ctx = inject(ContextService);
414
+ api = inject(ApiService);
415
+ router = inject(Router);
416
+ links = inject(ObservabilityLinks);
417
+ ranges = Object.keys(RANGES);
418
+ environments = toSignal(this.api.environments().pipe(map((p) => p.items), catchError(() => of([]))), { initialValue: [] });
419
+ query = "";
420
+ message = signal("", ...ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []);
421
+ lookup() {
422
+ const q = this.query.trim();
423
+ if (!q) return;
424
+ this.message.set("Searching…");
425
+ this.api.search(q).subscribe({
426
+ next: (page) => {
427
+ const hit = page.items[0];
428
+ if (!hit) {
429
+ this.message.set(`Nothing found for "${q}".`);
430
+ return;
431
+ }
432
+ this.message.set(page.items.length > 1 ? `${page.items.length} matches; showing the first.` : "");
433
+ const extra = hit.kind === "span" ? { span: hit.id } : {};
434
+ hit.kind === "service" ? this.router.navigate(this.links.to("services", hit.id), { queryParams: this.ctx.keep() }) : this.router.navigate(this.links.to("traces", hit.traceId), { queryParams: this.ctx.keep(extra) });
435
+ },
436
+ error: () => this.message.set("Search failed.")
437
+ });
438
+ }
439
+ static ɵfac = i0.ɵɵngDeclareFactory({
440
+ minVersion: "12.0.0",
441
+ version: "22.2.4",
442
+ ngImport: i0,
443
+ type: ContextBar,
444
+ deps: [],
445
+ target: i0.ɵɵFactoryTarget.Component
446
+ });
447
+ static ɵcmp = i0.ɵɵngDeclareComponent({
448
+ minVersion: "17.0.0",
449
+ version: "22.2.4",
450
+ type: ContextBar,
451
+ isStandalone: true,
452
+ selector: "jc-context-bar",
453
+ ngImport: i0,
454
+ template: `
455
+ <div class="jc-context">
456
+ <ng-content />
457
+ <span class="jc-spacer"></span>
458
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm">
459
+ <mat-label>Environment</mat-label>
460
+ <mat-select [value]="ctx.environment()" (valueChange)="ctx.set({ env: $event || null })">
461
+ <mat-option value="">All</mat-option>
462
+ @for (e of environments(); track e.id) { <mat-option [value]="e.key">{{ e.key }}</mat-option> }
463
+ </mat-select>
464
+ </mat-form-field>
465
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm">
466
+ <mat-label>Time range</mat-label>
467
+ <mat-select [value]="ctx.range()" (valueChange)="ctx.set({ range: $event })">
468
+ @for (r of ranges; track r) { <mat-option [value]="r">Last {{ r }}</mat-option> }
469
+ @if (ctx.range() === 'custom') { <mat-option value="custom" disabled>Custom window</mat-option> }
470
+ </mat-select>
471
+ </mat-form-field>
472
+ <form class="jc-lookup" (ngSubmit)="lookup()">
473
+ <mat-icon>search</mat-icon>
474
+ <input name="q" [(ngModel)]="query" placeholder="Trace ID, span ID or service" aria-label="Go to trace, span or service">
475
+ </form>
476
+ </div>
477
+ @if (message()) { <p class="jc-muted jc-lookup-message">{{ message() }}</p> }`,
478
+ isInline: true,
479
+ dependencies: [
480
+ {
481
+ kind: "ngmodule",
482
+ type: FormsModule
483
+ },
484
+ {
485
+ kind: "directive",
486
+ type: i1$1.ɵNgNoValidate,
487
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
488
+ },
489
+ {
490
+ kind: "directive",
491
+ type: i1$1.DefaultValueAccessor,
492
+ selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
493
+ },
494
+ {
495
+ kind: "directive",
496
+ type: i1$1.NgControlStatus,
497
+ selector: "[formControlName],[ngModel],[formControl]"
498
+ },
499
+ {
500
+ kind: "directive",
501
+ type: i1$1.NgControlStatusGroup,
502
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
503
+ },
504
+ {
505
+ kind: "directive",
506
+ type: i1$1.NgModel,
507
+ selector: "[ngModel]:not([formControlName]):not([formControl])",
508
+ inputs: [
509
+ "name",
510
+ "disabled",
511
+ "ngModel",
512
+ "ngModelOptions"
513
+ ],
514
+ outputs: ["ngModelChange"],
515
+ exportAs: ["ngModel"]
516
+ },
517
+ {
518
+ kind: "directive",
519
+ type: i1$1.NgForm,
520
+ selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]",
521
+ inputs: ["ngFormOptions"],
522
+ outputs: ["ngSubmit"],
523
+ exportAs: ["ngForm"]
524
+ },
525
+ {
526
+ kind: "ngmodule",
527
+ type: MatSelectModule
528
+ },
529
+ {
530
+ kind: "component",
531
+ type: i2$1.MatFormField,
532
+ selector: "mat-form-field",
533
+ inputs: [
534
+ "hideRequiredMarker",
535
+ "color",
536
+ "floatLabel",
537
+ "appearance",
538
+ "subscriptSizing",
539
+ "hintLabel"
540
+ ],
541
+ exportAs: ["matFormField"]
542
+ },
543
+ {
544
+ kind: "directive",
545
+ type: i2$1.MatLabel,
546
+ selector: "mat-label"
547
+ },
548
+ {
549
+ kind: "component",
550
+ type: i2$1.MatSelect,
551
+ selector: "mat-select",
552
+ inputs: [
553
+ "aria-describedby",
554
+ "panelClass",
555
+ "disabled",
556
+ "disableRipple",
557
+ "tabIndex",
558
+ "hideSingleSelectionIndicator",
559
+ "placeholder",
560
+ "required",
561
+ "multiple",
562
+ "disableOptionCentering",
563
+ "compareWith",
564
+ "value",
565
+ "aria-label",
566
+ "aria-labelledby",
567
+ "errorStateMatcher",
568
+ "typeaheadDebounceInterval",
569
+ "sortComparator",
570
+ "id",
571
+ "panelWidth",
572
+ "canSelectNullableOptions"
573
+ ],
574
+ outputs: [
575
+ "openedChange",
576
+ "opened",
577
+ "closed",
578
+ "selectionChange",
579
+ "valueChange"
580
+ ],
581
+ exportAs: ["matSelect"]
582
+ },
583
+ {
584
+ kind: "component",
585
+ type: i2$1.MatOption,
586
+ selector: "mat-option",
587
+ inputs: [
588
+ "value",
589
+ "id",
590
+ "disabled"
591
+ ],
592
+ outputs: ["onSelectionChange"],
593
+ exportAs: ["matOption"]
594
+ },
595
+ {
596
+ kind: "ngmodule",
597
+ type: MatIconModule
598
+ },
599
+ {
600
+ kind: "component",
601
+ type: i3.MatIcon,
602
+ selector: "mat-icon",
603
+ inputs: [
604
+ "color",
605
+ "inline",
606
+ "svgIcon",
607
+ "fontSet",
608
+ "fontIcon"
609
+ ],
610
+ exportAs: ["matIcon"]
611
+ },
612
+ {
613
+ kind: "ngmodule",
614
+ type: MatButtonModule
615
+ }
616
+ ],
617
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
618
+ });
619
+ };
620
+ i0.ɵɵngDeclareClassMetadata({
621
+ minVersion: "12.0.0",
622
+ version: "22.2.4",
623
+ ngImport: i0,
624
+ type: ContextBar,
625
+ decorators: [{
626
+ type: Component,
627
+ args: [{
628
+ selector: "jc-context-bar",
629
+ changeDetection: ChangeDetectionStrategy.OnPush,
630
+ imports: [
631
+ FormsModule,
632
+ MatSelectModule,
633
+ MatIconModule,
634
+ MatButtonModule
635
+ ],
636
+ template: `
637
+ <div class="jc-context">
638
+ <ng-content />
639
+ <span class="jc-spacer"></span>
640
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm">
641
+ <mat-label>Environment</mat-label>
642
+ <mat-select [value]="ctx.environment()" (valueChange)="ctx.set({ env: $event || null })">
643
+ <mat-option value="">All</mat-option>
644
+ @for (e of environments(); track e.id) { <mat-option [value]="e.key">{{ e.key }}</mat-option> }
645
+ </mat-select>
646
+ </mat-form-field>
647
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm">
648
+ <mat-label>Time range</mat-label>
649
+ <mat-select [value]="ctx.range()" (valueChange)="ctx.set({ range: $event })">
650
+ @for (r of ranges; track r) { <mat-option [value]="r">Last {{ r }}</mat-option> }
651
+ @if (ctx.range() === 'custom') { <mat-option value="custom" disabled>Custom window</mat-option> }
652
+ </mat-select>
653
+ </mat-form-field>
654
+ <form class="jc-lookup" (ngSubmit)="lookup()">
655
+ <mat-icon>search</mat-icon>
656
+ <input name="q" [(ngModel)]="query" placeholder="Trace ID, span ID or service" aria-label="Go to trace, span or service">
657
+ </form>
658
+ </div>
659
+ @if (message()) { <p class="jc-muted jc-lookup-message">{{ message() }}</p> }`
660
+ }]
661
+ }]
662
+ });
663
+ function withGaps(points, from, to, stepSeconds) {
664
+ const step = stepSeconds * 1e3;
665
+ const byTime = new Map(points.map((p) => [new Date(p.at).getTime(), p.value]));
666
+ const out = [];
667
+ for (let t = Math.floor(new Date(from).getTime() / step) * step; t < new Date(to).getTime(); t += step) out.push({
668
+ at: new Date(t).toISOString(),
669
+ value: byTime.get(t) ?? null
670
+ });
671
+ return out;
672
+ }
673
+ var Chart = class Chart {
674
+ title = input("", ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
675
+ series = input([], ...ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []);
676
+ format = input((v) => String(Math.round(v * 100) / 100), ...ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []);
677
+ host = viewChild.required("host", ...ngDevMode ? [{ debugName: "host" }] : /* istanbul ignore next */ []);
678
+ empty = computed(() => this.series().every((s) => s.points.every((p) => p.value === null)), ...ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []);
679
+ chart;
680
+ observer;
681
+ stopThemeWatch;
682
+ constructor() {
683
+ afterNextRender(async () => {
684
+ const [echarts, charts, components, renderers] = await Promise.all([
685
+ import("echarts/core"),
686
+ import("echarts/charts"),
687
+ import("echarts/components"),
688
+ import("echarts/renderers")
689
+ ]);
690
+ echarts.use([
691
+ charts.LineChart,
692
+ components.GridComponent,
693
+ components.TooltipComponent,
694
+ components.LegendComponent,
695
+ renderers.SVGRenderer
696
+ ]);
697
+ this.chart = echarts.init(this.host().nativeElement, void 0, { renderer: "svg" });
698
+ this.observer = new ResizeObserver(() => this.chart?.resize());
699
+ this.observer.observe(this.host().nativeElement);
700
+ this.stopThemeWatch = onThemeChange(() => this.render());
701
+ this.render();
702
+ });
703
+ effect(() => {
704
+ this.series();
705
+ this.render();
706
+ });
707
+ }
708
+ render() {
709
+ if (!this.chart) return;
710
+ const format = this.format();
711
+ const host = this.host().nativeElement;
712
+ const [text, muted, border, colors] = [
713
+ themeColor("text", host),
714
+ themeColor("muted", host),
715
+ themeColor("border", host),
716
+ seriesColors(host)
717
+ ];
718
+ const option = {
719
+ animation: false,
720
+ color: colors.length ? colors : void 0,
721
+ grid: {
722
+ left: 56,
723
+ right: 16,
724
+ top: 16,
725
+ bottom: 28
726
+ },
727
+ tooltip: {
728
+ trigger: "axis",
729
+ valueFormatter: (v) => typeof v === "number" ? format(v) : "—"
730
+ },
731
+ legend: this.series().length > 1 ? {
732
+ top: 0,
733
+ textStyle: { color: text }
734
+ } : void 0,
735
+ xAxis: {
736
+ type: "time",
737
+ axisLabel: { color: muted },
738
+ axisLine: { lineStyle: { color: border } }
739
+ },
740
+ yAxis: {
741
+ type: "value",
742
+ axisLabel: {
743
+ color: muted,
744
+ formatter: (v) => format(v)
745
+ },
746
+ splitLine: { lineStyle: { color: border } }
747
+ },
748
+ series: this.series().map((s) => ({
749
+ name: s.name,
750
+ type: "line",
751
+ showSymbol: false,
752
+ connectNulls: false,
753
+ data: s.points.map((p) => [p.at, p.value])
754
+ }))
755
+ };
756
+ this.chart.setOption(option, true);
757
+ }
758
+ ngOnDestroy() {
759
+ this.observer?.disconnect();
760
+ this.stopThemeWatch?.();
761
+ this.chart?.dispose();
762
+ }
763
+ static ɵfac = i0.ɵɵngDeclareFactory({
764
+ minVersion: "12.0.0",
765
+ version: "22.2.4",
766
+ ngImport: i0,
767
+ type: Chart,
768
+ deps: [],
769
+ target: i0.ɵɵFactoryTarget.Component
770
+ });
771
+ static ɵcmp = i0.ɵɵngDeclareComponent({
772
+ minVersion: "17.0.0",
773
+ version: "22.2.4",
774
+ type: Chart,
775
+ isStandalone: true,
776
+ selector: "jc-chart",
777
+ inputs: {
778
+ title: {
779
+ classPropertyName: "title",
780
+ publicName: "title",
781
+ isSignal: true,
782
+ isRequired: false,
783
+ transformFunction: null
784
+ },
785
+ series: {
786
+ classPropertyName: "series",
787
+ publicName: "series",
788
+ isSignal: true,
789
+ isRequired: false,
790
+ transformFunction: null
791
+ },
792
+ format: {
793
+ classPropertyName: "format",
794
+ publicName: "format",
795
+ isSignal: true,
796
+ isRequired: false,
797
+ transformFunction: null
798
+ }
799
+ },
800
+ viewQueries: [{
801
+ propertyName: "host",
802
+ first: true,
803
+ predicate: ["host"],
804
+ descendants: true,
805
+ isSignal: true
806
+ }],
807
+ ngImport: i0,
808
+ template: `<div class="jc-chart-title">{{ title() }}</div>
809
+ @if (empty()) { <div class="jc-chart jc-chart-empty">No data in this window</div> }
810
+ <div #host class="jc-chart" [hidden]="empty()"></div>`,
811
+ isInline: true,
812
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
813
+ });
814
+ };
815
+ i0.ɵɵngDeclareClassMetadata({
816
+ minVersion: "12.0.0",
817
+ version: "22.2.4",
818
+ ngImport: i0,
819
+ type: Chart,
820
+ decorators: [{
821
+ type: Component,
822
+ args: [{
823
+ selector: "jc-chart",
824
+ changeDetection: ChangeDetectionStrategy.OnPush,
825
+ template: `<div class="jc-chart-title">{{ title() }}</div>
826
+ @if (empty()) { <div class="jc-chart jc-chart-empty">No data in this window</div> }
827
+ <div #host class="jc-chart" [hidden]="empty()"></div>`
828
+ }]
829
+ }],
830
+ ctorParameters: () => [],
831
+ propDecorators: {
832
+ title: [{
833
+ type: i0.Input,
834
+ args: [{
835
+ isSignal: true,
836
+ alias: "title",
837
+ required: false
838
+ }]
839
+ }],
840
+ series: [{
841
+ type: i0.Input,
842
+ args: [{
843
+ isSignal: true,
844
+ alias: "series",
845
+ required: false
846
+ }]
847
+ }],
848
+ format: [{
849
+ type: i0.Input,
850
+ args: [{
851
+ isSignal: true,
852
+ alias: "format",
853
+ required: false
854
+ }]
855
+ }],
856
+ host: [{
857
+ type: i0.ViewChild,
858
+ args: ["host", { isSignal: true }]
859
+ }]
860
+ }
861
+ });
862
+ function cursorList(params, query, fetch) {
863
+ const destroyRef = inject(DestroyRef);
864
+ const first = apiResource({
865
+ params,
866
+ stream: ({ params }) => {
867
+ const q = query(params);
868
+ return fetch(q).pipe(map((page) => ({
869
+ page,
870
+ query: q
871
+ })));
872
+ }
873
+ });
874
+ const rest = linkedSignal({
875
+ ...ngDevMode ? { debugName: "rest" } : /* istanbul ignore next */ {},
876
+ source: first.value,
877
+ computation: () => ({
878
+ items: [],
879
+ cursor: void 0
880
+ })
881
+ });
882
+ const loadingMore = signal(false, ...ngDevMode ? [{ debugName: "loadingMore" }] : /* istanbul ignore next */ []);
883
+ const moreError = signal(null, ...ngDevMode ? [{ debugName: "moreError" }] : /* istanbul ignore next */ []);
884
+ const items = computed(() => [...first.value()?.page.items ?? [], ...rest().items], ...ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []);
885
+ const cursor = computed(() => rest().cursor !== void 0 ? rest().cursor : first.value()?.page.nextCursor ?? null, ...ngDevMode ? [{ debugName: "cursor" }] : /* istanbul ignore next */ []);
886
+ function more() {
887
+ const current = first.value();
888
+ const next = cursor();
889
+ if (!current || !next || loadingMore()) return;
890
+ loadingMore.set(true);
891
+ moreError.set(null);
892
+ fetch({
893
+ ...current.query,
894
+ cursor: next
895
+ }).pipe(takeUntilDestroyed(destroyRef)).subscribe({
896
+ next: (page) => rest.update((r) => ({
897
+ items: [...r.items, ...page.items],
898
+ cursor: page.nextCursor ?? null
899
+ })),
900
+ error: (e) => {
901
+ moreError.set(e);
902
+ loadingMore.set(false);
903
+ },
904
+ complete: () => loadingMore.set(false)
905
+ });
906
+ }
907
+ return {
908
+ first,
909
+ items,
910
+ hasMore: computed(() => cursor() !== null),
911
+ loadingMore,
912
+ moreError: moreError.asReadonly(),
913
+ more
914
+ };
915
+ }
916
+ function screenPages(rows, sizes = [
917
+ 50,
918
+ 100,
919
+ 200
920
+ ]) {
921
+ const size = signal(sizes[0], ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
922
+ const index = linkedSignal({
923
+ ...ngDevMode ? { debugName: "index" } : /* istanbul ignore next */ {},
924
+ source: rows,
925
+ computation: () => 0
926
+ });
927
+ return {
928
+ sizes,
929
+ size: size.asReadonly(),
930
+ index: index.asReadonly(),
931
+ visible: computed(() => rows().slice(index() * size(), (index() + 1) * size())),
932
+ needed: computed(() => rows().length > sizes[0]),
933
+ change(event) {
934
+ size.set(event.pageSize);
935
+ index.set(event.pageIndex);
936
+ }
937
+ };
938
+ }
939
+ function problemText(error) {
940
+ const e = error;
941
+ if (!e) return "";
942
+ if (e.status === 0) return "The Juice API cannot be reached.";
943
+ return [e.error?.title, e.error?.detail].filter(Boolean).join(": ") || `Request failed (${e.status}).`;
944
+ }
945
+ var TraceTable = class TraceTable {
946
+ ctx = inject(ContextService);
947
+ api = inject(ApiService);
948
+ query = input({}, ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
949
+ limit = input(50, ...ngDevMode ? [{ debugName: "limit" }] : /* istanbul ignore next */ []);
950
+ traces = cursorList(() => ({
951
+ query: this.query(),
952
+ env: this.ctx.environment(),
953
+ range: this.ctx.range(),
954
+ abs: this.ctx.absolute(),
955
+ limit: this.limit()
956
+ }), (p) => ({
957
+ environment: p.env,
958
+ ...this.ctx.window(MAX_SEARCH_MS),
959
+ limit: p.limit,
960
+ ...p.query
961
+ }), (q) => this.api.traces(q));
962
+ rows = this.traces.items;
963
+ longest = computed(() => Math.max(1, ...this.rows().map((t) => t.durationMs)), ...ngDevMode ? [{ debugName: "longest" }] : /* istanbul ignore next */ []);
964
+ error = computed(() => problemText(this.traces.first.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
965
+ moreError = computed(() => problemText(this.traces.moreError()), ...ngDevMode ? [{ debugName: "moreError" }] : /* istanbul ignore next */ []);
966
+ static ɵfac = i0.ɵɵngDeclareFactory({
967
+ minVersion: "12.0.0",
968
+ version: "22.2.4",
969
+ ngImport: i0,
970
+ type: TraceTable,
971
+ deps: [],
972
+ target: i0.ɵɵFactoryTarget.Component
973
+ });
974
+ static ɵcmp = i0.ɵɵngDeclareComponent({
975
+ minVersion: "17.0.0",
976
+ version: "22.2.4",
977
+ type: TraceTable,
978
+ isStandalone: true,
979
+ selector: "jc-trace-table",
980
+ inputs: {
981
+ query: {
982
+ classPropertyName: "query",
983
+ publicName: "query",
984
+ isSignal: true,
985
+ isRequired: false,
986
+ transformFunction: null
987
+ },
988
+ limit: {
989
+ classPropertyName: "limit",
990
+ publicName: "limit",
991
+ isSignal: true,
992
+ isRequired: false,
993
+ transformFunction: null
994
+ }
995
+ },
996
+ ngImport: i0,
997
+ template: `
998
+ @if (traces.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
999
+ @if (traces.first.error()) { <p class="jc-error">{{ error() }}</p> }
1000
+ @if (traces.first.hasValue()) {
1001
+ @if (rows().length === 0) { <p class="jc-muted">No traces match in this window.</p> }
1002
+ @else {
1003
+ <table class="jc-table">
1004
+ <thead><tr><th>Start</th><th>Root operation</th><th>Duration</th><th>Spans</th><th>Status</th><th>Instance</th></tr></thead>
1005
+ <tbody>
1006
+ @for (t of rows(); track t.traceId) {
1007
+ <tr>
1008
+ <td class="jc-nowrap">{{ t.startedAt | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1009
+ <td><a [routerLink]="['traces', t.traceId] | jcLink" [queryParams]="ctx.keep()">{{ t.rootService }} · {{ t.rootOperation }}</a>
1010
+ @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.%]="100 * t.durationMs / longest()"></span>{{ t.durationMs | duration }}</td>
1012
+ <td>{{ t.spanCount }}</td>
1013
+ <td>@if (t.errorCount > 0) { <span class="jc-badge jc-health-unhealthy">{{ t.errorCount }} error{{ t.errorCount > 1 ? 's' : '' }}</span> }
1014
+ @else { <span class="jc-badge jc-health-healthy">ok</span> }</td>
1015
+ <td class="jc-muted jc-small">{{ t.rootInstance ?? t.rootHost ?? '—' }}</td>
1016
+ </tr>
1017
+ }
1018
+ </tbody>
1019
+ </table>
1020
+ <div class="jc-more">
1021
+ <span class="jc-muted">{{ rows().length }} trace{{ rows().length === 1 ? '' : 's' }}{{ traces.hasMore() ? ', newest first' : '' }}</span>
1022
+ @if (traces.hasMore()) {
1023
+ <button mat-button (click)="traces.more()" [disabled]="traces.loadingMore()">{{ traces.loadingMore() ? 'Loading…' : 'Older traces' }}</button>
1024
+ }
1025
+ @if (traces.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1026
+ </div>
1027
+ }
1028
+ }`,
1029
+ isInline: true,
1030
+ dependencies: [
1031
+ {
1032
+ kind: "directive",
1033
+ type: RouterLink,
1034
+ selector: "[routerLink]",
1035
+ inputs: [
1036
+ "target",
1037
+ "queryParams",
1038
+ "fragment",
1039
+ "queryParamsHandling",
1040
+ "state",
1041
+ "info",
1042
+ "relativeTo",
1043
+ "preserveFragment",
1044
+ "skipLocationChange",
1045
+ "replaceUrl",
1046
+ "browserUrl",
1047
+ "routerLink"
1048
+ ]
1049
+ },
1050
+ {
1051
+ kind: "ngmodule",
1052
+ type: MatProgressBarModule
1053
+ },
1054
+ {
1055
+ kind: "component",
1056
+ type: i1.MatProgressBar,
1057
+ selector: "mat-progress-bar",
1058
+ inputs: [
1059
+ "color",
1060
+ "value",
1061
+ "bufferValue",
1062
+ "mode"
1063
+ ],
1064
+ outputs: ["animationEnd"],
1065
+ exportAs: ["matProgressBar"]
1066
+ },
1067
+ {
1068
+ kind: "ngmodule",
1069
+ type: MatButtonModule
1070
+ },
1071
+ {
1072
+ kind: "component",
1073
+ type: i2.MatButton,
1074
+ selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ",
1075
+ inputs: ["matButton"],
1076
+ exportAs: ["matButton", "matAnchor"]
1077
+ },
1078
+ {
1079
+ kind: "pipe",
1080
+ type: LinkPipe,
1081
+ name: "jcLink"
1082
+ },
1083
+ {
1084
+ kind: "pipe",
1085
+ type: DatePipe,
1086
+ name: "date"
1087
+ },
1088
+ {
1089
+ kind: "pipe",
1090
+ type: DurationPipe,
1091
+ name: "duration"
1092
+ }
1093
+ ],
1094
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1095
+ });
1096
+ };
1097
+ i0.ɵɵngDeclareClassMetadata({
1098
+ minVersion: "12.0.0",
1099
+ version: "22.2.4",
1100
+ ngImport: i0,
1101
+ type: TraceTable,
1102
+ decorators: [{
1103
+ type: Component,
1104
+ args: [{
1105
+ selector: "jc-trace-table",
1106
+ changeDetection: ChangeDetectionStrategy.OnPush,
1107
+ imports: [
1108
+ LinkPipe,
1109
+ RouterLink,
1110
+ DatePipe,
1111
+ DurationPipe,
1112
+ MatProgressBarModule,
1113
+ MatButtonModule
1114
+ ],
1115
+ template: `
1116
+ @if (traces.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1117
+ @if (traces.first.error()) { <p class="jc-error">{{ error() }}</p> }
1118
+ @if (traces.first.hasValue()) {
1119
+ @if (rows().length === 0) { <p class="jc-muted">No traces match in this window.</p> }
1120
+ @else {
1121
+ <table class="jc-table">
1122
+ <thead><tr><th>Start</th><th>Root operation</th><th>Duration</th><th>Spans</th><th>Status</th><th>Instance</th></tr></thead>
1123
+ <tbody>
1124
+ @for (t of rows(); track t.traceId) {
1125
+ <tr>
1126
+ <td class="jc-nowrap">{{ t.startedAt | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1127
+ <td><a [routerLink]="['traces', t.traceId] | jcLink" [queryParams]="ctx.keep()">{{ t.rootService }} · {{ t.rootOperation }}</a>
1128
+ @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.%]="100 * t.durationMs / longest()"></span>{{ t.durationMs | duration }}</td>
1130
+ <td>{{ t.spanCount }}</td>
1131
+ <td>@if (t.errorCount > 0) { <span class="jc-badge jc-health-unhealthy">{{ t.errorCount }} error{{ t.errorCount > 1 ? 's' : '' }}</span> }
1132
+ @else { <span class="jc-badge jc-health-healthy">ok</span> }</td>
1133
+ <td class="jc-muted jc-small">{{ t.rootInstance ?? t.rootHost ?? '—' }}</td>
1134
+ </tr>
1135
+ }
1136
+ </tbody>
1137
+ </table>
1138
+ <div class="jc-more">
1139
+ <span class="jc-muted">{{ rows().length }} trace{{ rows().length === 1 ? '' : 's' }}{{ traces.hasMore() ? ', newest first' : '' }}</span>
1140
+ @if (traces.hasMore()) {
1141
+ <button mat-button (click)="traces.more()" [disabled]="traces.loadingMore()">{{ traces.loadingMore() ? 'Loading…' : 'Older traces' }}</button>
1142
+ }
1143
+ @if (traces.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1144
+ </div>
1145
+ }
1146
+ }`
1147
+ }]
1148
+ }],
1149
+ propDecorators: {
1150
+ query: [{
1151
+ type: i0.Input,
1152
+ args: [{
1153
+ isSignal: true,
1154
+ alias: "query",
1155
+ required: false
1156
+ }]
1157
+ }],
1158
+ limit: [{
1159
+ type: i0.Input,
1160
+ args: [{
1161
+ isSignal: true,
1162
+ alias: "limit",
1163
+ required: false
1164
+ }]
1165
+ }]
1166
+ }
1167
+ });
1168
+ var LogTable = class LogTable {
1169
+ ctx = inject(ContextService);
1170
+ api = inject(ApiService);
1171
+ query = input({}, ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
1172
+ compact = input(false, ...ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []);
1173
+ logs = cursorList(() => ({
1174
+ query: this.query(),
1175
+ env: this.ctx.environment(),
1176
+ range: this.ctx.range(),
1177
+ abs: this.ctx.absolute()
1178
+ }), (p) => ({
1179
+ environment: p.env,
1180
+ ...this.ctx.window(MAX_SEARCH_MS),
1181
+ limit: 200,
1182
+ ...p.query
1183
+ }), (q) => this.api.logs(q));
1184
+ rows = this.logs.items;
1185
+ error = computed(() => problemText(this.logs.first.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
1186
+ moreError = computed(() => problemText(this.logs.moreError()), ...ngDevMode ? [{ debugName: "moreError" }] : /* istanbul ignore next */ []);
1187
+ severityClass(n) {
1188
+ return n >= 17 ? "jc-health-unhealthy" : n >= 13 ? "jc-health-degraded" : n >= 9 ? "jc-health-healthy" : "jc-health-none";
1189
+ }
1190
+ static ɵfac = i0.ɵɵngDeclareFactory({
1191
+ minVersion: "12.0.0",
1192
+ version: "22.2.4",
1193
+ ngImport: i0,
1194
+ type: LogTable,
1195
+ deps: [],
1196
+ target: i0.ɵɵFactoryTarget.Component
1197
+ });
1198
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1199
+ minVersion: "17.0.0",
1200
+ version: "22.2.4",
1201
+ type: LogTable,
1202
+ isStandalone: true,
1203
+ selector: "jc-log-table",
1204
+ inputs: {
1205
+ query: {
1206
+ classPropertyName: "query",
1207
+ publicName: "query",
1208
+ isSignal: true,
1209
+ isRequired: false,
1210
+ transformFunction: null
1211
+ },
1212
+ compact: {
1213
+ classPropertyName: "compact",
1214
+ publicName: "compact",
1215
+ isSignal: true,
1216
+ isRequired: false,
1217
+ transformFunction: null
1218
+ }
1219
+ },
1220
+ ngImport: i0,
1221
+ template: `
1222
+ @if (logs.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1223
+ @if (logs.first.error()) { <p class="jc-error">{{ error() }}</p> }
1224
+ @if (logs.first.hasValue()) {
1225
+ @if (rows().length === 0) { <p class="jc-muted">No log records match in this window.</p> }
1226
+ @else {
1227
+ <table class="jc-table jc-logs">
1228
+ <thead><tr><th>Time</th><th>Severity</th>@if (!compact()) { <th>Service</th> }<th>Message</th>@if (!compact()) { <th>Trace</th> }</tr></thead>
1229
+ <tbody>
1230
+ @for (l of rows(); track $index) {
1231
+ <tr>
1232
+ <td class="jc-nowrap">{{ l.timestamp | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1233
+ <td><span class="jc-badge" [class]="severityClass(l.severityNumber)">{{ l.severity || l.severityNumber }}</span></td>
1234
+ @if (!compact()) { <td class="jc-nowrap">{{ l.service }}@if (l.instance) {<div class="jc-muted jc-small">{{ l.instance }}</div>}</td> }
1235
+ <td><pre class="jc-body">{{ l.body }}@if (l.bodyTruncated) {…}</pre></td>
1236
+ @if (!compact()) { <td class="jc-nowrap">
1237
+ @if (l.traceId) {
1238
+ <a [routerLink]="['traces', l.traceId] | jcLink" [queryParams]="ctx.keep(l.spanId ? { span: l.spanId } : {})">{{ l.spanId ? 'span' : 'trace' }} ↗</a>
1239
+ } @else { <span class="jc-muted">—</span> }
1240
+ </td> }
1241
+ </tr>
1242
+ }
1243
+ </tbody>
1244
+ </table>
1245
+ <div class="jc-more">
1246
+ <span class="jc-muted">{{ rows().length }} record{{ rows().length === 1 ? '' : 's' }}{{ logs.hasMore() ? ', newest first' : '' }}</span>
1247
+ @if (logs.hasMore()) {
1248
+ <button mat-button (click)="logs.more()" [disabled]="logs.loadingMore()">{{ logs.loadingMore() ? 'Loading…' : 'Older logs' }}</button>
1249
+ }
1250
+ @if (logs.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1251
+ </div>
1252
+ }
1253
+ }`,
1254
+ isInline: true,
1255
+ dependencies: [
1256
+ {
1257
+ kind: "directive",
1258
+ type: RouterLink,
1259
+ selector: "[routerLink]",
1260
+ inputs: [
1261
+ "target",
1262
+ "queryParams",
1263
+ "fragment",
1264
+ "queryParamsHandling",
1265
+ "state",
1266
+ "info",
1267
+ "relativeTo",
1268
+ "preserveFragment",
1269
+ "skipLocationChange",
1270
+ "replaceUrl",
1271
+ "browserUrl",
1272
+ "routerLink"
1273
+ ]
1274
+ },
1275
+ {
1276
+ kind: "ngmodule",
1277
+ type: MatProgressBarModule
1278
+ },
1279
+ {
1280
+ kind: "component",
1281
+ type: i1.MatProgressBar,
1282
+ selector: "mat-progress-bar",
1283
+ inputs: [
1284
+ "color",
1285
+ "value",
1286
+ "bufferValue",
1287
+ "mode"
1288
+ ],
1289
+ outputs: ["animationEnd"],
1290
+ exportAs: ["matProgressBar"]
1291
+ },
1292
+ {
1293
+ kind: "ngmodule",
1294
+ type: MatButtonModule
1295
+ },
1296
+ {
1297
+ kind: "component",
1298
+ type: i2.MatButton,
1299
+ selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ",
1300
+ inputs: ["matButton"],
1301
+ exportAs: ["matButton", "matAnchor"]
1302
+ },
1303
+ {
1304
+ kind: "pipe",
1305
+ type: LinkPipe,
1306
+ name: "jcLink"
1307
+ },
1308
+ {
1309
+ kind: "pipe",
1310
+ type: DatePipe,
1311
+ name: "date"
1312
+ }
1313
+ ],
1314
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1315
+ });
1316
+ };
1317
+ i0.ɵɵngDeclareClassMetadata({
1318
+ minVersion: "12.0.0",
1319
+ version: "22.2.4",
1320
+ ngImport: i0,
1321
+ type: LogTable,
1322
+ decorators: [{
1323
+ type: Component,
1324
+ args: [{
1325
+ selector: "jc-log-table",
1326
+ changeDetection: ChangeDetectionStrategy.OnPush,
1327
+ imports: [
1328
+ LinkPipe,
1329
+ RouterLink,
1330
+ DatePipe,
1331
+ MatProgressBarModule,
1332
+ MatButtonModule
1333
+ ],
1334
+ template: `
1335
+ @if (logs.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1336
+ @if (logs.first.error()) { <p class="jc-error">{{ error() }}</p> }
1337
+ @if (logs.first.hasValue()) {
1338
+ @if (rows().length === 0) { <p class="jc-muted">No log records match in this window.</p> }
1339
+ @else {
1340
+ <table class="jc-table jc-logs">
1341
+ <thead><tr><th>Time</th><th>Severity</th>@if (!compact()) { <th>Service</th> }<th>Message</th>@if (!compact()) { <th>Trace</th> }</tr></thead>
1342
+ <tbody>
1343
+ @for (l of rows(); track $index) {
1344
+ <tr>
1345
+ <td class="jc-nowrap">{{ l.timestamp | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1346
+ <td><span class="jc-badge" [class]="severityClass(l.severityNumber)">{{ l.severity || l.severityNumber }}</span></td>
1347
+ @if (!compact()) { <td class="jc-nowrap">{{ l.service }}@if (l.instance) {<div class="jc-muted jc-small">{{ l.instance }}</div>}</td> }
1348
+ <td><pre class="jc-body">{{ l.body }}@if (l.bodyTruncated) {…}</pre></td>
1349
+ @if (!compact()) { <td class="jc-nowrap">
1350
+ @if (l.traceId) {
1351
+ <a [routerLink]="['traces', l.traceId] | jcLink" [queryParams]="ctx.keep(l.spanId ? { span: l.spanId } : {})">{{ l.spanId ? 'span' : 'trace' }} ↗</a>
1352
+ } @else { <span class="jc-muted">—</span> }
1353
+ </td> }
1354
+ </tr>
1355
+ }
1356
+ </tbody>
1357
+ </table>
1358
+ <div class="jc-more">
1359
+ <span class="jc-muted">{{ rows().length }} record{{ rows().length === 1 ? '' : 's' }}{{ logs.hasMore() ? ', newest first' : '' }}</span>
1360
+ @if (logs.hasMore()) {
1361
+ <button mat-button (click)="logs.more()" [disabled]="logs.loadingMore()">{{ logs.loadingMore() ? 'Loading…' : 'Older logs' }}</button>
1362
+ }
1363
+ @if (logs.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1364
+ </div>
1365
+ }
1366
+ }`
1367
+ }]
1368
+ }],
1369
+ propDecorators: {
1370
+ query: [{
1371
+ type: i0.Input,
1372
+ args: [{
1373
+ isSignal: true,
1374
+ alias: "query",
1375
+ required: false
1376
+ }]
1377
+ }],
1378
+ compact: [{
1379
+ type: i0.Input,
1380
+ args: [{
1381
+ isSignal: true,
1382
+ alias: "compact",
1383
+ required: false
1384
+ }]
1385
+ }]
1386
+ }
1387
+ });
1388
+ var MetricExplorer = class MetricExplorer {
1389
+ ctx = inject(ContextService);
1390
+ api = inject(ApiService);
1391
+ scope = input({}, ...ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []);
1392
+ initial = input(null, ...ngDevMode ? [{ debugName: "initial" }] : /* istanbul ignore next */ []);
1393
+ metrics = apiResource({
1394
+ params: () => ({
1395
+ scope: this.scope(),
1396
+ env: this.ctx.environment(),
1397
+ range: this.ctx.range(),
1398
+ abs: this.ctx.absolute()
1399
+ }),
1400
+ stream: ({ params }) => this.api.metrics({
1401
+ environment: params.env,
1402
+ ...this.ctx.window(),
1403
+ ...params.scope
1404
+ })
1405
+ });
1406
+ names = computed(() => this.metrics.value()?.items ?? [], ...ngDevMode ? [{ debugName: "names" }] : /* istanbul ignore next */ []);
1407
+ selected = linkedSignal(() => {
1408
+ const names = this.names();
1409
+ return names.find((m) => m.name === this.initial())?.name ?? names[0]?.name ?? null;
1410
+ }, ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
1411
+ series = apiResource({
1412
+ params: () => this.selected() ? {
1413
+ name: this.selected(),
1414
+ scope: this.scope(),
1415
+ env: this.ctx.environment(),
1416
+ range: this.ctx.range(),
1417
+ abs: this.ctx.absolute()
1418
+ } : void 0,
1419
+ stream: ({ params }) => {
1420
+ this.window = this.ctx.window();
1421
+ return this.api.series({
1422
+ name: params.name,
1423
+ environment: params.env,
1424
+ ...this.window,
1425
+ ...params.scope
1426
+ });
1427
+ }
1428
+ });
1429
+ window = {
1430
+ from: "",
1431
+ to: ""
1432
+ };
1433
+ points = computed(() => {
1434
+ const s = this.series.value();
1435
+ return s ? withGaps(s.points, this.window.from, this.window.to, s.stepSeconds) : [];
1436
+ }, ...ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []);
1437
+ title = computed(() => {
1438
+ const s = this.series.value();
1439
+ if (!s) return "";
1440
+ const what = s.aggregation === "rate" ? "rate per second" : "average";
1441
+ return `${s.name} — ${what}${s.unit ? ` (${s.unit}${s.aggregation === "rate" ? "/s" : ""})` : ""}`;
1442
+ }, ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
1443
+ metricsError = computed(() => problemText(this.metrics.error()), ...ngDevMode ? [{ debugName: "metricsError" }] : /* istanbul ignore next */ []);
1444
+ static ɵfac = i0.ɵɵngDeclareFactory({
1445
+ minVersion: "12.0.0",
1446
+ version: "22.2.4",
1447
+ ngImport: i0,
1448
+ type: MetricExplorer,
1449
+ deps: [],
1450
+ target: i0.ɵɵFactoryTarget.Component
1451
+ });
1452
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1453
+ minVersion: "17.0.0",
1454
+ version: "22.2.4",
1455
+ type: MetricExplorer,
1456
+ isStandalone: true,
1457
+ selector: "jc-metric-explorer",
1458
+ inputs: {
1459
+ scope: {
1460
+ classPropertyName: "scope",
1461
+ publicName: "scope",
1462
+ isSignal: true,
1463
+ isRequired: false,
1464
+ transformFunction: null
1465
+ },
1466
+ initial: {
1467
+ classPropertyName: "initial",
1468
+ publicName: "initial",
1469
+ isSignal: true,
1470
+ isRequired: false,
1471
+ transformFunction: null
1472
+ }
1473
+ },
1474
+ ngImport: i0,
1475
+ template: `
1476
+ @if (metrics.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1477
+ @if (metrics.error()) { <p class="jc-error">{{ metricsError() }}</p> }
1478
+ @if (metrics.hasValue()) {
1479
+ @if (names().length === 0) { <p class="jc-muted">No metrics reported in this window.</p> }
1480
+ @else {
1481
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide">
1482
+ <mat-label>Metric</mat-label>
1483
+ <mat-select [value]="selected()" (valueChange)="selected.set($event)">
1484
+ @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> }
1485
+ </mat-select>
1486
+ </mat-form-field>
1487
+ @if (series.value(); as s) {
1488
+ <jc-chart [title]="title()" [series]="[{ name: s.name, points: points() }]" />
1489
+ }
1490
+ }
1491
+ }`,
1492
+ isInline: true,
1493
+ dependencies: [
1494
+ {
1495
+ kind: "ngmodule",
1496
+ type: FormsModule
1497
+ },
1498
+ {
1499
+ kind: "ngmodule",
1500
+ type: MatSelectModule
1501
+ },
1502
+ {
1503
+ kind: "component",
1504
+ type: i2$1.MatFormField,
1505
+ selector: "mat-form-field",
1506
+ inputs: [
1507
+ "hideRequiredMarker",
1508
+ "color",
1509
+ "floatLabel",
1510
+ "appearance",
1511
+ "subscriptSizing",
1512
+ "hintLabel"
1513
+ ],
1514
+ exportAs: ["matFormField"]
1515
+ },
1516
+ {
1517
+ kind: "directive",
1518
+ type: i2$1.MatLabel,
1519
+ selector: "mat-label"
1520
+ },
1521
+ {
1522
+ kind: "component",
1523
+ type: i2$1.MatSelect,
1524
+ selector: "mat-select",
1525
+ inputs: [
1526
+ "aria-describedby",
1527
+ "panelClass",
1528
+ "disabled",
1529
+ "disableRipple",
1530
+ "tabIndex",
1531
+ "hideSingleSelectionIndicator",
1532
+ "placeholder",
1533
+ "required",
1534
+ "multiple",
1535
+ "disableOptionCentering",
1536
+ "compareWith",
1537
+ "value",
1538
+ "aria-label",
1539
+ "aria-labelledby",
1540
+ "errorStateMatcher",
1541
+ "typeaheadDebounceInterval",
1542
+ "sortComparator",
1543
+ "id",
1544
+ "panelWidth",
1545
+ "canSelectNullableOptions"
1546
+ ],
1547
+ outputs: [
1548
+ "openedChange",
1549
+ "opened",
1550
+ "closed",
1551
+ "selectionChange",
1552
+ "valueChange"
1553
+ ],
1554
+ exportAs: ["matSelect"]
1555
+ },
1556
+ {
1557
+ kind: "component",
1558
+ type: i2$1.MatOption,
1559
+ selector: "mat-option",
1560
+ inputs: [
1561
+ "value",
1562
+ "id",
1563
+ "disabled"
1564
+ ],
1565
+ outputs: ["onSelectionChange"],
1566
+ exportAs: ["matOption"]
1567
+ },
1568
+ {
1569
+ kind: "ngmodule",
1570
+ type: MatProgressBarModule
1571
+ },
1572
+ {
1573
+ kind: "component",
1574
+ type: i1.MatProgressBar,
1575
+ selector: "mat-progress-bar",
1576
+ inputs: [
1577
+ "color",
1578
+ "value",
1579
+ "bufferValue",
1580
+ "mode"
1581
+ ],
1582
+ outputs: ["animationEnd"],
1583
+ exportAs: ["matProgressBar"]
1584
+ },
1585
+ {
1586
+ kind: "component",
1587
+ type: Chart,
1588
+ selector: "jc-chart",
1589
+ inputs: [
1590
+ "title",
1591
+ "series",
1592
+ "format"
1593
+ ]
1594
+ }
1595
+ ],
1596
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1597
+ });
1598
+ };
1599
+ i0.ɵɵngDeclareClassMetadata({
1600
+ minVersion: "12.0.0",
1601
+ version: "22.2.4",
1602
+ ngImport: i0,
1603
+ type: MetricExplorer,
1604
+ decorators: [{
1605
+ type: Component,
1606
+ args: [{
1607
+ selector: "jc-metric-explorer",
1608
+ changeDetection: ChangeDetectionStrategy.OnPush,
1609
+ imports: [
1610
+ FormsModule,
1611
+ MatSelectModule,
1612
+ MatProgressBarModule,
1613
+ Chart
1614
+ ],
1615
+ template: `
1616
+ @if (metrics.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1617
+ @if (metrics.error()) { <p class="jc-error">{{ metricsError() }}</p> }
1618
+ @if (metrics.hasValue()) {
1619
+ @if (names().length === 0) { <p class="jc-muted">No metrics reported in this window.</p> }
1620
+ @else {
1621
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide">
1622
+ <mat-label>Metric</mat-label>
1623
+ <mat-select [value]="selected()" (valueChange)="selected.set($event)">
1624
+ @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> }
1625
+ </mat-select>
1626
+ </mat-form-field>
1627
+ @if (series.value(); as s) {
1628
+ <jc-chart [title]="title()" [series]="[{ name: s.name, points: points() }]" />
1629
+ }
1630
+ }
1631
+ }`
1632
+ }]
1633
+ }],
1634
+ propDecorators: {
1635
+ scope: [{
1636
+ type: i0.Input,
1637
+ args: [{
1638
+ isSignal: true,
1639
+ alias: "scope",
1640
+ required: false
1641
+ }]
1642
+ }],
1643
+ initial: [{
1644
+ type: i0.Input,
1645
+ args: [{
1646
+ isSignal: true,
1647
+ alias: "initial",
1648
+ required: false
1649
+ }]
1650
+ }]
1651
+ }
1652
+ });
1653
+ var ServiceStatsCharts = class ServiceStatsCharts {
1654
+ ctx = inject(ContextService);
1655
+ api = inject(ApiService);
1656
+ query = input.required(...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
1657
+ window = {
1658
+ from: "",
1659
+ to: ""
1660
+ };
1661
+ percent = (v) => `${Math.round(v * 1e3) / 10} %`;
1662
+ ms = (v) => `${Math.round(v)} ms`;
1663
+ stats = apiResource({
1664
+ params: () => ({
1665
+ query: this.query(),
1666
+ env: this.ctx.environment(),
1667
+ range: this.ctx.range(),
1668
+ abs: this.ctx.absolute()
1669
+ }),
1670
+ stream: ({ params }) => {
1671
+ this.window = this.ctx.window();
1672
+ return this.api.serviceStats({
1673
+ environment: params.env,
1674
+ ...this.window,
1675
+ ...params.query
1676
+ });
1677
+ }
1678
+ });
1679
+ pick(field) {
1680
+ const s = this.stats.value();
1681
+ return s ? withGaps(s.points.map((p) => ({
1682
+ at: p.at,
1683
+ value: p[field]
1684
+ })), this.window.from, this.window.to, s.stepSeconds) : [];
1685
+ }
1686
+ static ɵfac = i0.ɵɵngDeclareFactory({
1687
+ minVersion: "12.0.0",
1688
+ version: "22.2.4",
1689
+ ngImport: i0,
1690
+ type: ServiceStatsCharts,
1691
+ deps: [],
1692
+ target: i0.ɵɵFactoryTarget.Component
1693
+ });
1694
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1695
+ minVersion: "17.0.0",
1696
+ version: "22.2.4",
1697
+ type: ServiceStatsCharts,
1698
+ isStandalone: true,
1699
+ selector: "jc-service-stats",
1700
+ inputs: { query: {
1701
+ classPropertyName: "query",
1702
+ publicName: "query",
1703
+ isSignal: true,
1704
+ isRequired: true,
1705
+ transformFunction: null
1706
+ } },
1707
+ ngImport: i0,
1708
+ template: `
1709
+ @if (stats.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1710
+ @if (stats.hasValue()) {
1711
+ <div class="jc-grid-3">
1712
+ <jc-chart title="Requests per second" [series]="[{ name: 'requests/s', points: pick('requestsPerSecond') }]" />
1713
+ <jc-chart title="Error rate" [series]="[{ name: 'errors', points: pick('errorRate') }]" [format]="percent" />
1714
+ <jc-chart title="p95 latency" [series]="[{ name: 'p95', points: pick('p95Ms') }]" [format]="ms" />
1715
+ </div>
1716
+ }`,
1717
+ isInline: true,
1718
+ dependencies: [
1719
+ {
1720
+ kind: "component",
1721
+ type: Chart,
1722
+ selector: "jc-chart",
1723
+ inputs: [
1724
+ "title",
1725
+ "series",
1726
+ "format"
1727
+ ]
1728
+ },
1729
+ {
1730
+ kind: "ngmodule",
1731
+ type: MatProgressBarModule
1732
+ },
1733
+ {
1734
+ kind: "component",
1735
+ type: i1.MatProgressBar,
1736
+ selector: "mat-progress-bar",
1737
+ inputs: [
1738
+ "color",
1739
+ "value",
1740
+ "bufferValue",
1741
+ "mode"
1742
+ ],
1743
+ outputs: ["animationEnd"],
1744
+ exportAs: ["matProgressBar"]
1745
+ }
1746
+ ],
1747
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1748
+ });
1749
+ };
1750
+ i0.ɵɵngDeclareClassMetadata({
1751
+ minVersion: "12.0.0",
1752
+ version: "22.2.4",
1753
+ ngImport: i0,
1754
+ type: ServiceStatsCharts,
1755
+ decorators: [{
1756
+ type: Component,
1757
+ args: [{
1758
+ selector: "jc-service-stats",
1759
+ changeDetection: ChangeDetectionStrategy.OnPush,
1760
+ imports: [Chart, MatProgressBarModule],
1761
+ template: `
1762
+ @if (stats.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1763
+ @if (stats.hasValue()) {
1764
+ <div class="jc-grid-3">
1765
+ <jc-chart title="Requests per second" [series]="[{ name: 'requests/s', points: pick('requestsPerSecond') }]" />
1766
+ <jc-chart title="Error rate" [series]="[{ name: 'errors', points: pick('errorRate') }]" [format]="percent" />
1767
+ <jc-chart title="p95 latency" [series]="[{ name: 'p95', points: pick('p95Ms') }]" [format]="ms" />
1768
+ </div>
1769
+ }`
1770
+ }]
1771
+ }],
1772
+ propDecorators: { query: [{
1773
+ type: i0.Input,
1774
+ args: [{
1775
+ isSignal: true,
1776
+ alias: "query",
1777
+ required: true
1778
+ }]
1779
+ }] }
1780
+ });
1781
+ 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
+
1783
+ //# sourceMappingURL=juice-js-observability-telemetry-D9EYxkdr.mjs.map