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

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