@juice-js/observability 1.0.1-alpha.14 → 1.0.1-alpha.17

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/fesm2022/{juice-js-observability-alert.page-TKwqJT2d.mjs → juice-js-observability-alert.page-nEy5Gc6j.mjs} +2 -2
  4. package/fesm2022/{juice-js-observability-alert.page-TKwqJT2d.mjs.map → juice-js-observability-alert.page-nEy5Gc6j.mjs.map} +1 -1
  5. package/fesm2022/{juice-js-observability-alerts.page-B-2uS9wr.mjs → juice-js-observability-alerts.page-DUXXBanH.mjs} +2 -2
  6. package/fesm2022/{juice-js-observability-alerts.page-B-2uS9wr.mjs.map → juice-js-observability-alerts.page-DUXXBanH.mjs.map} +1 -1
  7. package/fesm2022/{juice-js-observability-logs.page-CZNbTl2Q.mjs → juice-js-observability-logs.page-i76xbJLH.mjs} +2 -2
  8. package/fesm2022/{juice-js-observability-logs.page-CZNbTl2Q.mjs.map → juice-js-observability-logs.page-i76xbJLH.mjs.map} +1 -1
  9. package/fesm2022/{juice-js-observability-metrics.page-DoVED9MT.mjs → juice-js-observability-metrics.page-Cq5S0yGJ.mjs} +2 -2
  10. package/fesm2022/{juice-js-observability-metrics.page-DoVED9MT.mjs.map → juice-js-observability-metrics.page-Cq5S0yGJ.mjs.map} +1 -1
  11. package/fesm2022/{juice-js-observability-overview.page-Cod7-cIy.mjs → juice-js-observability-overview.page-DXZl_fUh.mjs} +2 -2
  12. package/fesm2022/{juice-js-observability-overview.page-Cod7-cIy.mjs.map → juice-js-observability-overview.page-DXZl_fUh.mjs.map} +1 -1
  13. package/fesm2022/{juice-js-observability-resource.page-C8RhUipF.mjs → juice-js-observability-resource.page-CauDE6kK.mjs} +2 -2
  14. package/fesm2022/{juice-js-observability-resource.page-C8RhUipF.mjs.map → juice-js-observability-resource.page-CauDE6kK.mjs.map} +1 -1
  15. package/fesm2022/{juice-js-observability-resources.page-KadGxEAh.mjs → juice-js-observability-resources.page-CukW8vVc.mjs} +2 -2
  16. package/fesm2022/{juice-js-observability-resources.page-KadGxEAh.mjs.map → juice-js-observability-resources.page-CukW8vVc.mjs.map} +1 -1
  17. package/fesm2022/{juice-js-observability-rules.page-DKWvNpu9.mjs → juice-js-observability-rules.page-Crb07uWm.mjs} +2 -2
  18. package/fesm2022/{juice-js-observability-rules.page-DKWvNpu9.mjs.map → juice-js-observability-rules.page-Crb07uWm.mjs.map} +1 -1
  19. package/fesm2022/{juice-js-observability-service.page-_yK0kVYS.mjs → juice-js-observability-service.page-Dt8bN2Ry.mjs} +2 -2
  20. package/fesm2022/{juice-js-observability-service.page-_yK0kVYS.mjs.map → juice-js-observability-service.page-Dt8bN2Ry.mjs.map} +1 -1
  21. package/fesm2022/{juice-js-observability-services.page-BtG8zyiI.mjs → juice-js-observability-services.page-B1huLe_a.mjs} +2 -2
  22. package/fesm2022/{juice-js-observability-services.page-BtG8zyiI.mjs.map → juice-js-observability-services.page-B1huLe_a.mjs.map} +1 -1
  23. package/fesm2022/{juice-js-observability-telemetry-B5jMJYfn.mjs → juice-js-observability-telemetry-BftWSOaQ.mjs} +235 -8
  24. package/fesm2022/juice-js-observability-telemetry-BftWSOaQ.mjs.map +1 -0
  25. package/fesm2022/{juice-js-observability-topology.page-B7aXWOYV.mjs → juice-js-observability-topology.page-aMWEXyMB.mjs} +2 -2
  26. package/fesm2022/{juice-js-observability-topology.page-B7aXWOYV.mjs.map → juice-js-observability-topology.page-aMWEXyMB.mjs.map} +1 -1
  27. package/fesm2022/{juice-js-observability-trace.page-CkLNNnKf.mjs → juice-js-observability-trace.page-DUCWnZL5.mjs} +126 -3
  28. package/fesm2022/juice-js-observability-trace.page-DUCWnZL5.mjs.map +1 -0
  29. package/fesm2022/{juice-js-observability-traces.page-DgvNixIc.mjs → juice-js-observability-traces.page-DXSDdeTH.mjs} +2 -2
  30. package/fesm2022/{juice-js-observability-traces.page-DgvNixIc.mjs.map → juice-js-observability-traces.page-DXSDdeTH.mjs.map} +1 -1
  31. package/fesm2022/juice-js-observability.mjs +13 -13
  32. package/package.json +1 -1
  33. package/styles/_observability.scss +2 -0
  34. package/types/juice-js-observability.d.ts +1 -0
  35. package/fesm2022/juice-js-observability-telemetry-B5jMJYfn.mjs.map +0 -1
  36. package/fesm2022/juice-js-observability-trace.page-CkLNNnKf.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"juice-js-observability-service.page-_yK0kVYS.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 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>\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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"}
