@juice-js/observability 1.1.0-alpha.18 → 1.1.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,726 +0,0 @@
1
- import { ApiService, ContextService, LinkPipe, ObservabilityLinks } from "./juice-js-observability.mjs";
2
- import { S as apiResource, _ as PercentPipe, a as TraceTable, d as HealthBadge, g as DurationPipe, h as AgoPipe, i as ServiceStatsCharts, l as ConfidenceBadge, n as LogTable, o as problemText, r as MetricExplorer, u as ContextBar, v as ReasonPipe } from "./juice-js-observability-telemetry-BftWSOaQ.mjs";
3
- import * as i0 from "@angular/core";
4
- import { ChangeDetectionStrategy, Component, computed, inject, input } from "@angular/core";
5
- import { Router, RouterLink } from "@angular/router";
6
- import * as i3 from "@angular/material/button";
7
- import { MatButtonModule } from "@angular/material/button";
8
- import * as i2 from "@angular/material/progress-bar";
9
- import { MatProgressBarModule } from "@angular/material/progress-bar";
10
- import * as i1 from "@angular/material/tabs";
11
- import { MatTabsModule } from "@angular/material/tabs";
12
- const TABS = [
13
- "overview",
14
- "operations",
15
- "dependencies",
16
- "traces",
17
- "logs",
18
- "metrics"
19
- ];
20
- var ServicePage = class ServicePage {
21
- ctx = inject(ContextService);
22
- api = inject(ApiService);
23
- router = inject(Router);
24
- links = inject(ObservabilityLinks);
25
- id = input.required(...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
26
- tab = input(...ngDevMode ? [void 0, { debugName: "tab" }] : /* istanbul ignore next */ []);
27
- op = input(...ngDevMode ? [void 0, { debugName: "op" }] : /* istanbul ignore next */ []);
28
- status = input(...ngDevMode ? [void 0, { debugName: "status" }] : /* istanbul ignore next */ []);
29
- slow = input(...ngDevMode ? [void 0, { debugName: "slow" }] : /* istanbul ignore next */ []);
30
- instance = input(...ngDevMode ? [void 0, { debugName: "instance" }] : /* istanbul ignore next */ []);
31
- dependency = input(...ngDevMode ? [void 0, { debugName: "dependency" }] : /* istanbul ignore next */ []);
32
- tabIndex = computed(() => Math.max(0, TABS.indexOf(this.tab() ?? "overview")), ...ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []);
33
- operation = computed(() => this.op() ?? null, ...ngDevMode ? [{ debugName: "operation" }] : /* istanbul ignore next */ []);
34
- instanceFilter = computed(() => this.instance() ?? null, ...ngDevMode ? [{ debugName: "instanceFilter" }] : /* istanbul ignore next */ []);
35
- service = apiResource({
36
- params: () => this.id(),
37
- stream: ({ params }) => this.api.resource(params)
38
- });
39
- topology = apiResource({
40
- params: () => this.ctx.environment(),
41
- stream: ({ params }) => this.api.topology(params || null)
42
- });
43
- operations = apiResource({
44
- params: () => ({
45
- id: this.id(),
46
- env: this.ctx.environment(),
47
- range: this.ctx.range(),
48
- abs: this.ctx.absolute(),
49
- on: this.tabIndex() === 1
50
- }),
51
- stream: ({ params }) => this.api.operations({
52
- serviceId: params.id,
53
- environment: params.env,
54
- ...this.ctx.window(),
55
- limit: 200
56
- })
57
- });
58
- error = computed(() => problemText(this.service.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
59
- health = computed(() => (this.service.value()?.health ?? []).filter((h) => h.subjectType === "service"), ...ngDevMode ? [{ debugName: "health" }] : /* istanbul ignore next */ []);
60
- instances = computed(() => {
61
- const env = this.ctx.environment();
62
- return (this.service.value()?.instances ?? []).filter((i) => !env || i.environment === env);
63
- }, ...ngDevMode ? [{ debugName: "instances" }] : /* istanbul ignore next */ []);
64
- nodes = computed(() => new Map((this.topology.value()?.nodes ?? []).map((n) => [n.id, n])), ...ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []);
65
- edgesWhere(match) {
66
- const other = match === "source" ? "target" : "source";
67
- return (this.topology.value()?.edges ?? []).filter((e) => e[match] === this.id()).map((edge) => {
68
- const node = this.nodes().get(edge[other]);
69
- return {
70
- edge,
71
- name: node?.displayName ?? edge[other],
72
- type: node?.type ?? "",
73
- link: this.links.to(node?.type === "service" ? "services" : "resources", edge[other])
74
- };
75
- });
76
- }
77
- downstream = computed(() => this.edgesWhere("source"), ...ngDevMode ? [{ debugName: "downstream" }] : /* istanbul ignore next */ []);
78
- upstream = computed(() => this.edgesWhere("target"), ...ngDevMode ? [{ debugName: "upstream" }] : /* istanbul ignore next */ []);
79
- dependencyOf(reason) {
80
- const name = reason.match(/^(?:critical_)?dependency_(?:failed|unknown):(.+)$/)?.[1];
81
- if (!name) return null;
82
- const node = [...this.nodes().values()].find((n) => n.type === "service" && (n.displayName === name || name.endsWith(`/${n.displayName}`)));
83
- return node ? { id: node.id } : null;
84
- }
85
- traceQuery = computed(() => ({
86
- serviceId: this.id(),
87
- operation: this.op(),
88
- status: this.status(),
89
- minDurationMs: this.slow(),
90
- instanceId: this.instance(),
91
- dependencyId: this.dependency()
92
- }), ...ngDevMode ? [{ debugName: "traceQuery" }] : /* istanbul ignore next */ []);
93
- traceFilters = computed(() => {
94
- const filters = [];
95
- if (this.op()) filters.push({
96
- label: `operation: ${this.op()}`,
97
- clear: { op: null }
98
- });
99
- if (this.status()) filters.push({
100
- label: `status: ${this.status()}`,
101
- clear: { status: null }
102
- });
103
- if (this.slow()) filters.push({
104
- label: `slower than ${Math.round(Number(this.slow()))} ms`,
105
- clear: { slow: null }
106
- });
107
- if (this.instance()) filters.push({
108
- label: "one instance",
109
- clear: { instance: null }
110
- });
111
- if (this.dependency()) filters.push({
112
- label: "through one dependency",
113
- clear: { dependency: null }
114
- });
115
- return filters;
116
- }, ...ngDevMode ? [{ debugName: "traceFilters" }] : /* istanbul ignore next */ []);
117
- openTab(index) {
118
- this.router.navigate([], {
119
- queryParams: { tab: TABS[index] },
120
- queryParamsHandling: "merge"
121
- });
122
- }
123
- static ɵfac = i0.ɵɵngDeclareFactory({
124
- minVersion: "12.0.0",
125
- version: "22.2.4",
126
- ngImport: i0,
127
- type: ServicePage,
128
- deps: [],
129
- target: i0.ɵɵFactoryTarget.Component
130
- });
131
- static ɵcmp = i0.ɵɵngDeclareComponent({
132
- minVersion: "17.0.0",
133
- version: "22.2.4",
134
- type: ServicePage,
135
- isStandalone: true,
136
- selector: "ng-component",
137
- inputs: {
138
- id: {
139
- classPropertyName: "id",
140
- publicName: "id",
141
- isSignal: true,
142
- isRequired: true,
143
- transformFunction: null
144
- },
145
- tab: {
146
- classPropertyName: "tab",
147
- publicName: "tab",
148
- isSignal: true,
149
- isRequired: false,
150
- transformFunction: null
151
- },
152
- op: {
153
- classPropertyName: "op",
154
- publicName: "op",
155
- isSignal: true,
156
- isRequired: false,
157
- transformFunction: null
158
- },
159
- status: {
160
- classPropertyName: "status",
161
- publicName: "status",
162
- isSignal: true,
163
- isRequired: false,
164
- transformFunction: null
165
- },
166
- slow: {
167
- classPropertyName: "slow",
168
- publicName: "slow",
169
- isSignal: true,
170
- isRequired: false,
171
- transformFunction: null
172
- },
173
- instance: {
174
- classPropertyName: "instance",
175
- publicName: "instance",
176
- isSignal: true,
177
- isRequired: false,
178
- transformFunction: null
179
- },
180
- dependency: {
181
- classPropertyName: "dependency",
182
- publicName: "dependency",
183
- isSignal: true,
184
- isRequired: false,
185
- transformFunction: null
186
- }
187
- },
188
- ngImport: i0,
189
- template: `
190
- <jc-context-bar>
191
- <a [routerLink]="['services'] | jcLink" [queryParams]="ctx.keep()" class="jc-crumb">Services</a>
192
- <span class="jc-crumb-sep">›</span>
193
- <h1>{{ service.value()?.key ?? '…' }}</h1>
194
- @for (l of service.value()?.links ?? []; track l.url) { <a mat-button [href]="l.url" target="_blank" rel="noopener">{{ l.title }} ↗</a> }
195
- </jc-context-bar>
196
- @if (service.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
197
- @if (service.error()) { <p class="jc-error">{{ error() }}</p> }
198
-
199
- @if (service.value(); as s) {
200
- <div class="jc-health-strip">
201
- @for (h of health(); track h.environment) {
202
- <div class="jc-card jc-health-card" [class.jc-selected]="h.environment === ctx.environment()">
203
- <div class="jc-row"><strong>{{ h.environment ?? 'no environment' }}</strong><jc-health [state]="h.state" [stale]="h.stale" /></div>
204
- <ul class="jc-reasons">
205
- @for (r of h.reasons; track r) {
206
- <li>
207
- @if (dependencyOf(r); as dep) {
208
- {{ r | reason }} — <a [routerLink]="['services', dep.id] | jcLink" [queryParams]="ctx.keep()">open</a>
209
- } @else { {{ r | reason }} }
210
- </li>
211
- }
212
- </ul>
213
- <div class="jc-muted jc-small">since {{ h.since | ago }} · evaluated {{ h.evaluatedAt | ago }}</div>
214
- @if (h.environment && h.environment !== ctx.environment()) {
215
- <button mat-button (click)="ctx.set({ env: h.environment })">Investigate {{ h.environment }}</button>
216
- }
217
- </div>
218
- } @empty { <p class="jc-muted">No health evaluated yet: no data.</p> }
219
- </div>
220
-
221
- <mat-tab-group [selectedIndex]="tabIndex()" (selectedIndexChange)="openTab($event)" animationDuration="0ms" mat-stretch-tabs="false">
222
- <mat-tab label="Overview">
223
- @if (tabIndex() === 0) {
224
- <jc-service-stats [query]="{ serviceId: id() }" />
225
- <h3>Instances</h3>
226
- <table class="jc-table">
227
- <thead><tr><th>Instance</th><th>Environment</th><th>Health</th><th>Version</th><th>State</th><th>Last seen</th><th></th></tr></thead>
228
- <tbody>
229
- @for (i of instances(); track i.id) {
230
- <tr>
231
- <td>{{ i.instanceKey }}@if (i.keySource !== 'Observed') { <span class="jc-muted jc-small" title="No service.instance.id: derived from host and process"> (derived)</span> }</td>
232
- <td>{{ i.environment }}</td>
233
- <td><jc-health [state]="i.health" /></td>
234
- <td>{{ i.serviceVersion ?? '—' }}</td>
235
- <td>{{ i.state }}</td>
236
- <td>{{ i.lastSeen | ago }}</td>
237
- <td class="jc-nowrap">
238
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', instance: i.id })">traces</a> ·
239
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'logs', instance: i.id })">logs</a>
240
- @if (i.hostResourceId) { · <a [routerLink]="['resources', i.hostResourceId] | jcLink" [queryParams]="ctx.keep()">host</a> }
241
- </td>
242
- </tr>
243
- } @empty { <tr><td colspan="7" class="jc-muted">No instances.</td></tr> }
244
- </tbody>
245
- </table>
246
- }
247
- </mat-tab>
248
-
249
- <mat-tab label="Operations">
250
- @if (tabIndex() === 1) {
251
- @if (operations.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
252
- <table class="jc-table">
253
- <thead><tr><th>Operation</th><th>Kind</th><th>Requests</th><th title="Average over the selected time range. The chart shows each moment, so a burst reads higher there.">Avg/s</th><th>Errors</th><th>p50</th><th>p95</th><th>p99</th><th>Last seen</th><th></th></tr></thead>
254
- <tbody>
255
- @for (o of operations.value()?.items ?? []; track o.name + o.kind) {
256
- <tr [class.jc-selected]="o.name === operation()">
257
- <td><a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'operations', op: o.name })">{{ o.name }}</a></td>
258
- <td>{{ o.kind }}</td>
259
- <td>{{ o.count }}</td>
260
- <td>{{ o.requestsPerSecond.toFixed(2) }}</td>
261
- <td [class.jc-bad]="o.errorRate >= 0.05">{{ o.errorRate | percent2 }}</td>
262
- <td>{{ o.p50Ms | duration }}</td><td>{{ o.p95Ms | duration }}</td><td>{{ o.p99Ms | duration }}</td>
263
- <td>{{ o.lastSeen | ago }}</td>
264
- <td class="jc-nowrap">
265
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', op: o.name, status: 'error' })">error traces</a> ·
266
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', op: o.name, slow: o.p95Ms })">slow traces</a>
267
- </td>
268
- </tr>
269
- } @empty { <tr><td colspan="10" class="jc-muted">No operations in this window.</td></tr> }
270
- </tbody>
271
- </table>
272
- @if (operation(); as op) {
273
- <h3>{{ op }}</h3>
274
- <jc-service-stats [query]="{ serviceId: id(), operation: op }" />
275
- }
276
- }
277
- </mat-tab>
278
-
279
- <mat-tab label="Dependencies">
280
- @if (tabIndex() === 2) {
281
- <h3>Calls and uses</h3>
282
- <table class="jc-table">
283
- <thead><tr><th>Target</th><th>Relation</th><th>Evidence</th><th>Spans seen</th><th>Last seen</th><th></th></tr></thead>
284
- <tbody>
285
- @for (d of downstream(); track d.edge.id) {
286
- <tr>
287
- <td><a [routerLink]="d.link" [queryParams]="ctx.keep()">{{ d.name }}</a>&nbsp;<span class="jc-muted jc-small">{{ d.type }}</span></td>
288
- <td>{{ d.edge.relation }}</td>
289
- <td><jc-confidence [confidence]="d.edge.confidence" /></td>
290
- <td>{{ d.edge.spanCount }}</td>
291
- <td>{{ d.edge.lastSeen | ago }}</td>
292
- <td><a [routerLink]="['topology'] | jcLink" [queryParams]="ctx.keep({ edge: d.edge.id })">evidence</a> ·
293
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', dependency: d.edge.id })">traces</a></td>
294
- </tr>
295
- } @empty { <tr><td colspan="6" class="jc-muted">No downstream dependencies seen.</td></tr> }
296
- </tbody>
297
- </table>
298
- <h3>Called by</h3>
299
- <table class="jc-table">
300
- <tbody>
301
- @for (d of upstream(); track d.edge.id) {
302
- <tr><td><a [routerLink]="d.link" [queryParams]="ctx.keep()">{{ d.name }}</a></td><td>{{ d.edge.relation }}</td>
303
- <td><jc-confidence [confidence]="d.edge.confidence" /></td><td>{{ d.edge.lastSeen | ago }}</td>
304
- <td><a [routerLink]="['topology'] | jcLink" [queryParams]="ctx.keep({ edge: d.edge.id })">evidence</a></td></tr>
305
- } @empty { <tr><td class="jc-muted">No callers seen.</td></tr> }
306
- </tbody>
307
- </table>
308
- }
309
- </mat-tab>
310
-
311
- <mat-tab label="Traces">
312
- @if (tabIndex() === 3) {
313
- <div class="jc-filters">
314
- @for (f of traceFilters(); track f.label) { <span class="jc-chip">{{ f.label }} <a [routerLink]="[]" [queryParams]="f.clear" queryParamsHandling="merge" [attr.aria-label]="'Remove ' + f.label">×</a></span> }
315
- </div>
316
- <jc-trace-table [query]="traceQuery()" />
317
- }
318
- </mat-tab>
319
-
320
- <mat-tab label="Logs">
321
- @if (tabIndex() === 4) { <jc-log-table [query]="{ serviceId: id(), instanceId: instanceFilter() }" /> }
322
- </mat-tab>
323
-
324
- <mat-tab label="Metrics">
325
- @if (tabIndex() === 5) { <jc-metric-explorer [scope]="{ serviceId: id(), instanceId: instanceFilter() }" /> }
326
- </mat-tab>
327
- </mat-tab-group>
328
- }`,
329
- isInline: true,
330
- dependencies: [
331
- {
332
- kind: "directive",
333
- type: RouterLink,
334
- selector: "[routerLink]",
335
- inputs: [
336
- "target",
337
- "queryParams",
338
- "fragment",
339
- "queryParamsHandling",
340
- "state",
341
- "info",
342
- "relativeTo",
343
- "preserveFragment",
344
- "skipLocationChange",
345
- "replaceUrl",
346
- "browserUrl",
347
- "routerLink"
348
- ]
349
- },
350
- {
351
- kind: "ngmodule",
352
- type: MatTabsModule
353
- },
354
- {
355
- kind: "component",
356
- type: i1.MatTab,
357
- selector: "mat-tab",
358
- inputs: [
359
- "disabled",
360
- "label",
361
- "aria-label",
362
- "aria-labelledby",
363
- "labelClass",
364
- "bodyClass",
365
- "id"
366
- ],
367
- exportAs: ["matTab"]
368
- },
369
- {
370
- kind: "component",
371
- type: i1.MatTabGroup,
372
- selector: "mat-tab-group",
373
- inputs: [
374
- "color",
375
- "fitInkBarToContent",
376
- "mat-stretch-tabs",
377
- "mat-align-tabs",
378
- "dynamicHeight",
379
- "selectedIndex",
380
- "headerPosition",
381
- "animationDuration",
382
- "contentTabIndex",
383
- "disablePagination",
384
- "disableRipple",
385
- "preserveContent",
386
- "backgroundColor",
387
- "aria-label",
388
- "aria-labelledby"
389
- ],
390
- outputs: [
391
- "selectedIndexChange",
392
- "focusChange",
393
- "animationDone",
394
- "selectedTabChange"
395
- ],
396
- exportAs: ["matTabGroup"]
397
- },
398
- {
399
- kind: "ngmodule",
400
- type: MatProgressBarModule
401
- },
402
- {
403
- kind: "component",
404
- type: i2.MatProgressBar,
405
- selector: "mat-progress-bar",
406
- inputs: [
407
- "color",
408
- "value",
409
- "bufferValue",
410
- "mode"
411
- ],
412
- outputs: ["animationEnd"],
413
- exportAs: ["matProgressBar"]
414
- },
415
- {
416
- kind: "ngmodule",
417
- type: MatButtonModule
418
- },
419
- {
420
- kind: "component",
421
- type: i3.MatButton,
422
- 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] ",
423
- inputs: ["matButton"],
424
- exportAs: ["matButton", "matAnchor"]
425
- },
426
- {
427
- kind: "component",
428
- type: ContextBar,
429
- selector: "jc-context-bar"
430
- },
431
- {
432
- kind: "component",
433
- type: HealthBadge,
434
- selector: "jc-health",
435
- inputs: ["state", "stale"]
436
- },
437
- {
438
- kind: "component",
439
- type: ConfidenceBadge,
440
- selector: "jc-confidence",
441
- inputs: ["confidence"]
442
- },
443
- {
444
- kind: "component",
445
- type: TraceTable,
446
- selector: "jc-trace-table",
447
- inputs: ["query", "limit"]
448
- },
449
- {
450
- kind: "component",
451
- type: LogTable,
452
- selector: "jc-log-table",
453
- inputs: ["query", "compact"]
454
- },
455
- {
456
- kind: "component",
457
- type: MetricExplorer,
458
- selector: "jc-metric-explorer",
459
- inputs: ["scope", "initial"]
460
- },
461
- {
462
- kind: "component",
463
- type: ServiceStatsCharts,
464
- selector: "jc-service-stats",
465
- inputs: ["query"]
466
- },
467
- {
468
- kind: "pipe",
469
- type: LinkPipe,
470
- name: "jcLink"
471
- },
472
- {
473
- kind: "pipe",
474
- type: AgoPipe,
475
- name: "ago"
476
- },
477
- {
478
- kind: "pipe",
479
- type: DurationPipe,
480
- name: "duration"
481
- },
482
- {
483
- kind: "pipe",
484
- type: PercentPipe,
485
- name: "percent2"
486
- },
487
- {
488
- kind: "pipe",
489
- type: ReasonPipe,
490
- name: "reason"
491
- }
492
- ],
493
- changeDetection: i0.ChangeDetectionStrategy.OnPush
494
- });
495
- };
496
- i0.ɵɵngDeclareClassMetadata({
497
- minVersion: "12.0.0",
498
- version: "22.2.4",
499
- ngImport: i0,
500
- type: ServicePage,
501
- decorators: [{
502
- type: Component,
503
- args: [{
504
- changeDetection: ChangeDetectionStrategy.OnPush,
505
- imports: [
506
- LinkPipe,
507
- RouterLink,
508
- MatTabsModule,
509
- MatProgressBarModule,
510
- MatButtonModule,
511
- ContextBar,
512
- HealthBadge,
513
- ConfidenceBadge,
514
- TraceTable,
515
- LogTable,
516
- MetricExplorer,
517
- ServiceStatsCharts,
518
- AgoPipe,
519
- DurationPipe,
520
- PercentPipe,
521
- ReasonPipe
522
- ],
523
- template: `
524
- <jc-context-bar>
525
- <a [routerLink]="['services'] | jcLink" [queryParams]="ctx.keep()" class="jc-crumb">Services</a>
526
- <span class="jc-crumb-sep">›</span>
527
- <h1>{{ service.value()?.key ?? '…' }}</h1>
528
- @for (l of service.value()?.links ?? []; track l.url) { <a mat-button [href]="l.url" target="_blank" rel="noopener">{{ l.title }} ↗</a> }
529
- </jc-context-bar>
530
- @if (service.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
531
- @if (service.error()) { <p class="jc-error">{{ error() }}</p> }
532
-
533
- @if (service.value(); as s) {
534
- <div class="jc-health-strip">
535
- @for (h of health(); track h.environment) {
536
- <div class="jc-card jc-health-card" [class.jc-selected]="h.environment === ctx.environment()">
537
- <div class="jc-row"><strong>{{ h.environment ?? 'no environment' }}</strong><jc-health [state]="h.state" [stale]="h.stale" /></div>
538
- <ul class="jc-reasons">
539
- @for (r of h.reasons; track r) {
540
- <li>
541
- @if (dependencyOf(r); as dep) {
542
- {{ r | reason }} — <a [routerLink]="['services', dep.id] | jcLink" [queryParams]="ctx.keep()">open</a>
543
- } @else { {{ r | reason }} }
544
- </li>
545
- }
546
- </ul>
547
- <div class="jc-muted jc-small">since {{ h.since | ago }} · evaluated {{ h.evaluatedAt | ago }}</div>
548
- @if (h.environment && h.environment !== ctx.environment()) {
549
- <button mat-button (click)="ctx.set({ env: h.environment })">Investigate {{ h.environment }}</button>
550
- }
551
- </div>
552
- } @empty { <p class="jc-muted">No health evaluated yet: no data.</p> }
553
- </div>
554
-
555
- <mat-tab-group [selectedIndex]="tabIndex()" (selectedIndexChange)="openTab($event)" animationDuration="0ms" mat-stretch-tabs="false">
556
- <mat-tab label="Overview">
557
- @if (tabIndex() === 0) {
558
- <jc-service-stats [query]="{ serviceId: id() }" />
559
- <h3>Instances</h3>
560
- <table class="jc-table">
561
- <thead><tr><th>Instance</th><th>Environment</th><th>Health</th><th>Version</th><th>State</th><th>Last seen</th><th></th></tr></thead>
562
- <tbody>
563
- @for (i of instances(); track i.id) {
564
- <tr>
565
- <td>{{ i.instanceKey }}@if (i.keySource !== 'Observed') { <span class="jc-muted jc-small" title="No service.instance.id: derived from host and process"> (derived)</span> }</td>
566
- <td>{{ i.environment }}</td>
567
- <td><jc-health [state]="i.health" /></td>
568
- <td>{{ i.serviceVersion ?? '—' }}</td>
569
- <td>{{ i.state }}</td>
570
- <td>{{ i.lastSeen | ago }}</td>
571
- <td class="jc-nowrap">
572
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', instance: i.id })">traces</a> ·
573
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'logs', instance: i.id })">logs</a>
574
- @if (i.hostResourceId) { · <a [routerLink]="['resources', i.hostResourceId] | jcLink" [queryParams]="ctx.keep()">host</a> }
575
- </td>
576
- </tr>
577
- } @empty { <tr><td colspan="7" class="jc-muted">No instances.</td></tr> }
578
- </tbody>
579
- </table>
580
- }
581
- </mat-tab>
582
-
583
- <mat-tab label="Operations">
584
- @if (tabIndex() === 1) {
585
- @if (operations.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
586
- <table class="jc-table">
587
- <thead><tr><th>Operation</th><th>Kind</th><th>Requests</th><th title="Average over the selected time range. The chart shows each moment, so a burst reads higher there.">Avg/s</th><th>Errors</th><th>p50</th><th>p95</th><th>p99</th><th>Last seen</th><th></th></tr></thead>
588
- <tbody>
589
- @for (o of operations.value()?.items ?? []; track o.name + o.kind) {
590
- <tr [class.jc-selected]="o.name === operation()">
591
- <td><a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'operations', op: o.name })">{{ o.name }}</a></td>
592
- <td>{{ o.kind }}</td>
593
- <td>{{ o.count }}</td>
594
- <td>{{ o.requestsPerSecond.toFixed(2) }}</td>
595
- <td [class.jc-bad]="o.errorRate >= 0.05">{{ o.errorRate | percent2 }}</td>
596
- <td>{{ o.p50Ms | duration }}</td><td>{{ o.p95Ms | duration }}</td><td>{{ o.p99Ms | duration }}</td>
597
- <td>{{ o.lastSeen | ago }}</td>
598
- <td class="jc-nowrap">
599
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', op: o.name, status: 'error' })">error traces</a> ·
600
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', op: o.name, slow: o.p95Ms })">slow traces</a>
601
- </td>
602
- </tr>
603
- } @empty { <tr><td colspan="10" class="jc-muted">No operations in this window.</td></tr> }
604
- </tbody>
605
- </table>
606
- @if (operation(); as op) {
607
- <h3>{{ op }}</h3>
608
- <jc-service-stats [query]="{ serviceId: id(), operation: op }" />
609
- }
610
- }
611
- </mat-tab>
612
-
613
- <mat-tab label="Dependencies">
614
- @if (tabIndex() === 2) {
615
- <h3>Calls and uses</h3>
616
- <table class="jc-table">
617
- <thead><tr><th>Target</th><th>Relation</th><th>Evidence</th><th>Spans seen</th><th>Last seen</th><th></th></tr></thead>
618
- <tbody>
619
- @for (d of downstream(); track d.edge.id) {
620
- <tr>
621
- <td><a [routerLink]="d.link" [queryParams]="ctx.keep()">{{ d.name }}</a>&nbsp;<span class="jc-muted jc-small">{{ d.type }}</span></td>
622
- <td>{{ d.edge.relation }}</td>
623
- <td><jc-confidence [confidence]="d.edge.confidence" /></td>
624
- <td>{{ d.edge.spanCount }}</td>
625
- <td>{{ d.edge.lastSeen | ago }}</td>
626
- <td><a [routerLink]="['topology'] | jcLink" [queryParams]="ctx.keep({ edge: d.edge.id })">evidence</a> ·
627
- <a [routerLink]="[]" [queryParams]="ctx.keep({ tab: 'traces', dependency: d.edge.id })">traces</a></td>
628
- </tr>
629
- } @empty { <tr><td colspan="6" class="jc-muted">No downstream dependencies seen.</td></tr> }
630
- </tbody>
631
- </table>
632
- <h3>Called by</h3>
633
- <table class="jc-table">
634
- <tbody>
635
- @for (d of upstream(); track d.edge.id) {
636
- <tr><td><a [routerLink]="d.link" [queryParams]="ctx.keep()">{{ d.name }}</a></td><td>{{ d.edge.relation }}</td>
637
- <td><jc-confidence [confidence]="d.edge.confidence" /></td><td>{{ d.edge.lastSeen | ago }}</td>
638
- <td><a [routerLink]="['topology'] | jcLink" [queryParams]="ctx.keep({ edge: d.edge.id })">evidence</a></td></tr>
639
- } @empty { <tr><td class="jc-muted">No callers seen.</td></tr> }
640
- </tbody>
641
- </table>
642
- }
643
- </mat-tab>
644
-
645
- <mat-tab label="Traces">
646
- @if (tabIndex() === 3) {
647
- <div class="jc-filters">
648
- @for (f of traceFilters(); track f.label) { <span class="jc-chip">{{ f.label }} <a [routerLink]="[]" [queryParams]="f.clear" queryParamsHandling="merge" [attr.aria-label]="'Remove ' + f.label">×</a></span> }
649
- </div>
650
- <jc-trace-table [query]="traceQuery()" />
651
- }
652
- </mat-tab>
653
-
654
- <mat-tab label="Logs">
655
- @if (tabIndex() === 4) { <jc-log-table [query]="{ serviceId: id(), instanceId: instanceFilter() }" /> }
656
- </mat-tab>
657
-
658
- <mat-tab label="Metrics">
659
- @if (tabIndex() === 5) { <jc-metric-explorer [scope]="{ serviceId: id(), instanceId: instanceFilter() }" /> }
660
- </mat-tab>
661
- </mat-tab-group>
662
- }`
663
- }]
664
- }],
665
- propDecorators: {
666
- id: [{
667
- type: i0.Input,
668
- args: [{
669
- isSignal: true,
670
- alias: "id",
671
- required: true
672
- }]
673
- }],
674
- tab: [{
675
- type: i0.Input,
676
- args: [{
677
- isSignal: true,
678
- alias: "tab",
679
- required: false
680
- }]
681
- }],
682
- op: [{
683
- type: i0.Input,
684
- args: [{
685
- isSignal: true,
686
- alias: "op",
687
- required: false
688
- }]
689
- }],
690
- status: [{
691
- type: i0.Input,
692
- args: [{
693
- isSignal: true,
694
- alias: "status",
695
- required: false
696
- }]
697
- }],
698
- slow: [{
699
- type: i0.Input,
700
- args: [{
701
- isSignal: true,
702
- alias: "slow",
703
- required: false
704
- }]
705
- }],
706
- instance: [{
707
- type: i0.Input,
708
- args: [{
709
- isSignal: true,
710
- alias: "instance",
711
- required: false
712
- }]
713
- }],
714
- dependency: [{
715
- type: i0.Input,
716
- args: [{
717
- isSignal: true,
718
- alias: "dependency",
719
- required: false
720
- }]
721
- }]
722
- }
723
- });
724
- export { ServicePage };
725
-
726
- //# sourceMappingURL=juice-js-observability-service.page-Dt8bN2Ry.mjs.map