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

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 +63 -2
  2. package/fesm2022/juice-js-observability-alert.page-TKwqJT2d.mjs +404 -0
  3. package/fesm2022/juice-js-observability-alert.page-TKwqJT2d.mjs.map +1 -0
  4. package/fesm2022/juice-js-observability-alerts.page-B-2uS9wr.mjs +211 -0
  5. package/fesm2022/juice-js-observability-alerts.page-B-2uS9wr.mjs.map +1 -0
  6. package/fesm2022/juice-js-observability-logs.page-CZNbTl2Q.mjs +413 -0
  7. package/fesm2022/juice-js-observability-logs.page-CZNbTl2Q.mjs.map +1 -0
  8. package/fesm2022/juice-js-observability-metrics.page-DoVED9MT.mjs +267 -0
  9. package/fesm2022/juice-js-observability-metrics.page-DoVED9MT.mjs.map +1 -0
  10. package/fesm2022/juice-js-observability-overview.page-Cod7-cIy.mjs +249 -0
  11. package/fesm2022/juice-js-observability-overview.page-Cod7-cIy.mjs.map +1 -0
  12. package/fesm2022/juice-js-observability-resource.page-C8RhUipF.mjs +274 -0
  13. package/fesm2022/juice-js-observability-resource.page-C8RhUipF.mjs.map +1 -0
  14. package/fesm2022/juice-js-observability-resources.page-KadGxEAh.mjs +209 -0
  15. package/fesm2022/juice-js-observability-resources.page-KadGxEAh.mjs.map +1 -0
  16. package/fesm2022/juice-js-observability-rules.page-DKWvNpu9.mjs +175 -0
  17. package/fesm2022/juice-js-observability-rules.page-DKWvNpu9.mjs.map +1 -0
  18. package/fesm2022/juice-js-observability-service.page-_yK0kVYS.mjs +726 -0
  19. package/fesm2022/juice-js-observability-service.page-_yK0kVYS.mjs.map +1 -0
  20. package/fesm2022/juice-js-observability-services.page-BtG8zyiI.mjs +251 -0
  21. package/fesm2022/juice-js-observability-services.page-BtG8zyiI.mjs.map +1 -0
  22. package/fesm2022/juice-js-observability-telemetry-B5jMJYfn.mjs +1801 -0
  23. package/fesm2022/juice-js-observability-telemetry-B5jMJYfn.mjs.map +1 -0
  24. package/fesm2022/juice-js-observability-topology.page-B7aXWOYV.mjs +486 -0
  25. package/fesm2022/juice-js-observability-topology.page-B7aXWOYV.mjs.map +1 -0
  26. package/fesm2022/juice-js-observability-trace.page-CkLNNnKf.mjs +528 -0
  27. package/fesm2022/juice-js-observability-trace.page-CkLNNnKf.mjs.map +1 -0
  28. package/fesm2022/juice-js-observability-traces.page-DgvNixIc.mjs +520 -0
  29. package/fesm2022/juice-js-observability-traces.page-DgvNixIc.mjs.map +1 -0
  30. package/fesm2022/juice-js-observability.mjs +405 -0
  31. package/fesm2022/juice-js-observability.mjs.map +1 -0
  32. package/package.json +48 -4
  33. package/styles/_observability.scss +220 -0
  34. package/types/juice-js-observability.d.ts +400 -0