1
+ {"version":3,"file":"juice-js-observability-service.page-Dt8bN2Ry.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 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>\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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"}
@@ -1,5 +1,5 @@
1
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-B5jMJYfn.mjs";
2
+ import { S as apiResource, c as screenPages, d as HealthBadge, h as AgoPipe, o as problemText, u as ContextBar, v as ReasonPipe } from "./juice-js-observability-telemetry-BftWSOaQ.mjs";
3
3
  import * as i0 from "@angular/core";
4
4
  import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from "@angular/core";
5
5
  import { forkJoin, map } from "rxjs";
@@ -248,4 +248,4 @@ i0.ɵɵngDeclareClassMetadata({
248
248
  });
249
249
  export { ServicesPage };
250
250
 
251
- //# sourceMappingURL=juice-js-observability-services.page-BtG8zyiI.mjs.map
251
+ //# sourceMappingURL=juice-js-observability-services.page-B1huLe_a.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"juice-js-observability-services.page-BtG8zyiI.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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"}
1
+ {"version":3,"file":"juice-js-observability-services.page-B1huLe_a.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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"}
@@ -674,6 +674,7 @@ var Chart = class Chart {
674
674
  title = input("", ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
675
675
  series = input([], ...ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []);
676
676
  format = input((v) => String(Math.round(v * 100) / 100), ...ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []);
677
+ marks = input([], ...ngDevMode ? [{ debugName: "marks" }] : /* istanbul ignore next */ []);
677
678
  host = viewChild.required("host", ...ngDevMode ? [{ debugName: "host" }] : /* istanbul ignore next */ []);
678
679
  empty = computed(() => this.series().every((s) => s.points.every((p) => p.value === null)), ...ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []);
679
680
  chart;
@@ -692,6 +693,7 @@ var Chart = class Chart {
692
693
  components.GridComponent,
693
694
  components.TooltipComponent,
694
695
  components.LegendComponent,
696
+ components.MarkLineComponent,
695
697
  renderers.SVGRenderer
696
698
  ]);
697
699
  this.chart = echarts.init(this.host().nativeElement, void 0, { renderer: "svg" });
@@ -702,6 +704,7 @@ var Chart = class Chart {
702
704
  });
703
705
  effect(() => {
704
706
  this.series();
707
+ this.marks();
705
708
  this.render();
706
709
  });
707
710
  }
