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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +60 -2
  2. package/fesm2022/juice-js-observability-alert.page-BHKAUvR-.mjs +404 -0
  3. package/fesm2022/juice-js-observability-alert.page-BHKAUvR-.mjs.map +1 -0
  4. package/fesm2022/juice-js-observability-alerts.page-C2tXzLQl.mjs +208 -0
  5. package/fesm2022/juice-js-observability-alerts.page-C2tXzLQl.mjs.map +1 -0
  6. package/fesm2022/juice-js-observability-logs.page--j6W1Ej-.mjs +413 -0
  7. package/fesm2022/juice-js-observability-logs.page--j6W1Ej-.mjs.map +1 -0
  8. package/fesm2022/juice-js-observability-metrics.page-DR6qZtfl.mjs +267 -0
  9. package/fesm2022/juice-js-observability-metrics.page-DR6qZtfl.mjs.map +1 -0
  10. package/fesm2022/juice-js-observability-overview.page-DU0GxSXQ.mjs +243 -0
  11. package/fesm2022/juice-js-observability-overview.page-DU0GxSXQ.mjs.map +1 -0
  12. package/fesm2022/juice-js-observability-resource.page-D6qpvo_f.mjs +274 -0
  13. package/fesm2022/juice-js-observability-resource.page-D6qpvo_f.mjs.map +1 -0
  14. package/fesm2022/juice-js-observability-resources.page-CR7-DFsh.mjs +209 -0
  15. package/fesm2022/juice-js-observability-resources.page-CR7-DFsh.mjs.map +1 -0
  16. package/fesm2022/juice-js-observability-rules.page-fq-wO5I_.mjs +175 -0
  17. package/fesm2022/juice-js-observability-rules.page-fq-wO5I_.mjs.map +1 -0
  18. package/fesm2022/juice-js-observability-service.page-Qc6FYChB.mjs +726 -0
  19. package/fesm2022/juice-js-observability-service.page-Qc6FYChB.mjs.map +1 -0
  20. package/fesm2022/juice-js-observability-services.page-CX6S7sUu.mjs +251 -0
  21. package/fesm2022/juice-js-observability-services.page-CX6S7sUu.mjs.map +1 -0
  22. package/fesm2022/juice-js-observability-telemetry-D9EYxkdr.mjs +1783 -0
  23. package/fesm2022/juice-js-observability-telemetry-D9EYxkdr.mjs.map +1 -0
  24. package/fesm2022/juice-js-observability-topology.page-CLz7J5pq.mjs +483 -0
  25. package/fesm2022/juice-js-observability-topology.page-CLz7J5pq.mjs.map +1 -0
  26. package/fesm2022/juice-js-observability-trace.page-DccvH1SV.mjs +518 -0
  27. package/fesm2022/juice-js-observability-trace.page-DccvH1SV.mjs.map +1 -0
  28. package/fesm2022/juice-js-observability-traces.page-BZxnac4Y.mjs +520 -0
  29. package/fesm2022/juice-js-observability-traces.page-BZxnac4Y.mjs.map +1 -0
  30. package/fesm2022/juice-js-observability.mjs +396 -0
  31. package/fesm2022/juice-js-observability.mjs.map +1 -0
  32. package/package.json +48 -4
  33. package/styles/_observability.scss +216 -0
  34. package/types/juice-js-observability.d.ts +387 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"juice-js-observability-service.page-Qc6FYChB.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/service.page.ts"],"sourcesContent":["import { LinkPipe, ObservabilityLinks } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { MatTabsModule } from '@angular/material/tabs';\nimport { MatButtonModule } from '@angular/material/button';\nimport { Router, RouterLink } from '@angular/router';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { AgoPipe, DurationPipe, PercentPipe, ReasonPipe } from '../shared/format';\nimport { ConfidenceBadge, ContextBar, HealthBadge } from '../shared/ui';\nimport { LogTable, MetricExplorer, ServiceStatsCharts, TraceTable, problemText } from '../shared/telemetry';\n\nconst TABS = ['overview', 'operations', 'dependencies', 'traces', 'logs', 'metrics'] as const;\n\n/**\n * One service across its environments (Console requirements §8): health with reasons, instances, operations,\n * dependencies, traces, logs and metrics, without leaving the service. The tab and the selected operation are in the\n * URL, so the investigation can be shared and the back button returns to it.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, MatTabsModule, MatProgressBarModule, MatButtonModule, ContextBar, HealthBadge, ConfidenceBadge,\n TraceTable, LogTable, MetricExplorer, ServiceStatsCharts, AgoPipe, DurationPipe, PercentPipe, ReasonPipe],\n template: `\n <jc-context-bar>\n <a [routerLink]=\"['services'] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-crumb\">Services</a>\n <span class=\"jc-crumb-sep\">›</span>\n <h1>{{ service.value()?.key ?? '…' }}</h1>\n @for (l of service.value()?.links ?? []; track l.url) { <a mat-button [href]=\"l.url\" target=\"_blank\" rel=\"noopener\">{{ l.title }} ↗</a> }\n </jc-context-bar>\n @if (service.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (service.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n\n @if (service.value(); as s) {\n <div class=\"jc-health-strip\">\n @for (h of health(); track h.environment) {\n <div class=\"jc-card jc-health-card\" [class.jc-selected]=\"h.environment === ctx.environment()\">\n <div class=\"jc-row\"><strong>{{ h.environment ?? 'no environment' }}</strong><jc-health [state]=\"h.state\" [stale]=\"h.stale\" /></div>\n <ul class=\"jc-reasons\">\n @for (r of h.reasons; track r) {\n <li>\n @if (dependencyOf(r); as dep) {\n {{ r | reason }} — <a [routerLink]=\"['services', dep.id] | jcLink\" [queryParams]=\"ctx.keep()\">open</a>\n } @else { {{ r | reason }} }\n </li>\n }\n </ul>\n <div class=\"jc-muted jc-small\">since {{ h.since | ago }} · evaluated {{ h.evaluatedAt | ago }}</div>\n @if (h.environment && h.environment !== ctx.environment()) {\n <button mat-button (click)=\"ctx.set({ env: h.environment })\">Investigate {{ h.environment }}</button>\n }\n </div>\n } @empty { <p class=\"jc-muted\">No health evaluated yet: no data.</p> }\n </div>\n\n <mat-tab-group [selectedIndex]=\"tabIndex()\" (selectedIndexChange)=\"openTab($event)\" animationDuration=\"0ms\" mat-stretch-tabs=\"false\">\n <mat-tab label=\"Overview\">\n @if (tabIndex() === 0) {\n <jc-service-stats [query]=\"{ serviceId: id() }\" />\n <h3>Instances</h3>\n <table class=\"jc-table\">\n <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>\n <tbody>\n @for (i of instances(); track i.id) {\n <tr>\n <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>\n <td>{{ i.environment }}</td>\n <td><jc-health [state]=\"i.health\" /></td>\n <td>{{ i.serviceVersion ?? '—' }}</td>\n <td>{{ i.state }}</td>\n <td>{{ i.lastSeen | ago }}</td>\n <td class=\"jc-nowrap\">\n <a [routerLink]=\"[]\" [queryParams]=\"ctx.keep({ tab: 'traces', instance: i.id })\">traces</a> ·\n <a [routerLink]=\"[]\" [queryParams]=\"ctx.keep({ tab: 'logs', instance: i.id })\">logs</a>\n @if (i.hostResourceId) { · <a [routerLink]=\"['resources', i.hostResourceId] | jcLink\" [queryParams]=\"ctx.keep()\">host</a> }\n </td>\n </tr>\n } @empty { <tr><td colspan=\"7\" class=\"jc-muted\">No instances.</td></tr> }\n </tbody>\n </table>\n }\n </mat-tab>\n\n <mat-tab label=\"Operations\">\n @if (tabIndex() === 1) {\n @if (operations.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n <table class=\"jc-table\">\n <thead><tr><th>Operation</th><th>Kind</th><th>Requests</th><th>Per second</th><th>Errors</th><th>p50</th><th>p95</th><th>p99</th><th>Last seen</th><th></th></tr></thead>\n <tbody>\n @for (o of operations.value()?.items ?? []; track o.name + o.kind) {\n <tr [class.jc-selected]=\"o.name === operation()\">\n <td><a [routerLink]=\"[]\" [queryParams]=\"ctx.keep({ tab: 'operations', op: o.name })\">{{ o.name }}</a></td>\n <td>{{ o.kind }}</td>\n <td>{{ o.count }}</td>\n <td>{{ o.requestsPerSecond.toFixed(2) }}</td>\n <td [class.jc-bad]=\"o.errorRate >= 0.05\">{{ o.errorRate | percent2 }}</td>\n <td>{{ o.p50Ms | duration }}</td><td>{{ o.p95Ms | duration }}</td><td>{{ o.p99Ms | duration }}</td>\n <td>{{ o.lastSeen | ago }}</td>\n <td class=\"jc-nowrap\">\n <a [routerLink]=\"[]\" [queryParams]=\"ctx.keep({ tab: 'traces', op: o.name, status: 'error' })\">error traces</a> ·\n <a [routerLink]=\"[]\" [queryParams]=\"ctx.keep({ tab: 'traces', op: o.name, slow: o.p95Ms })\">slow traces</a>\n </td>\n </tr>\n } @empty { <tr><td colspan=\"10\" class=\"jc-muted\">No operations in this window.</td></tr> }\n </tbody>\n </table>\n @if (operation(); as op) {\n <h3>{{ op }}</h3>\n <jc-service-stats [query]=\"{ serviceId: id(), operation: op }\" />\n }\n }\n </mat-tab>\n\n <mat-tab label=\"Dependencies\">\n @if (tabIndex() === 2) {\n <h3>Calls and uses</h3>\n <table class=\"jc-table\">\n <thead><tr><th>Target</th><th>Relation</th><th>Evidence</th><th>Spans seen</th><th>Last seen</th><th></th></tr></thead>\n <tbody>\n @for (d of downstream(); track d.edge.id) {\n <tr>\n <td><a [routerLink]=\"d.link\" [queryParams]=\"ctx.keep()\">{{ d.name }}</a>&nbsp;<span class=\"jc-muted jc-small\">{{ d.type }}</span></td>\n <td>{{ d.edge.relation }}</td>\n <td><jc-confidence [confidence]=\"d.edge.confidence\" /></td>\n <td>{{ d.edge.spanCount }}</td>\n <td>{{ d.edge.lastSeen | ago }}</td>\n <td><a [routerLink]=\"['topology'] | jcLink\" [queryParams]=\"ctx.keep({ edge: d.edge.id })\">evidence</a> ·\n <a [routerLink]=\"[]\" [queryParams]=\"ctx.keep({ tab: 'traces', dependency: d.edge.id })\">traces</a></td>\n </tr>\n } @empty { <tr><td colspan=\"6\" class=\"jc-muted\">No downstream dependencies seen.</td></tr> }\n </tbody>\n </table>\n <h3>Called by</h3>\n <table class=\"jc-table\">\n <tbody>\n @for (d of upstream(); track d.edge.id) {\n <tr><td><a [routerLink]=\"d.link\" [queryParams]=\"ctx.keep()\">{{ d.name }}</a></td><td>{{ d.edge.relation }}</td>\n <td><jc-confidence [confidence]=\"d.edge.confidence\" /></td><td>{{ d.edge.lastSeen | ago }}</td>\n <td><a [routerLink]=\"['topology'] | jcLink\" [queryParams]=\"ctx.keep({ edge: d.edge.id })\">evidence</a></td></tr>\n } @empty { <tr><td class=\"jc-muted\">No callers seen.</td></tr> }\n </tbody>\n </table>\n }\n </mat-tab>\n\n <mat-tab label=\"Traces\">\n @if (tabIndex() === 3) {\n <div class=\"jc-filters\">\n @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> }\n </div>\n <jc-trace-table [query]=\"traceQuery()\" />\n }\n </mat-tab>\n\n <mat-tab label=\"Logs\">\n @if (tabIndex() === 4) { <jc-log-table [query]=\"{ serviceId: id(), instanceId: instanceFilter() }\" /> }\n </mat-tab>\n\n <mat-tab label=\"Metrics\">\n @if (tabIndex() === 5) { <jc-metric-explorer [scope]=\"{ serviceId: id(), instanceId: instanceFilter() }\" /> }\n </mat-tab>\n </mat-tab-group>\n }`,\n})\nexport class ServicePage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n private readonly links = inject(ObservabilityLinks);\n\n readonly id = input.required<string>();\n readonly tab = input<string>();\n readonly op = input<string>();\n readonly status = input<string>();\n readonly slow = input<string>();\n readonly instance = input<string>();\n readonly dependency = input<string>();\n\n protected readonly tabIndex = computed(() => Math.max(0, TABS.indexOf((this.tab() ?? 'overview') as typeof TABS[number])));\n protected readonly operation = computed(() => this.op() ?? null);\n protected readonly instanceFilter = computed(() => this.instance() ?? null);\n\n protected readonly service = apiResource({ params: () => this.id(), stream: ({ params }) => this.api.resource(params) });\n protected readonly topology = apiResource({ params: () => this.ctx.environment(), stream: ({ params }) => this.api.topology(params || null) });\n protected readonly operations = apiResource({\n params: () => ({ id: this.id(), env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute(), on: this.tabIndex() === 1 }),\n stream: ({ params }) => this.api.operations({ serviceId: params.id, environment: params.env, ...this.ctx.window(), limit: 200 }),\n });\n\n protected readonly error = computed(() => problemText(this.service.error()));\n protected readonly health = computed(() => (this.service.value()?.health ?? []).filter(h => h.subjectType === 'service'));\n protected readonly instances = computed(() => {\n const env = this.ctx.environment();\n return (this.service.value()?.instances ?? []).filter(i => !env || i.environment === env);\n });\n\n private readonly nodes = computed(() => new Map((this.topology.value()?.nodes ?? []).map(n => [n.id, n])));\n private edgesWhere(match: 'source' | 'target') {\n const other = match === 'source' ? 'target' : 'source';\n return (this.topology.value()?.edges ?? []).filter(e => e[match] === this.id()).map(edge => {\n const node = this.nodes().get(edge[other]);\n return {\n edge, name: node?.displayName ?? edge[other], type: node?.type ?? '',\n link: this.links.to(node?.type === 'service' ? 'services' : 'resources', edge[other]),\n };\n });\n }\n protected readonly downstream = computed(() => this.edgesWhere('source'));\n protected readonly upstream = computed(() => this.edgesWhere('target'));\n\n /** `dependency_failed:shop/payments` links to that service when it is in the topology. */\n dependencyOf(reason: string) {\n const name = reason.match(/^(?:critical_)?dependency_(?:failed|unknown):(.+)$/)?.[1];\n if (!name) return null;\n const node = [...this.nodes().values()].find(n => n.type === 'service' && (n.displayName === name || name.endsWith(`/${n.displayName}`)));\n return node ? { id: node.id } : null;\n }\n\n protected readonly traceQuery = computed(() => ({\n serviceId: this.id(), operation: this.op(), status: this.status(), minDurationMs: this.slow(),\n instanceId: this.instance(), dependencyId: this.dependency(),\n }));\n /** The trace filters in effect, each removable on its own. */\n protected readonly traceFilters = computed(() => {\n const filters: { label: string; clear: Record<string, null> }[] = [];\n if (this.op()) filters.push({ label: `operation: ${this.op()}`, clear: { op: null } });\n if (this.status()) filters.push({ label: `status: ${this.status()}`, clear: { status: null } });\n if (this.slow()) filters.push({ label: `slower than ${Math.round(Number(this.slow()))} ms`, clear: { slow: null } });\n if (this.instance()) filters.push({ label: 'one instance', clear: { instance: null } });\n if (this.dependency()) filters.push({ label: 'through one dependency', clear: { dependency: null } });\n return filters;\n });\n\n openTab(index: number) {\n void this.router.navigate([], { queryParams: { tab: TABS[index] }, queryParamsHandling: 'merge' });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,251 @@
1
+ import { ApiService, ContextService, LinkPipe } from "./juice-js-observability.mjs";
2
+ import { _ as ReasonPipe, a as problemText, l as ContextBar, m as AgoPipe, s as screenPages, u as HealthBadge, x as apiResource } from "./juice-js-observability-telemetry-D9EYxkdr.mjs";
3
+ import * as i0 from "@angular/core";
4
+ import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from "@angular/core";
5
+ import { forkJoin, map } from "rxjs";
6
+ import { RouterLink } from "@angular/router";
7
+ import * as i2 from "@angular/material/progress-bar";
8
+ import { MatProgressBarModule } from "@angular/material/progress-bar";
9
+ import * as i1 from "@angular/material/paginator";
10
+ import { MatPaginatorModule } from "@angular/material/paginator";
11
+ const SEVERITY = [
12
+ "Critical",
13
+ "Unhealthy",
14
+ "Degraded",
15
+ "Offline",
16
+ "Unknown",
17
+ "Healthy"
18
+ ];
19
+ var ServicesPage = class ServicesPage {
20
+ ctx = inject(ContextService);
21
+ api = inject(ApiService);
22
+ state = input(...ngDevMode ? [void 0, { debugName: "state" }] : /* istanbul ignore next */ []);
23
+ filter = signal("", ...ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []);
24
+ reasonPipe = new ReasonPipe();
25
+ reason = (r) => this.reasonPipe.transform(r);
26
+ data = apiResource({ stream: () => forkJoin({
27
+ services: this.api.resources("service"),
28
+ health: this.api.health({ subjectType: "service" })
29
+ }).pipe(map(({ services, health }) => ({
30
+ services: services.items,
31
+ health: health.items,
32
+ capped: services.nextCursor !== null || health.nextCursor !== null
33
+ }))) });
34
+ capped = computed(() => this.data.value()?.capped ?? false, ...ngDevMode ? [{ debugName: "capped" }] : /* istanbul ignore next */ []);
35
+ error = computed(() => problemText(this.data.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
36
+ rows = computed(() => {
37
+ const data = this.data.value();
38
+ if (!data) return [];
39
+ const env = this.ctx.environment();
40
+ const byService = /* @__PURE__ */ new Map();
41
+ for (const h of data.health) byService.set(h.subjectId, [...byService.get(h.subjectId) ?? [], h]);
42
+ const q = this.filter().toLowerCase();
43
+ return data.services.map((s) => {
44
+ const health = (byService.get(s.id) ?? []).filter((h) => !env || h.environment === env);
45
+ const worst = [...health].sort((a, b) => SEVERITY.indexOf(a.state) - SEVERITY.indexOf(b.state))[0];
46
+ return {
47
+ ...s,
48
+ health,
49
+ worst
50
+ };
51
+ }).filter((s) => !env || s.health.length > 0).filter((s) => !q || s.key.toLowerCase().includes(q)).filter((s) => !this.state() || s.health.some((h) => h.state === this.state())).sort((a, b) => SEVERITY.indexOf(a.worst?.state ?? "Unknown") - SEVERITY.indexOf(b.worst?.state ?? "Unknown") || a.key.localeCompare(b.key));
52
+ }, ...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
53
+ pages = screenPages(this.rows);
54
+ onSearch(query) {
55
+ this.filter.set(query);
56
+ }
57
+ static ɵfac = i0.ɵɵngDeclareFactory({
58
+ minVersion: "12.0.0",
59
+ version: "22.2.4",
60
+ ngImport: i0,
61
+ type: ServicesPage,
62
+ deps: [],
63
+ target: i0.ɵɵFactoryTarget.Component
64
+ });
65
+ static ɵcmp = i0.ɵɵngDeclareComponent({
66
+ minVersion: "17.0.0",
67
+ version: "22.2.4",
68
+ type: ServicesPage,
69
+ isStandalone: true,
70
+ selector: "ng-component",
71
+ inputs: { state: {
72
+ classPropertyName: "state",
73
+ publicName: "state",
74
+ isSignal: true,
75
+ isRequired: false,
76
+ transformFunction: null
77
+ } },
78
+ ngImport: i0,
79
+ template: `
80
+ <jc-context-bar><h1>Services</h1>
81
+ @if (state()) { <span class="jc-chip">{{ state() }} <a [routerLink]="['services'] | jcLink" [queryParams]="ctx.keep()">×</a></span> }
82
+ </jc-context-bar>
83
+ @if (data.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
84
+ @if (data.error()) { <p class="jc-error">{{ error() }}</p> }
85
+ @if (capped()) { <p class="jc-notice">Showing the first 5,000 only. Narrow the list with the search box or a filter.</p> }
86
+ <table class="jc-table">
87
+ <thead><tr><th>Service</th><th>Health by environment</th><th>Why</th><th>Last seen</th></tr></thead>
88
+ <tbody>
89
+ @for (s of pages.visible(); track s.id) {
90
+ <tr>
91
+ <td><a [routerLink]="['services', s.id] | jcLink" [queryParams]="ctx.keep()">{{ s.key }}</a></td>
92
+ <td class="jc-envs">
93
+ @for (h of s.health; track h.environment) {
94
+ <span><span class="jc-muted jc-small">{{ h.environment ?? '—' }}</span> <jc-health [state]="h.state" [stale]="h.stale" /></span>
95
+ } @empty { <jc-health [state]="null" /> }
96
+ </td>
97
+ <td class="jc-small">{{ s.worst?.reasons?.slice(0, 2)?.map(reason)?.join(' · ') }}</td>
98
+ <td>{{ s.lastSeen | ago }}</td>
99
+ </tr>
100
+ } @empty { <tr><td colspan="4" class="jc-muted">No services{{ filter() ? ' match "' + filter() + '"' : '' }}.</td></tr> }
101
+ </tbody>
102
+ </table>
103
+ @if (pages.needed()) {
104
+ <mat-paginator [length]="rows().length" [pageSize]="pages.size()" [pageIndex]="pages.index()" [pageSizeOptions]="pages.sizes"
105
+ (page)="pages.change($event)" aria-label="Pages" />
106
+ }`,
107
+ isInline: true,
108
+ dependencies: [
109
+ {
110
+ kind: "directive",
111
+ type: RouterLink,
112
+ selector: "[routerLink]",
113
+ inputs: [
114
+ "target",
115
+ "queryParams",
116
+ "fragment",
117
+ "queryParamsHandling",
118
+ "state",
119
+ "info",
120
+ "relativeTo",
121
+ "preserveFragment",
122
+ "skipLocationChange",
123
+ "replaceUrl",
124
+ "browserUrl",
125
+ "routerLink"
126
+ ]
127
+ },
128
+ {
129
+ kind: "ngmodule",
130
+ type: MatPaginatorModule
131
+ },
132
+ {
133
+ kind: "component",
134
+ type: i1.MatPaginator,
135
+ selector: "mat-paginator",
136
+ inputs: [
137
+ "color",
138
+ "pageIndex",
139
+ "length",
140
+ "pageSize",
141
+ "pageSizeOptions",
142
+ "hidePageSize",
143
+ "showFirstLastButtons",
144
+ "selectConfig",
145
+ "disabled"
146
+ ],
147
+ outputs: ["page"],
148
+ exportAs: ["matPaginator"]
149
+ },
150
+ {
151
+ kind: "ngmodule",
152
+ type: MatProgressBarModule
153
+ },
154
+ {
155
+ kind: "component",
156
+ type: i2.MatProgressBar,
157
+ selector: "mat-progress-bar",
158
+ inputs: [
159
+ "color",
160
+ "value",
161
+ "bufferValue",
162
+ "mode"
163
+ ],
164
+ outputs: ["animationEnd"],
165
+ exportAs: ["matProgressBar"]
166
+ },
167
+ {
168
+ kind: "component",
169
+ type: ContextBar,
170
+ selector: "jc-context-bar"
171
+ },
172
+ {
173
+ kind: "component",
174
+ type: HealthBadge,
175
+ selector: "jc-health",
176
+ inputs: ["state", "stale"]
177
+ },
178
+ {
179
+ kind: "pipe",
180
+ type: LinkPipe,
181
+ name: "jcLink"
182
+ },
183
+ {
184
+ kind: "pipe",
185
+ type: AgoPipe,
186
+ name: "ago"
187
+ }
188
+ ],
189
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
190
+ });
191
+ };
192
+ i0.ɵɵngDeclareClassMetadata({
193
+ minVersion: "12.0.0",
194
+ version: "22.2.4",
195
+ ngImport: i0,
196
+ type: ServicesPage,
197
+ decorators: [{
198
+ type: Component,
199
+ args: [{
200
+ changeDetection: ChangeDetectionStrategy.OnPush,
201
+ imports: [
202
+ LinkPipe,
203
+ RouterLink,
204
+ MatPaginatorModule,
205
+ MatProgressBarModule,
206
+ ContextBar,
207
+ HealthBadge,
208
+ AgoPipe
209
+ ],
210
+ template: `
211
+ <jc-context-bar><h1>Services</h1>
212
+ @if (state()) { <span class="jc-chip">{{ state() }} <a [routerLink]="['services'] | jcLink" [queryParams]="ctx.keep()">×</a></span> }
213
+ </jc-context-bar>
214
+ @if (data.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
215
+ @if (data.error()) { <p class="jc-error">{{ error() }}</p> }
216
+ @if (capped()) { <p class="jc-notice">Showing the first 5,000 only. Narrow the list with the search box or a filter.</p> }
217
+ <table class="jc-table">
218
+ <thead><tr><th>Service</th><th>Health by environment</th><th>Why</th><th>Last seen</th></tr></thead>
219
+ <tbody>
220
+ @for (s of pages.visible(); track s.id) {
221
+ <tr>
222
+ <td><a [routerLink]="['services', s.id] | jcLink" [queryParams]="ctx.keep()">{{ s.key }}</a></td>
223
+ <td class="jc-envs">
224
+ @for (h of s.health; track h.environment) {
225
+ <span><span class="jc-muted jc-small">{{ h.environment ?? '—' }}</span> <jc-health [state]="h.state" [stale]="h.stale" /></span>
226
+ } @empty { <jc-health [state]="null" /> }
227
+ </td>
228
+ <td class="jc-small">{{ s.worst?.reasons?.slice(0, 2)?.map(reason)?.join(' · ') }}</td>
229
+ <td>{{ s.lastSeen | ago }}</td>
230
+ </tr>
231
+ } @empty { <tr><td colspan="4" class="jc-muted">No services{{ filter() ? ' match "' + filter() + '"' : '' }}.</td></tr> }
232
+ </tbody>
233
+ </table>
234
+ @if (pages.needed()) {
235
+ <mat-paginator [length]="rows().length" [pageSize]="pages.size()" [pageIndex]="pages.index()" [pageSizeOptions]="pages.sizes"
236
+ (page)="pages.change($event)" aria-label="Pages" />
237
+ }`
238
+ }]
239
+ }],
240
+ propDecorators: { state: [{
241
+ type: i0.Input,
242
+ args: [{
243
+ isSignal: true,
244
+ alias: "state",
245
+ required: false
246
+ }]
247
+ }] }
248
+ });
249
+ export { ServicesPage };
250
+
251
+ //# sourceMappingURL=juice-js-observability-services.page-CX6S7sUu.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"juice-js-observability-services.page-CX6S7sUu.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/services.page.ts"],"sourcesContent":["import { LinkPipe } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { RouterLink } from '@angular/router';\nimport { SearchablePage } from '@juice-js/layout';\nimport { forkJoin, map } from 'rxjs';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Health, HealthState } from '../core/models';\nimport { AgoPipe, ReasonPipe } from '../shared/format';\nimport { ContextBar, HealthBadge } from '../shared/ui';\nimport { problemText } from '../shared/telemetry';\nimport { screenPages } from '../shared/paging';\n\nconst SEVERITY: HealthState[] = ['Critical', 'Unhealthy', 'Degraded', 'Offline', 'Unknown', 'Healthy'];\n\n/**\n * The service catalog (Console requirements §8): one row per service, environment-independent, with its health in\n * each environment. The toolbar search box filters it.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, MatPaginatorModule, MatProgressBarModule, ContextBar, HealthBadge, AgoPipe],\n template: `\n <jc-context-bar><h1>Services</h1>\n @if (state()) { <span class=\"jc-chip\">{{ state() }} <a [routerLink]=\"['services'] | jcLink\" [queryParams]=\"ctx.keep()\">×</a></span> }\n </jc-context-bar>\n @if (data.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (data.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n @if (capped()) { <p class=\"jc-notice\">Showing the first 5,000 only. Narrow the list with the search box or a filter.</p> }\n <table class=\"jc-table\">\n <thead><tr><th>Service</th><th>Health by environment</th><th>Why</th><th>Last seen</th></tr></thead>\n <tbody>\n @for (s of pages.visible(); track s.id) {\n <tr>\n <td><a [routerLink]=\"['services', s.id] | jcLink\" [queryParams]=\"ctx.keep()\">{{ s.key }}</a></td>\n <td class=\"jc-envs\">\n @for (h of s.health; track h.environment) {\n <span><span class=\"jc-muted jc-small\">{{ h.environment ?? '—' }}</span> <jc-health [state]=\"h.state\" [stale]=\"h.stale\" /></span>\n } @empty { <jc-health [state]=\"null\" /> }\n </td>\n <td class=\"jc-small\">{{ s.worst?.reasons?.slice(0, 2)?.map(reason)?.join(' · ') }}</td>\n <td>{{ s.lastSeen | ago }}</td>\n </tr>\n } @empty { <tr><td colspan=\"4\" class=\"jc-muted\">No services{{ filter() ? ' match \"' + filter() + '\"' : '' }}.</td></tr> }\n </tbody>\n </table>\n @if (pages.needed()) {\n <mat-paginator [length]=\"rows().length\" [pageSize]=\"pages.size()\" [pageIndex]=\"pages.index()\" [pageSizeOptions]=\"pages.sizes\"\n (page)=\"pages.change($event)\" aria-label=\"Pages\" />\n }`,\n})\nexport class ServicesPage implements SearchablePage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n readonly state = input<string>();\n protected readonly filter = signal('');\n private readonly reasonPipe = new ReasonPipe();\n protected readonly reason = (r: string) => this.reasonPipe.transform(r);\n\n protected readonly data = apiResource({\n stream: () => forkJoin({\n services: this.api.resources('service'),\n health: this.api.health({ subjectType: 'service' }),\n }).pipe(map(({ services, health }) => ({\n services: services.items, health: health.items, capped: services.nextCursor !== null || health.nextCursor !== null,\n }))),\n });\n protected readonly capped = computed(() => this.data.value()?.capped ?? false);\n protected readonly error = computed(() => problemText(this.data.error()));\n\n protected readonly rows = computed(() => {\n const data = this.data.value();\n if (!data) return [];\n const env = this.ctx.environment();\n const byService = new Map<string, Health[]>();\n for (const h of data.health) byService.set(h.subjectId, [...(byService.get(h.subjectId) ?? []), h]);\n const q = this.filter().toLowerCase();\n return data.services\n .map(s => {\n const health = (byService.get(s.id) ?? []).filter(h => !env || h.environment === env);\n const worst = [...health].sort((a, b) => SEVERITY.indexOf(a.state) - SEVERITY.indexOf(b.state))[0];\n return { ...s, health, worst };\n })\n // With an environment chosen, only the services seen in it.\n .filter(s => !env || s.health.length > 0)\n .filter(s => !q || s.key.toLowerCase().includes(q))\n .filter(s => !this.state() || s.health.some(h => h.state === this.state()))\n .sort((a, b) => SEVERITY.indexOf(a.worst?.state ?? 'Unknown') - SEVERITY.indexOf(b.worst?.state ?? 'Unknown') || a.key.localeCompare(b.key));\n });\n\n protected readonly pages = screenPages(this.rows);\n\n onSearch(query: string) { this.filter.set(query); }\n}\n"],"mappings":"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"}