@@ -0,0 +1,1801 @@
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
+ barWidth(ms) {
965
+ return Math.max(2, 80 * ms / this.longest());
966
+ }
967
+ error = computed(() => problemText(this.traces.first.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
968
+ moreError = computed(() => problemText(this.traces.moreError()), ...ngDevMode ? [{ debugName: "moreError" }] : /* istanbul ignore next */ []);
969
+ static ɵfac = i0.ɵɵngDeclareFactory({
970
+ minVersion: "12.0.0",
971
+ version: "22.2.4",
972
+ ngImport: i0,
973
+ type: TraceTable,
974
+ deps: [],
975
+ target: i0.ɵɵFactoryTarget.Component
976
+ });
977
+ static ɵcmp = i0.ɵɵngDeclareComponent({
978
+ minVersion: "17.0.0",
979
+ version: "22.2.4",
980
+ type: TraceTable,
981
+ isStandalone: true,
982
+ selector: "jc-trace-table",
983
+ inputs: {
984
+ query: {
985
+ classPropertyName: "query",
986
+ publicName: "query",
987
+ isSignal: true,
988
+ isRequired: false,
989
+ transformFunction: null
990
+ },
991
+ limit: {
992
+ classPropertyName: "limit",
993
+ publicName: "limit",
994
+ isSignal: true,
995
+ isRequired: false,
996
+ transformFunction: null
997
+ }
998
+ },
999
+ ngImport: i0,
1000
+ template: `
1001
+ @if (traces.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1002
+ @if (traces.first.error()) { <p class="jc-error">{{ error() }}</p> }
1003
+ @if (traces.first.hasValue()) {
1004
+ @if (rows().length === 0) { <p class="jc-muted">No traces match in this window.</p> }
1005
+ @else {
1006
+ <table class="jc-table">
1007
+ <thead><tr><th>Start</th><th>Root operation</th><th>Duration</th><th>Spans</th><th>Status</th><th>Instance</th></tr></thead>
1008
+ <tbody>
1009
+ @for (t of rows(); track t.traceId) {
1010
+ <tr>
1011
+ <td class="jc-nowrap">{{ t.startedAt | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1012
+ <td><a [routerLink]="['traces', t.traceId] | jcLink" [queryParams]="ctx.keep()">{{ t.rootService }} · {{ t.rootOperation }}</a>
1013
+ @if (t.services.length > 1) { <div class="jc-muted jc-small">{{ t.services.join(' → ') }}</div> }</td>
1014
+ <td class="jc-nowrap"><span class="jc-bar" [style.width.px]="barWidth(t.durationMs)"></span>{{ t.durationMs | duration }}</td>
1015
+ <td>{{ t.spanCount }}</td>
1016
+ <td>@if (t.errorCount > 0) { <span class="jc-badge jc-health-unhealthy">{{ t.errorCount }} error{{ t.errorCount > 1 ? 's' : '' }}</span> }
1017
+ @else { <span class="jc-badge jc-health-healthy">ok</span> }</td>
1018
+ <td class="jc-muted jc-small">{{ t.rootInstance ?? t.rootHost ?? '—' }}</td>
1019
+ </tr>
1020
+ }
1021
+ </tbody>
1022
+ </table>
1023
+ <div class="jc-more">
1024
+ <span class="jc-muted">{{ rows().length }} trace{{ rows().length === 1 ? '' : 's' }}{{ traces.hasMore() ? ', newest first' : '' }}</span>
1025
+ @if (traces.hasMore()) {
1026
+ <button mat-button (click)="traces.more()" [disabled]="traces.loadingMore()">{{ traces.loadingMore() ? 'Loading…' : 'Older traces' }}</button>
1027
+ }
1028
+ @if (traces.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1029
+ </div>
1030
+ }
1031
+ }`,
1032
+ isInline: true,
1033
+ dependencies: [
1034
+ {
1035
+ kind: "directive",
1036
+ type: RouterLink,
1037
+ selector: "[routerLink]",
1038
+ inputs: [
1039
+ "target",
1040
+ "queryParams",
1041
+ "fragment",
1042
+ "queryParamsHandling",
1043
+ "state",
1044
+ "info",
1045
+ "relativeTo",
1046
+ "preserveFragment",
1047
+ "skipLocationChange",
1048
+ "replaceUrl",
1049
+ "browserUrl",
1050
+ "routerLink"
1051
+ ]
1052
+ },
1053
+ {
1054
+ kind: "ngmodule",
1055
+ type: MatProgressBarModule
1056
+ },
1057
+ {
1058
+ kind: "component",
1059
+ type: i1.MatProgressBar,
1060
+ selector: "mat-progress-bar",
1061
+ inputs: [
1062
+ "color",
1063
+ "value",
1064
+ "bufferValue",
1065
+ "mode"
1066
+ ],
1067
+ outputs: ["animationEnd"],
1068
+ exportAs: ["matProgressBar"]
1069
+ },
1070
+ {
1071
+ kind: "ngmodule",
1072
+ type: MatButtonModule
1073
+ },
1074
+ {
1075
+ kind: "component",
1076
+ type: i2.MatButton,
1077
+ 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] ",
1078
+ inputs: ["matButton"],
1079
+ exportAs: ["matButton", "matAnchor"]
1080
+ },
1081
+ {
1082
+ kind: "pipe",
1083
+ type: LinkPipe,
1084
+ name: "jcLink"
1085
+ },
1086
+ {
1087
+ kind: "pipe",
1088
+ type: DatePipe,
1089
+ name: "date"
1090
+ },
1091
+ {
1092
+ kind: "pipe",
1093
+ type: DurationPipe,
1094
+ name: "duration"
1095
+ }
1096
+ ],
1097
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1098
+ });
1099
+ };
1100
+ i0.ɵɵngDeclareClassMetadata({
1101
+ minVersion: "12.0.0",
1102
+ version: "22.2.4",
1103
+ ngImport: i0,
1104
+ type: TraceTable,
1105
+ decorators: [{
1106
+ type: Component,
1107
+ args: [{
1108
+ selector: "jc-trace-table",
1109
+ changeDetection: ChangeDetectionStrategy.OnPush,
1110
+ imports: [
1111
+ LinkPipe,
1112
+ RouterLink,
1113
+ DatePipe,
1114
+ DurationPipe,
1115
+ MatProgressBarModule,
1116
+ MatButtonModule
1117
+ ],
1118
+ template: `
1119
+ @if (traces.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1120
+ @if (traces.first.error()) { <p class="jc-error">{{ error() }}</p> }
1121
+ @if (traces.first.hasValue()) {
1122
+ @if (rows().length === 0) { <p class="jc-muted">No traces match in this window.</p> }
1123
+ @else {
1124
+ <table class="jc-table">
1125
+ <thead><tr><th>Start</th><th>Root operation</th><th>Duration</th><th>Spans</th><th>Status</th><th>Instance</th></tr></thead>
1126
+ <tbody>
1127
+ @for (t of rows(); track t.traceId) {
1128
+ <tr>
1129
+ <td class="jc-nowrap">{{ t.startedAt | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1130
+ <td><a [routerLink]="['traces', t.traceId] | jcLink" [queryParams]="ctx.keep()">{{ t.rootService }} · {{ t.rootOperation }}</a>
1131
+ @if (t.services.length > 1) { <div class="jc-muted jc-small">{{ t.services.join(' → ') }}</div> }</td>
1132
+ <td class="jc-nowrap"><span class="jc-bar" [style.width.px]="barWidth(t.durationMs)"></span>{{ t.durationMs | duration }}</td>
1133
+ <td>{{ t.spanCount }}</td>
1134
+ <td>@if (t.errorCount > 0) { <span class="jc-badge jc-health-unhealthy">{{ t.errorCount }} error{{ t.errorCount > 1 ? 's' : '' }}</span> }
1135
+ @else { <span class="jc-badge jc-health-healthy">ok</span> }</td>
1136
+ <td class="jc-muted jc-small">{{ t.rootInstance ?? t.rootHost ?? '—' }}</td>
1137
+ </tr>
1138
+ }
1139
+ </tbody>
1140
+ </table>
1141
+ <div class="jc-more">
1142
+ <span class="jc-muted">{{ rows().length }} trace{{ rows().length === 1 ? '' : 's' }}{{ traces.hasMore() ? ', newest first' : '' }}</span>
1143
+ @if (traces.hasMore()) {
1144
+ <button mat-button (click)="traces.more()" [disabled]="traces.loadingMore()">{{ traces.loadingMore() ? 'Loading…' : 'Older traces' }}</button>
1145
+ }
1146
+ @if (traces.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1147
+ </div>
1148
+ }
1149
+ }`
1150
+ }]
1151
+ }],
1152
+ propDecorators: {
1153
+ query: [{
1154
+ type: i0.Input,
1155
+ args: [{
1156
+ isSignal: true,
1157
+ alias: "query",
1158
+ required: false
1159
+ }]
1160
+ }],
1161
+ limit: [{
1162
+ type: i0.Input,
1163
+ args: [{
1164
+ isSignal: true,
1165
+ alias: "limit",
1166
+ required: false
1167
+ }]
1168
+ }]
1169
+ }
1170
+ });
1171
+ var LogTable = class LogTable {
1172
+ ctx = inject(ContextService);
1173
+ api = inject(ApiService);
1174
+ query = input({}, ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
1175
+ compact = input(false, ...ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []);
1176
+ logs = cursorList(() => ({
1177
+ query: this.query(),
1178
+ env: this.ctx.environment(),
1179
+ range: this.ctx.range(),
1180
+ abs: this.ctx.absolute()
1181
+ }), (p) => ({
1182
+ environment: p.env,
1183
+ ...this.ctx.window(MAX_SEARCH_MS),
1184
+ limit: 200,
1185
+ ...p.query
1186
+ }), (q) => this.api.logs(q));
1187
+ rows = this.logs.items;
1188
+ error = computed(() => problemText(this.logs.first.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
1189
+ moreError = computed(() => problemText(this.logs.moreError()), ...ngDevMode ? [{ debugName: "moreError" }] : /* istanbul ignore next */ []);
1190
+ severityClass(n) {
1191
+ return n >= 17 ? "jc-health-unhealthy" : n >= 13 ? "jc-health-degraded" : n >= 9 ? "jc-health-healthy" : "jc-health-none";
1192
+ }
1193
+ static ɵfac = i0.ɵɵngDeclareFactory({
1194
+ minVersion: "12.0.0",
1195
+ version: "22.2.4",
1196
+ ngImport: i0,
1197
+ type: LogTable,
1198
+ deps: [],
1199
+ target: i0.ɵɵFactoryTarget.Component
1200
+ });
1201
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1202
+ minVersion: "17.0.0",
1203
+ version: "22.2.4",
1204
+ type: LogTable,
1205
+ isStandalone: true,
1206
+ selector: "jc-log-table",
1207
+ inputs: {
1208
+ query: {
1209
+ classPropertyName: "query",
1210
+ publicName: "query",
1211
+ isSignal: true,
1212
+ isRequired: false,
1213
+ transformFunction: null
1214
+ },
1215
+ compact: {
1216
+ classPropertyName: "compact",
1217
+ publicName: "compact",
1218
+ isSignal: true,
1219
+ isRequired: false,
1220
+ transformFunction: null
1221
+ }
1222
+ },
1223
+ ngImport: i0,
1224
+ template: `
1225
+ @if (logs.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1226
+ @if (logs.first.error()) { <p class="jc-error">{{ error() }}</p> }
1227
+ @if (logs.first.hasValue()) {
1228
+ @if (rows().length === 0) { <p class="jc-muted">No log records match in this window.</p> }
1229
+ @else {
1230
+ <table class="jc-table jc-logs">
1231
+ <thead><tr><th>Time</th><th>Severity</th>@if (!compact()) { <th>Service</th> }<th>Message</th>@if (!compact()) { <th>Trace</th> }</tr></thead>
1232
+ <tbody>
1233
+ @for (l of rows(); track $index) {
1234
+ <tr>
1235
+ <td class="jc-nowrap">{{ l.timestamp | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1236
+ <td><span class="jc-badge" [class]="severityClass(l.severityNumber)">{{ l.severity || l.severityNumber }}</span></td>
1237
+ @if (!compact()) { <td class="jc-nowrap">{{ l.service }}@if (l.instance) {<div class="jc-muted jc-small">{{ l.instance }}</div>}</td> }
1238
+ <td><pre class="jc-body">{{ l.body }}@if (l.bodyTruncated) {…}</pre></td>
1239
+ @if (!compact()) { <td class="jc-nowrap">
1240
+ @if (l.traceId) {
1241
+ <a [routerLink]="['traces', l.traceId] | jcLink" [queryParams]="ctx.keep(l.spanId ? { span: l.spanId } : {})">{{ l.spanId ? 'span' : 'trace' }} ↗</a>
1242
+ } @else { <span class="jc-muted">—</span> }
1243
+ </td> }
1244
+ </tr>
1245
+ }
1246
+ </tbody>
1247
+ </table>
1248
+ <div class="jc-more">
1249
+ <span class="jc-muted">{{ rows().length }} record{{ rows().length === 1 ? '' : 's' }}{{ logs.hasMore() ? ', newest first' : '' }}</span>
1250
+ @if (logs.hasMore()) {
1251
+ <button mat-button (click)="logs.more()" [disabled]="logs.loadingMore()">{{ logs.loadingMore() ? 'Loading…' : 'Older logs' }}</button>
1252
+ }
1253
+ @if (logs.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1254
+ </div>
1255
+ }
1256
+ }`,
1257
+ isInline: true,
1258
+ dependencies: [
1259
+ {
1260
+ kind: "directive",
1261
+ type: RouterLink,
1262
+ selector: "[routerLink]",
1263
+ inputs: [
1264
+ "target",
1265
+ "queryParams",
1266
+ "fragment",
1267
+ "queryParamsHandling",
1268
+ "state",
1269
+ "info",
1270
+ "relativeTo",
1271
+ "preserveFragment",
1272
+ "skipLocationChange",
1273
+ "replaceUrl",
1274
+ "browserUrl",
1275
+ "routerLink"
1276
+ ]
1277
+ },
1278
+ {
1279
+ kind: "ngmodule",
1280
+ type: MatProgressBarModule
1281
+ },
1282
+ {
1283
+ kind: "component",
1284
+ type: i1.MatProgressBar,
1285
+ selector: "mat-progress-bar",
1286
+ inputs: [
1287
+ "color",
1288
+ "value",
1289
+ "bufferValue",
1290
+ "mode"
1291
+ ],
1292
+ outputs: ["animationEnd"],
1293
+ exportAs: ["matProgressBar"]
1294
+ },
1295
+ {
1296
+ kind: "ngmodule",
1297
+ type: MatButtonModule
1298
+ },
1299
+ {
1300
+ kind: "component",
1301
+ type: i2.MatButton,
1302
+ 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] ",
1303
+ inputs: ["matButton"],
1304
+ exportAs: ["matButton", "matAnchor"]
1305
+ },
1306
+ {
1307
+ kind: "pipe",
1308
+ type: LinkPipe,
1309
+ name: "jcLink"
1310
+ },
1311
+ {
1312
+ kind: "pipe",
1313
+ type: DatePipe,
1314
+ name: "date"
1315
+ }
1316
+ ],
1317
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1318
+ });
1319
+ };
1320
+ i0.ɵɵngDeclareClassMetadata({
1321
+ minVersion: "12.0.0",
1322
+ version: "22.2.4",
1323
+ ngImport: i0,
1324
+ type: LogTable,
1325
+ decorators: [{
1326
+ type: Component,
1327
+ args: [{
1328
+ selector: "jc-log-table",
1329
+ changeDetection: ChangeDetectionStrategy.OnPush,
1330
+ imports: [
1331
+ LinkPipe,
1332
+ RouterLink,
1333
+ DatePipe,
1334
+ MatProgressBarModule,
1335
+ MatButtonModule
1336
+ ],
1337
+ template: `
1338
+ @if (logs.first.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1339
+ @if (logs.first.error()) { <p class="jc-error">{{ error() }}</p> }
1340
+ @if (logs.first.hasValue()) {
1341
+ @if (rows().length === 0) { <p class="jc-muted">No log records match in this window.</p> }
1342
+ @else {
1343
+ <table class="jc-table jc-logs">
1344
+ <thead><tr><th>Time</th><th>Severity</th>@if (!compact()) { <th>Service</th> }<th>Message</th>@if (!compact()) { <th>Trace</th> }</tr></thead>
1345
+ <tbody>
1346
+ @for (l of rows(); track $index) {
1347
+ <tr>
1348
+ <td class="jc-nowrap">{{ l.timestamp | date: 'MMM d, HH:mm:ss.SSS' }}</td>
1349
+ <td><span class="jc-badge" [class]="severityClass(l.severityNumber)">{{ l.severity || l.severityNumber }}</span></td>
1350
+ @if (!compact()) { <td class="jc-nowrap">{{ l.service }}@if (l.instance) {<div class="jc-muted jc-small">{{ l.instance }}</div>}</td> }
1351
+ <td><pre class="jc-body">{{ l.body }}@if (l.bodyTruncated) {…}</pre></td>
1352
+ @if (!compact()) { <td class="jc-nowrap">
1353
+ @if (l.traceId) {
1354
+ <a [routerLink]="['traces', l.traceId] | jcLink" [queryParams]="ctx.keep(l.spanId ? { span: l.spanId } : {})">{{ l.spanId ? 'span' : 'trace' }} ↗</a>
1355
+ } @else { <span class="jc-muted">—</span> }
1356
+ </td> }
1357
+ </tr>
1358
+ }
1359
+ </tbody>
1360
+ </table>
1361
+ <div class="jc-more">
1362
+ <span class="jc-muted">{{ rows().length }} record{{ rows().length === 1 ? '' : 's' }}{{ logs.hasMore() ? ', newest first' : '' }}</span>
1363
+ @if (logs.hasMore()) {
1364
+ <button mat-button (click)="logs.more()" [disabled]="logs.loadingMore()">{{ logs.loadingMore() ? 'Loading…' : 'Older logs' }}</button>
1365
+ }
1366
+ @if (logs.moreError()) { <span class="jc-error">{{ moreError() }}</span> }
1367
+ </div>
1368
+ }
1369
+ }`
1370
+ }]
1371
+ }],
1372
+ propDecorators: {
1373
+ query: [{
1374
+ type: i0.Input,
1375
+ args: [{
1376
+ isSignal: true,
1377
+ alias: "query",
1378
+ required: false
1379
+ }]
1380
+ }],
1381
+ compact: [{
1382
+ type: i0.Input,
1383
+ args: [{
1384
+ isSignal: true,
1385
+ alias: "compact",
1386
+ required: false
1387
+ }]
1388
+ }]
1389
+ }
1390
+ });
1391
+ var MetricExplorer = class MetricExplorer {
1392
+ ctx = inject(ContextService);
1393
+ api = inject(ApiService);
1394
+ scope = input({}, ...ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []);
1395
+ initial = input(null, ...ngDevMode ? [{ debugName: "initial" }] : /* istanbul ignore next */ []);
1396
+ metrics = apiResource({
1397
+ params: () => ({
1398
+ scope: this.scope(),
1399
+ env: this.ctx.environment(),
1400
+ range: this.ctx.range(),
1401
+ abs: this.ctx.absolute()
1402
+ }),
1403
+ stream: ({ params }) => this.api.metrics({
1404
+ environment: params.env,
1405
+ ...this.ctx.window(),
1406
+ ...params.scope
1407
+ })
1408
+ });
1409
+ names = computed(() => this.metrics.value()?.items ?? [], ...ngDevMode ? [{ debugName: "names" }] : /* istanbul ignore next */ []);
1410
+ selected = linkedSignal(() => {
1411
+ const names = this.names();
1412
+ return names.find((m) => m.name === this.initial())?.name ?? names[0]?.name ?? null;
1413
+ }, ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
1414
+ series = apiResource({
1415
+ params: () => this.selected() ? {
1416
+ name: this.selected(),
1417
+ scope: this.scope(),
1418
+ env: this.ctx.environment(),
1419
+ range: this.ctx.range(),
1420
+ abs: this.ctx.absolute()
1421
+ } : void 0,
1422
+ stream: ({ params }) => {
1423
+ this.window = this.ctx.window();
1424
+ return this.api.series({
1425
+ name: params.name,
1426
+ environment: params.env,
1427
+ ...this.window,
1428
+ ...params.scope
1429
+ });
1430
+ }
1431
+ });
1432
+ window = {
1433
+ from: "",
1434
+ to: ""
1435
+ };
1436
+ points = computed(() => {
1437
+ const s = this.series.value();
1438
+ return s ? withGaps(s.points, this.window.from, this.window.to, s.stepSeconds) : [];
1439
+ }, ...ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []);
1440
+ title = computed(() => {
1441
+ const s = this.series.value();
1442
+ if (!s) return "";
1443
+ const what = s.aggregation === "rate" ? "rate per second" : "average";
1444
+ return `${s.name} — ${what}${s.unit ? ` (${s.unit}${s.aggregation === "rate" ? "/s" : ""})` : ""}`;
1445
+ }, ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
1446
+ metricsError = computed(() => problemText(this.metrics.error()), ...ngDevMode ? [{ debugName: "metricsError" }] : /* istanbul ignore next */ []);
1447
+ static ɵfac = i0.ɵɵngDeclareFactory({
1448
+ minVersion: "12.0.0",
1449
+ version: "22.2.4",
1450
+ ngImport: i0,
1451
+ type: MetricExplorer,
1452
+ deps: [],
1453
+ target: i0.ɵɵFactoryTarget.Component
1454
+ });
1455
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1456
+ minVersion: "17.0.0",
1457
+ version: "22.2.4",
1458
+ type: MetricExplorer,
1459
+ isStandalone: true,
1460
+ selector: "jc-metric-explorer",
1461
+ inputs: {
1462
+ scope: {
1463
+ classPropertyName: "scope",
1464
+ publicName: "scope",
1465
+ isSignal: true,
1466
+ isRequired: false,
1467
+ transformFunction: null
1468
+ },
1469
+ initial: {
1470
+ classPropertyName: "initial",
1471
+ publicName: "initial",
1472
+ isSignal: true,
1473
+ isRequired: false,
1474
+ transformFunction: null
1475
+ }
1476
+ },
1477
+ ngImport: i0,
1478
+ template: `
1479
+ @if (metrics.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1480
+ @if (metrics.error()) { <p class="jc-error">{{ metricsError() }}</p> }
1481
+ @if (metrics.hasValue()) {
1482
+ @if (names().length === 0) { <p class="jc-muted">No metrics reported in this window.</p> }
1483
+ @else {
1484
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide">
1485
+ <mat-label>Metric</mat-label>
1486
+ <mat-select [value]="selected()" (valueChange)="selected.set($event)">
1487
+ @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> }
1488
+ </mat-select>
1489
+ </mat-form-field>
1490
+ @if (series.value(); as s) {
1491
+ <jc-chart [title]="title()" [series]="[{ name: s.name, points: points() }]" />
1492
+ }
1493
+ }
1494
+ }`,
1495
+ isInline: true,
1496
+ dependencies: [
1497
+ {
1498
+ kind: "ngmodule",
1499
+ type: FormsModule
1500
+ },
1501
+ {
1502
+ kind: "ngmodule",
1503
+ type: MatSelectModule
1504
+ },
1505
+ {
1506
+ kind: "component",
1507
+ type: i2$1.MatFormField,
1508
+ selector: "mat-form-field",
1509
+ inputs: [
1510
+ "hideRequiredMarker",
1511
+ "color",
1512
+ "floatLabel",
1513
+ "appearance",
1514
+ "subscriptSizing",
1515
+ "hintLabel"
1516
+ ],
1517
+ exportAs: ["matFormField"]
1518
+ },
1519
+ {
1520
+ kind: "directive",
1521
+ type: i2$1.MatLabel,
1522
+ selector: "mat-label"
1523
+ },
1524
+ {
1525
+ kind: "component",
1526
+ type: i2$1.MatSelect,
1527
+ selector: "mat-select",
1528
+ inputs: [
1529
+ "aria-describedby",
1530
+ "panelClass",
1531
+ "disabled",
1532
+ "disableRipple",
1533
+ "tabIndex",
1534
+ "hideSingleSelectionIndicator",
1535
+ "placeholder",
1536
+ "required",
1537
+ "multiple",
1538
+ "disableOptionCentering",
1539
+ "compareWith",
1540
+ "value",
1541
+ "aria-label",
1542
+ "aria-labelledby",
1543
+ "errorStateMatcher",
1544
+ "typeaheadDebounceInterval",
1545
+ "sortComparator",
1546
+ "id",
1547
+ "panelWidth",
1548
+ "canSelectNullableOptions"
1549
+ ],
1550
+ outputs: [
1551
+ "openedChange",
1552
+ "opened",
1553
+ "closed",
1554
+ "selectionChange",
1555
+ "valueChange"
1556
+ ],
1557
+ exportAs: ["matSelect"]
1558
+ },
1559
+ {
1560
+ kind: "component",
1561
+ type: i2$1.MatOption,
1562
+ selector: "mat-option",
1563
+ inputs: [
1564
+ "value",
1565
+ "id",
1566
+ "disabled"
1567
+ ],
1568
+ outputs: ["onSelectionChange"],
1569
+ exportAs: ["matOption"]
1570
+ },
1571
+ {
1572
+ kind: "ngmodule",
1573
+ type: MatProgressBarModule
1574
+ },
1575
+ {
1576
+ kind: "component",
1577
+ type: i1.MatProgressBar,
1578
+ selector: "mat-progress-bar",
1579
+ inputs: [
1580
+ "color",
1581
+ "value",
1582
+ "bufferValue",
1583
+ "mode"
1584
+ ],
1585
+ outputs: ["animationEnd"],
1586
+ exportAs: ["matProgressBar"]
1587
+ },
1588
+ {
1589
+ kind: "component",
1590
+ type: Chart,
1591
+ selector: "jc-chart",
1592
+ inputs: [
1593
+ "title",
1594
+ "series",
1595
+ "format"
1596
+ ]
1597
+ }
1598
+ ],
1599
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1600
+ });
1601
+ };
1602
+ i0.ɵɵngDeclareClassMetadata({
1603
+ minVersion: "12.0.0",
1604
+ version: "22.2.4",
1605
+ ngImport: i0,
1606
+ type: MetricExplorer,
1607
+ decorators: [{
1608
+ type: Component,
1609
+ args: [{
1610
+ selector: "jc-metric-explorer",
1611
+ changeDetection: ChangeDetectionStrategy.OnPush,
1612
+ imports: [
1613
+ FormsModule,
1614
+ MatSelectModule,
1615
+ MatProgressBarModule,
1616
+ Chart
1617
+ ],
1618
+ template: `
1619
+ @if (metrics.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1620
+ @if (metrics.error()) { <p class="jc-error">{{ metricsError() }}</p> }
1621
+ @if (metrics.hasValue()) {
1622
+ @if (names().length === 0) { <p class="jc-muted">No metrics reported in this window.</p> }
1623
+ @else {
1624
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide">
1625
+ <mat-label>Metric</mat-label>
1626
+ <mat-select [value]="selected()" (valueChange)="selected.set($event)">
1627
+ @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> }
1628
+ </mat-select>
1629
+ </mat-form-field>
1630
+ @if (series.value(); as s) {
1631
+ <jc-chart [title]="title()" [series]="[{ name: s.name, points: points() }]" />
1632
+ }
1633
+ }
1634
+ }`
1635
+ }]
1636
+ }],
1637
+ propDecorators: {
1638
+ scope: [{
1639
+ type: i0.Input,
1640
+ args: [{
1641
+ isSignal: true,
1642
+ alias: "scope",
1643
+ required: false
1644
+ }]
1645
+ }],
1646
+ initial: [{
1647
+ type: i0.Input,
1648
+ args: [{
1649
+ isSignal: true,
1650
+ alias: "initial",
1651
+ required: false
1652
+ }]
1653
+ }]
1654
+ }
1655
+ });
1656
+ var ServiceStatsCharts = class ServiceStatsCharts {
1657
+ ctx = inject(ContextService);
1658
+ api = inject(ApiService);
1659
+ query = input.required(...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
1660
+ window = {
1661
+ from: "",
1662
+ to: ""
1663
+ };
1664
+ percent = (v) => `${Math.round(v * 1e3) / 10} %`;
1665
+ ms = (v) => `${Math.round(v)} ms`;
1666
+ stats = apiResource({
1667
+ params: () => ({
1668
+ query: this.query(),
1669
+ env: this.ctx.environment(),
1670
+ range: this.ctx.range(),
1671
+ abs: this.ctx.absolute()
1672
+ }),
1673
+ stream: ({ params }) => {
1674
+ this.window = this.ctx.window();
1675
+ return this.api.serviceStats({
1676
+ environment: params.env,
1677
+ ...this.window,
1678
+ ...params.query
1679
+ });
1680
+ }
1681
+ });
1682
+ rates() {
1683
+ const points = this.stats.value()?.points ?? [];
1684
+ const requests = {
1685
+ title: "Requests per second",
1686
+ name: "requests/s",
1687
+ points: this.pick("requestsPerSecond")
1688
+ };
1689
+ const messages = {
1690
+ title: "Messages per second",
1691
+ name: "messages/s",
1692
+ points: this.pick("messagesPerSecond")
1693
+ };
1694
+ if (!points.some((p) => p.messagesPerSecond > 0)) return [requests];
1695
+ return points.some((p) => p.requestsPerSecond > 0) ? [requests, messages] : [messages];
1696
+ }
1697
+ pick(field) {
1698
+ const s = this.stats.value();
1699
+ return s ? withGaps(s.points.map((p) => ({
1700
+ at: p.at,
1701
+ value: p[field]
1702
+ })), this.window.from, this.window.to, s.stepSeconds) : [];
1703
+ }
1704
+ static ɵfac = i0.ɵɵngDeclareFactory({
1705
+ minVersion: "12.0.0",
1706
+ version: "22.2.4",
1707
+ ngImport: i0,
1708
+ type: ServiceStatsCharts,
1709
+ deps: [],
1710
+ target: i0.ɵɵFactoryTarget.Component
1711
+ });
1712
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1713
+ minVersion: "17.0.0",
1714
+ version: "22.2.4",
1715
+ type: ServiceStatsCharts,
1716
+ isStandalone: true,
1717
+ selector: "jc-service-stats",
1718
+ inputs: { query: {
1719
+ classPropertyName: "query",
1720
+ publicName: "query",
1721
+ isSignal: true,
1722
+ isRequired: true,
1723
+ transformFunction: null
1724
+ } },
1725
+ ngImport: i0,
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]="rates().length > 1 ? 'Requests and messages per second' : rates()[0].title" [series]="rates()" />
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
+ isInline: true,
1736
+ dependencies: [
1737
+ {
1738
+ kind: "component",
1739
+ type: Chart,
1740
+ selector: "jc-chart",
1741
+ inputs: [
1742
+ "title",
1743
+ "series",
1744
+ "format"
1745
+ ]
1746
+ },
1747
+ {
1748
+ kind: "ngmodule",
1749
+ type: MatProgressBarModule
1750
+ },
1751
+ {
1752
+ kind: "component",
1753
+ type: i1.MatProgressBar,
1754
+ selector: "mat-progress-bar",
1755
+ inputs: [
1756
+ "color",
1757
+ "value",
1758
+ "bufferValue",
1759
+ "mode"
1760
+ ],
1761
+ outputs: ["animationEnd"],
1762
+ exportAs: ["matProgressBar"]
1763
+ }
1764
+ ],
1765
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1766
+ });
1767
+ };
1768
+ i0.ɵɵngDeclareClassMetadata({
1769
+ minVersion: "12.0.0",
1770
+ version: "22.2.4",
1771
+ ngImport: i0,
1772
+ type: ServiceStatsCharts,
1773
+ decorators: [{
1774
+ type: Component,
1775
+ args: [{
1776
+ selector: "jc-service-stats",
1777
+ changeDetection: ChangeDetectionStrategy.OnPush,
1778
+ imports: [Chart, MatProgressBarModule],
1779
+ template: `
1780
+ @if (stats.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
1781
+ @if (stats.hasValue()) {
1782
+ <div class="jc-grid-3">
1783
+ <jc-chart [title]="rates().length > 1 ? 'Requests and messages per second' : rates()[0].title" [series]="rates()" />
1784
+ <jc-chart title="Error rate" [series]="[{ name: 'errors', points: pick('errorRate') }]" [format]="percent" />
1785
+ <jc-chart title="p95 latency" [series]="[{ name: 'p95', points: pick('p95Ms') }]" [format]="ms" />
1786
+ </div>
1787
+ }`
1788
+ }]
1789
+ }],
1790
+ propDecorators: { query: [{
1791
+ type: i0.Input,
1792
+ args: [{
1793
+ isSignal: true,
1794
+ alias: "query",
1795
+ required: true
1796
+ }]
1797
+ }] }
1798
+ });
1799
+ 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 };
1800
+
1801
+ //# sourceMappingURL=juice-js-observability-telemetry-B5jMJYfn.mjs.map