@@ -734,7 +737,10 @@ var Chart = class Chart {
734
737
  } : void 0,
735
738
  xAxis: {
736
739
  type: "time",
737
- axisLabel: { color: muted },
740
+ axisLabel: {
741
+ color: muted,
742
+ hideOverlap: true
743
+ },
738
744
  axisLine: { lineStyle: { color: border } }
739
745
  },
740
746
  yAxis: {
@@ -745,12 +751,22 @@ var Chart = class Chart {
745
751
  },
746
752
  splitLine: { lineStyle: { color: border } }
747
753
  },
748
- series: this.series().map((s) => ({
754
+ series: this.series().map((s, i) => ({
749
755
  name: s.name,
750
756
  type: "line",
751
757
  showSymbol: false,
752
758
  connectNulls: false,
753
- data: s.points.map((p) => [p.at, p.value])
759
+ data: s.points.map((p) => [p.at, p.value]),
760
+ ...i === 0 && this.marks().length ? { markLine: {
761
+ silent: true,
762
+ symbol: "none",
763
+ label: { show: false },
764
+ lineStyle: {
765
+ color: muted,
766
+ type: "dashed"
767
+ },
768
+ data: this.marks().map((at) => ({ xAxis: at }))
769
+ } } : {}
754
770
  }))
755
771
  };
756
772
  this.chart.setOption(option, true);
@@ -795,6 +811,13 @@ var Chart = class Chart {
795
811
  isSignal: true,
796
812
  isRequired: false,
797
813
  transformFunction: null
814
+ },
815
+ marks: {
816
+ classPropertyName: "marks",
817
+ publicName: "marks",
818
+ isSignal: true,
819
+ isRequired: false,
820
+ transformFunction: null
798
821
  }
799
822
  },
800
823
  viewQueries: [{
@@ -853,6 +876,14 @@ i0.ɵɵngDeclareClassMetadata({
853
876
  required: false
854
877
  }]
855
878
  }],
879
+ marks: [{
880
+ type: i0.Input,
881
+ args: [{
882
+ isSignal: true,
883
+ alias: "marks",
884
+ required: false
885
+ }]
886
+ }],
856
887
  host: [{
857
888
  type: i0.ViewChild,
858
889
  args: ["host", { isSignal: true }]
@@ -1409,7 +1440,7 @@ var MetricExplorer = class MetricExplorer {
1409
1440
  names = computed(() => this.metrics.value()?.items ?? [], ...ngDevMode ? [{ debugName: "names" }] : /* istanbul ignore next */ []);
1410
1441
  selected = linkedSignal(() => {
1411
1442
  const names = this.names();
1412
- return names.find((m) => m.name === this.initial())?.name ?? names[0]?.name ?? null;
1443
+ return names.find((m) => m.name === this.initial())?.name ?? names.find((m) => !/^(dotnet|process|system)./.test(m.name))?.name ?? names[0]?.name ?? null;
1413
1444
  }, ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
1414
1445
  series = apiResource({
1415
1446
  params: () => this.selected() ? {
@@ -1592,7 +1623,8 @@ var MetricExplorer = class MetricExplorer {
1592
1623
  inputs: [
1593
1624
  "title",
1594
1625
  "series",
1595
- "format"
1626
+ "format",
1627
+ "marks"
1596
1628
  ]
1597
1629
  }
1598
1630
  ],
@@ -1653,6 +1685,200 @@ i0.ɵɵngDeclareClassMetadata({
1653
1685
  }]
1654
1686
  }
1655
1687
  });
1688
+ var InstanceMetric = class InstanceMetric {
1689
+ api = inject(ApiService);
1690
+ title = input.required(...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
1691
+ name = input.required(...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
1692
+ instanceId = input.required(...ngDevMode ? [{ debugName: "instanceId" }] : /* istanbul ignore next */ []);
1693
+ from = input.required(...ngDevMode ? [{ debugName: "from" }] : /* istanbul ignore next */ []);
1694
+ to = input.required(...ngDevMode ? [{ debugName: "to" }] : /* istanbul ignore next */ []);
1695
+ marks = input([], ...ngDevMode ? [{ debugName: "marks" }] : /* istanbul ignore next */ []);
1696
+ format = input((v) => String(Math.round(v * 100) / 100), ...ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []);
1697
+ level = input(false, ...ngDevMode ? [{ debugName: "level" }] : /* istanbul ignore next */ []);
1698
+ series = apiResource({
1699
+ params: () => ({
1700
+ name: this.name(),
1701
+ instanceId: this.instanceId(),
1702
+ from: this.from(),
1703
+ to: this.to(),
1704
+ ...this.level() ? { level: true } : {}
1705
+ }),
1706
+ stream: ({ params }) => this.api.series(params)
1707
+ });
1708
+ points = computed(() => {
1709
+ const s = this.series.value();
1710
+ return s ? withGaps(s.points, this.from(), this.to(), s.stepSeconds) : [];
1711
+ }, ...ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []);
1712
+ static ɵfac = i0.ɵɵngDeclareFactory({
1713
+ minVersion: "12.0.0",
1714
+ version: "22.2.4",
1715
+ ngImport: i0,
1716
+ type: InstanceMetric,
1717
+ deps: [],
1718
+ target: i0.ɵɵFactoryTarget.Component
1719
+ });
1720
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1721
+ minVersion: "17.1.0",
1722
+ version: "22.2.4",
1723
+ type: InstanceMetric,
1724
+ isStandalone: true,
1725
+ selector: "jc-instance-metric",
1726
+ inputs: {
1727
+ title: {
1728
+ classPropertyName: "title",
1729
+ publicName: "title",
1730
+ isSignal: true,
1731
+ isRequired: true,
1732
+ transformFunction: null
1733
+ },
1734
+ name: {
1735
+ classPropertyName: "name",
1736
+ publicName: "name",
1737
+ isSignal: true,
1738
+ isRequired: true,
1739
+ transformFunction: null
1740
+ },
1741
+ instanceId: {
1742
+ classPropertyName: "instanceId",
1743
+ publicName: "instanceId",
1744
+ isSignal: true,
1745
+ isRequired: true,
1746
+ transformFunction: null
1747
+ },
1748
+ from: {
1749
+ classPropertyName: "from",
1750
+ publicName: "from",
1751
+ isSignal: true,
1752
+ isRequired: true,
1753
+ transformFunction: null
1754
+ },
1755
+ to: {
1756
+ classPropertyName: "to",
1757
+ publicName: "to",
1758
+ isSignal: true,
1759
+ isRequired: true,
1760
+ transformFunction: null
1761
+ },
1762
+ marks: {
1763
+ classPropertyName: "marks",
1764
+ publicName: "marks",
1765
+ isSignal: true,
1766
+ isRequired: false,
1767
+ transformFunction: null
1768
+ },
1769
+ format: {
1770
+ classPropertyName: "format",
1771
+ publicName: "format",
1772
+ isSignal: true,
1773
+ isRequired: false,
1774
+ transformFunction: null
1775
+ },
1776
+ level: {
1777
+ classPropertyName: "level",
1778
+ publicName: "level",
1779
+ isSignal: true,
1780
+ isRequired: false,
1781
+ transformFunction: null
1782
+ }
1783
+ },
1784
+ ngImport: i0,
1785
+ template: `<jc-chart [title]="title()" [series]="[{ name: name(), points: points() }]" [format]="format()" [marks]="marks()" />`,
1786
+ isInline: true,
1787
+ dependencies: [{
1788
+ kind: "component",
1789
+ type: Chart,
1790
+ selector: "jc-chart",
1791
+ inputs: [
1792
+ "title",
1793
+ "series",
1794
+ "format",
1795
+ "marks"
1796
+ ]
1797
+ }],
1798
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1799
+ });
1800
+ };
1801
+ i0.ɵɵngDeclareClassMetadata({
1802
+ minVersion: "12.0.0",
1803
+ version: "22.2.4",
1804
+ ngImport: i0,
1805
+ type: InstanceMetric,
1806
+ decorators: [{
1807
+ type: Component,
1808
+ args: [{
1809
+ selector: "jc-instance-metric",
1810
+ changeDetection: ChangeDetectionStrategy.OnPush,
1811
+ imports: [Chart],
1812
+ template: `<jc-chart [title]="title()" [series]="[{ name: name(), points: points() }]" [format]="format()" [marks]="marks()" />`
1813
+ }]
1814
+ }],
1815
+ propDecorators: {
1816
+ title: [{
1817
+ type: i0.Input,
1818
+ args: [{
1819
+ isSignal: true,
1820
+ alias: "title",
1821
+ required: true
1822
+ }]
1823
+ }],
1824
+ name: [{
1825
+ type: i0.Input,
1826
+ args: [{
1827
+ isSignal: true,
1828
+ alias: "name",
1829
+ required: true
1830
+ }]
1831
+ }],
1832
+ instanceId: [{
1833
+ type: i0.Input,
1834
+ args: [{
1835
+ isSignal: true,
1836
+ alias: "instanceId",
1837
+ required: true
1838
+ }]
1839
+ }],
1840
+ from: [{
1841
+ type: i0.Input,
1842
+ args: [{
1843
+ isSignal: true,
1844
+ alias: "from",
1845
+ required: true
1846
+ }]
1847
+ }],
1848
+ to: [{
1849
+ type: i0.Input,
1850
+ args: [{
1851
+ isSignal: true,
1852
+ alias: "to",
1853
+ required: true
1854
+ }]
1855
+ }],
1856
+ marks: [{
1857
+ type: i0.Input,
1858
+ args: [{
1859
+ isSignal: true,
1860
+ alias: "marks",
1861
+ required: false
1862
+ }]
1863
+ }],
1864
+ format: [{
1865
+ type: i0.Input,
1866
+ args: [{
1867
+ isSignal: true,
1868
+ alias: "format",
1869
+ required: false
1870
+ }]
1871
+ }],
1872
+ level: [{
1873
+ type: i0.Input,
1874
+ args: [{
1875
+ isSignal: true,
1876
+ alias: "level",
1877
+ required: false
1878
+ }]
1879
+ }]
1880
+ }
1881
+ });
1656
1882
  var ServiceStatsCharts = class ServiceStatsCharts {
1657
1883
  ctx = inject(ContextService);
1658
1884
  api = inject(ApiService);
@@ -1741,7 +1967,8 @@ var ServiceStatsCharts = class ServiceStatsCharts {
1741
1967
  inputs: [
1742
1968
  "title",
1743
1969
  "series",
1744
- "format"
1970
+ "format",
1971
+ "marks"
1745
1972
  ]
1746
1973
  },
1747
1974
  {
@@ -1796,6 +2023,6 @@ i0.ɵɵngDeclareClassMetadata({
1796
2023
  }]
1797
2024
  }] }
1798
2025
  });
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 };
2026
+ export { apiResource as S, PercentPipe as _, TraceTable as a, plainTraceId as b, screenPages as c, HealthBadge as d, KeyValueTable as f, DurationPipe as g, AgoPipe as h, ServiceStatsCharts as i, ConfidenceBadge as l, themeColor as m, LogTable as n, problemText as o, onThemeChange as p, MetricExplorer as r, cursorList as s, InstanceMetric as t, ContextBar as u, ReasonPipe as v, preciseTime as x, formatDuration as y };
1800
2027
 
1801
- //# sourceMappingURL=juice-js-observability-telemetry-B5jMJYfn.mjs.map
2028
+ //# sourceMappingURL=juice-js-observability-telemetry-BftWSOaQ.mjs.map