@juice-js/observability 1.0.0-alpha.9 → 1.0.1-alpha.14

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.
@@ -26,6 +26,7 @@ var TopologyPage = class TopologyPage {
26
26
  graph = viewChild.required("graph", ...ngDevMode ? [{ debugName: "graph" }] : /* istanbul ignore next */ []);
27
27
  cy;
28
28
  stopThemeWatch;
29
+ destroyed = false;
29
30
  topology = apiResource({
30
31
  params: () => this.ctx.environment(),
31
32
  stream: ({ params }) => this.api.topology(params || null)
@@ -49,6 +50,7 @@ var TopologyPage = class TopologyPage {
49
50
  constructor() {
50
51
  afterNextRender(async () => {
51
52
  const cytoscape = (await import("cytoscape")).default;
53
+ if (this.destroyed) return;
52
54
  this.cy = cytoscape({
53
55
  container: this.graph().nativeElement,
54
56
  wheelSensitivity: .3,
@@ -197,6 +199,7 @@ var TopologyPage = class TopologyPage {
197
199
  return this.links.to(this.nodes().get(id)?.type === "service" ? "services" : "resources", id);
198
200
  }
199
201
  ngOnDestroy() {
202
+ this.destroyed = true;
200
203
  this.stopThemeWatch?.();
201
204
  this.cy?.destroy();
202
205
  }
@@ -480,4 +483,4 @@ i0.ɵɵngDeclareClassMetadata({
480
483
  });
481
484
  export { TopologyPage };
482
485
 
483
- //# sourceMappingURL=juice-js-observability-topology.page-M_kitXna.mjs.map
486
+ //# sourceMappingURL=juice-js-observability-topology.page-B7aXWOYV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"juice-js-observability-topology.page-B7aXWOYV.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/topology.page.ts"],"sourcesContent":["import { LinkPipe, ObservabilityLinks } from '../core/options';\nimport {\n ChangeDetectionStrategy, Component, ElementRef, OnDestroy, afterNextRender, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { Router, RouterLink } from '@angular/router';\nimport type { Core, StylesheetJson } from 'cytoscape';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Topology } from '../core/models';\nimport { AgoPipe, DurationPipe, PercentPipe } from '../shared/format';\nimport { ConfidenceBadge, ContextBar, HealthBadge } from '../shared/ui';\nimport { problemText } from '../shared/telemetry';\nimport { onThemeChange, themeColor } from '../shared/theme';\n\n/** A service's health → its --jc-* colour from the app's theme (observability.colors). */\nconst HEALTH_TOKENS: Record<string, string> = {\n Healthy: 'ok', Degraded: 'warn', Unhealthy: 'bad', Critical: 'crit', Offline: 'off', Unknown: 'none',\n};\n\n/**\n * Dependency topology (Console requirements §9): services, databases and destinations; zoom, pan and selection.\n * Observed edges are solid, inferred ones dashed and labelled, so an inference never reads as a confirmed call. A\n * selected edge shows its evidence and representative traces; the selection is in the URL.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, MatButtonModule, MatIconModule, MatProgressBarModule, ContextBar, HealthBadge, ConfidenceBadge,\n AgoPipe, DurationPipe, PercentPipe],\n template: `\n <jc-context-bar><h1>Dependencies</h1></jc-context-bar>\n @if (topology.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (topology.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n <div class=\"jc-topology\">\n <div class=\"jc-graph-wrap\">\n <div #graph class=\"jc-graph\" aria-label=\"Dependency graph\"></div>\n <div class=\"jc-graph-legend\">\n <span><i class=\"jc-line\"></i> observed</span><span><i class=\"jc-line jc-dashed\"></i> inferred</span>\n <span>■ service ◆ database ⬣ destination</span>\n <button mat-button (click)=\"fit()\">Fit</button>\n </div>\n @if (topology.value()?.truncated) { <p class=\"jc-muted\">Showing the most recent edges only.</p> }\n @if (topology.hasValue() && topology.value()!.nodes.length === 0) { <p class=\"jc-muted\">No dependencies seen{{ ctx.environment() ? ' in ' + ctx.environment() : '' }}.</p> }\n </div>\n\n <aside class=\"jc-card jc-side\">\n @if (selectedEdge(); as e) {\n <h2>{{ name(e.source) }} → {{ name(e.target) }}</h2>\n <p>{{ e.relation }} <jc-confidence [confidence]=\"e.confidence\" /></p>\n @if (e.confidence.toLowerCase() !== 'observed') {\n <p class=\"jc-muted jc-small\">Inferred from span attributes (for example db.system.name or messaging.destination.name), not a call\n seen between two instrumented services.</p>\n }\n @if (evidence.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (evidence.value(); as ev) {\n <dl class=\"jc-facts\">\n <dt>Calls in window</dt><dd>{{ ev.count }}</dd>\n <dt>Error rate</dt><dd [class.jc-bad]=\"ev.errorRate >= 0.05\">{{ ev.count ? (ev.errorRate | percent2) : 'no data' }}</dd>\n <dt>p95</dt><dd>{{ ev.p95Ms | duration }}</dd>\n <dt>Last seen</dt><dd>{{ ev.lastSeen ?? e.lastSeen | ago }}</dd>\n </dl>\n <h3>Representative traces</h3>\n @for (t of ev.errorTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-badge jc-health-unhealthy\">error</span></div> }\n @for (t of ev.slowestTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-muted jc-small\">slow</span></div> }\n @if (!ev.errorTraces.length && !ev.slowestTraces.length) { <p class=\"jc-muted\">No calls in this window.</p> }\n <a mat-stroked-button [routerLink]=\"['traces'] | jcLink\" [queryParams]=\"ctx.keep({ dependency: e.id })\">All traces through this edge</a>\n }\n <p><a [routerLink]=\"link(e.source)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.source) }}</a> ·\n <a [routerLink]=\"link(e.target)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.target) }}</a></p>\n } @else if (selectedNode(); as n) {\n <h2>{{ n.displayName }}</h2>\n <p>{{ n.type }} <jc-health [state]=\"n.health\" /></p>\n <a mat-stroked-button [routerLink]=\"link(n.id)\" [queryParams]=\"ctx.keep()\">Open</a>\n } @else {\n <p class=\"jc-muted\">Select a node or an edge. Scroll to zoom, drag to pan.</p>\n }\n </aside>\n </div>`,\n})\nexport class TopologyPage implements OnDestroy {\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 edge = input<string>();\n readonly node = input<string>();\n\n private readonly graph = viewChild.required<ElementRef<HTMLDivElement>>('graph');\n private cy?: Core;\n private stopThemeWatch?: () => void;\n private destroyed = false;\n\n protected readonly topology = apiResource({ params: () => this.ctx.environment(), stream: ({ params }) => this.api.topology(params || null) });\n protected readonly error = computed(() => problemText(this.topology.error()));\n private readonly nodes = computed(() => new Map((this.topology.value()?.nodes ?? []).map(n => [n.id, n])));\n protected readonly selectedEdge = computed(() => this.topology.value()?.edges.find(e => e.id === this.edge()) ?? null);\n protected readonly selectedNode = computed(() => (this.node() ? this.nodes().get(this.node()!) : null) ?? null);\n protected readonly evidence = apiResource({\n // No edge selected: undefined params leave the resource idle.\n params: () => this.edge() ? { edge: this.edge()!, env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() } : undefined,\n stream: ({ params }) => this.api.dependency(params.edge, { environment: params.env, ...this.ctx.window() }),\n });\n\n constructor() {\n afterNextRender(async () => {\n const cytoscape = (await import('cytoscape')).default;\n // Left the page while the graph library loaded: its container is detached (no size, so cytoscape's first layout\n // throws), and a graph made now would outlive ngOnDestroy.\n if (this.destroyed) return;\n this.cy = cytoscape({\n container: this.graph().nativeElement,\n wheelSensitivity: 0.3,\n style: this.graphStyle(),\n });\n // The app switched theme (light/dark): restyle and recolour the nodes.\n this.stopThemeWatch = onThemeChange(() => {\n this.cy?.style(this.graphStyle());\n this.draw(this.topology.value());\n });\n this.cy.on('tap', 'edge', e => this.choose({ edge: e.target.id(), node: null }));\n this.cy.on('tap', 'node', e => this.choose({ node: e.target.id(), edge: null }));\n this.cy.on('tap', e => { if (e.target === this.cy) this.choose({ node: null, edge: null }); });\n this.draw(this.topology.value());\n });\n effect(() => this.draw(this.topology.value()));\n effect(() => {\n const id = this.edge() ?? this.node();\n this.cy?.elements().unselect();\n if (id) this.cy?.getElementById(id).select();\n });\n }\n\n private draw(topology: Topology | undefined) {\n if (!this.cy || !topology) return;\n this.cy.elements().remove();\n this.cy.add([\n ...topology.nodes.map(n => ({ data: {\n id: n.id, label: n.displayName, type: n.type,\n color: this.color(n.type === 'service' ? HEALTH_TOKENS[n.health ?? 'Unknown'] ?? 'none' : 'muted') } })),\n ...topology.edges.filter(e => this.nodes().has(e.source) && this.nodes().has(e.target)).map(e => ({ data: {\n id: e.id, source: e.source, target: e.target, confidence: e.confidence.toLowerCase(),\n label: e.confidence.toLowerCase() === 'inferred' ? `${e.relation} (inferred)` : e.relation } })),\n ]);\n this.cy.layout({ name: 'breadthfirst', directed: true, padding: 30, spacingFactor: 1.2 }).run();\n const id = this.edge() ?? this.node();\n if (id) this.cy.getElementById(id).select();\n }\n\n private color(token: string) { return themeColor(token, this.graph().nativeElement); }\n\n /** The graph's look, in the app's theme colours. */\n private graphStyle() {\n const [text, muted, line, surface, selected] = [this.color('text'), this.color('muted'), this.color('border'),\n this.color('surface'), this.color('accent')];\n return [\n { selector: 'node', style: {\n label: 'data(label)', 'font-size': 11, color: text, 'text-valign': 'bottom', 'text-margin-y': 4,\n 'background-color': 'data(color)', width: 26, height: 26, 'border-width': 2, 'border-color': surface } },\n { selector: 'node[type = \"database\"]', style: { shape: 'diamond' } },\n { selector: 'node[type = \"messaging-destination\"]', style: { shape: 'hexagon' } },\n { selector: 'node[type = \"service\"]', style: { shape: 'round-rectangle' } },\n { selector: 'edge', style: {\n width: 2, 'curve-style': 'bezier', 'target-arrow-shape': 'triangle', 'line-color': muted,\n 'target-arrow-color': muted, label: 'data(label)', 'font-size': 9, color: muted, 'text-rotation': 'autorotate',\n 'text-background-color': surface, 'text-background-opacity': 0.8, 'text-background-padding': '1px' } },\n { selector: 'edge[confidence = \"inferred\"]', style: { 'line-style': 'dashed', 'line-color': line, 'target-arrow-color': line } },\n { selector: ':selected', style: { 'line-color': selected, 'target-arrow-color': selected, 'border-color': selected, 'border-width': 3 } },\n ] as StylesheetJson;\n }\n\n fit() { this.cy?.fit(undefined, 30); }\n\n private choose(selection: { edge?: string | null; node?: string | null }) {\n void this.router.navigate([], { queryParams: selection, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n name(id: string) { return this.nodes().get(id)?.displayName ?? id; }\n\n link(id: string) { return this.links.to(this.nodes().get(id)?.type === 'service' ? 'services' : 'resources', id); }\n\n ngOnDestroy() {\n this.destroyed = true;\n this.stopThemeWatch?.();\n this.cy?.destroy();\n }\n}\n"],"mappings":"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"}
@@ -328,7 +328,7 @@ const OBSERVABILITY_ROUTES = [
328
328
  page("services/:id", "Service", () => import("./juice-js-observability-service.page-_yK0kVYS.mjs").then((m) => m.ServicePage)),
329
329
  page("traces", "Traces", () => import("./juice-js-observability-traces.page-DgvNixIc.mjs").then((m) => m.TracesPage)),
330
330
  page("traces/:traceId", "Trace", () => import("./juice-js-observability-trace.page-CkLNNnKf.mjs").then((m) => m.TracePage)),
331
- page("topology", "Dependencies", () => import("./juice-js-observability-topology.page-M_kitXna.mjs").then((m) => m.TopologyPage)),
331
+ page("topology", "Dependencies", () => import("./juice-js-observability-topology.page-B7aXWOYV.mjs").then((m) => m.TopologyPage)),
332
332
  page("logs", "Logs", () => import("./juice-js-observability-logs.page-CZNbTl2Q.mjs").then((m) => m.LogsPage)),
333
333
  page("metrics", "Metrics", () => import("./juice-js-observability-metrics.page-DoVED9MT.mjs").then((m) => m.MetricsPage)),
334
334
  page("resources", "Resources", () => import("./juice-js-observability-resources.page-KadGxEAh.mjs").then((m) => m.ResourcesPage)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juice-js/observability",
3
- "version": "1.0.0-alpha.9",
3
+ "version": "1.0.1-alpha.14",
4
4
  "description": "Juice Observability pages (services, traces, topology, logs, metrics, resources, alerts, rules) for an Angular app on juice-layout",
5
5
  "author": "jack@creatorflow.io",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"file":"juice-js-observability-topology.page-M_kitXna.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/topology.page.ts"],"sourcesContent":["import { LinkPipe, ObservabilityLinks } from '../core/options';\nimport {\n ChangeDetectionStrategy, Component, ElementRef, OnDestroy, afterNextRender, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { Router, RouterLink } from '@angular/router';\nimport type { Core, StylesheetJson } from 'cytoscape';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Topology } from '../core/models';\nimport { AgoPipe, DurationPipe, PercentPipe } from '../shared/format';\nimport { ConfidenceBadge, ContextBar, HealthBadge } from '../shared/ui';\nimport { problemText } from '../shared/telemetry';\nimport { onThemeChange, themeColor } from '../shared/theme';\n\n/** A service's health → its --jc-* colour from the app's theme (observability.colors). */\nconst HEALTH_TOKENS: Record<string, string> = {\n Healthy: 'ok', Degraded: 'warn', Unhealthy: 'bad', Critical: 'crit', Offline: 'off', Unknown: 'none',\n};\n\n/**\n * Dependency topology (Console requirements §9): services, databases and destinations; zoom, pan and selection.\n * Observed edges are solid, inferred ones dashed and labelled, so an inference never reads as a confirmed call. A\n * selected edge shows its evidence and representative traces; the selection is in the URL.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, MatButtonModule, MatIconModule, MatProgressBarModule, ContextBar, HealthBadge, ConfidenceBadge,\n AgoPipe, DurationPipe, PercentPipe],\n template: `\n <jc-context-bar><h1>Dependencies</h1></jc-context-bar>\n @if (topology.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (topology.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n <div class=\"jc-topology\">\n <div class=\"jc-graph-wrap\">\n <div #graph class=\"jc-graph\" aria-label=\"Dependency graph\"></div>\n <div class=\"jc-graph-legend\">\n <span><i class=\"jc-line\"></i> observed</span><span><i class=\"jc-line jc-dashed\"></i> inferred</span>\n <span>■ service ◆ database ⬣ destination</span>\n <button mat-button (click)=\"fit()\">Fit</button>\n </div>\n @if (topology.value()?.truncated) { <p class=\"jc-muted\">Showing the most recent edges only.</p> }\n @if (topology.hasValue() && topology.value()!.nodes.length === 0) { <p class=\"jc-muted\">No dependencies seen{{ ctx.environment() ? ' in ' + ctx.environment() : '' }}.</p> }\n </div>\n\n <aside class=\"jc-card jc-side\">\n @if (selectedEdge(); as e) {\n <h2>{{ name(e.source) }} → {{ name(e.target) }}</h2>\n <p>{{ e.relation }} <jc-confidence [confidence]=\"e.confidence\" /></p>\n @if (e.confidence.toLowerCase() !== 'observed') {\n <p class=\"jc-muted jc-small\">Inferred from span attributes (for example db.system.name or messaging.destination.name), not a call\n seen between two instrumented services.</p>\n }\n @if (evidence.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (evidence.value(); as ev) {\n <dl class=\"jc-facts\">\n <dt>Calls in window</dt><dd>{{ ev.count }}</dd>\n <dt>Error rate</dt><dd [class.jc-bad]=\"ev.errorRate >= 0.05\">{{ ev.count ? (ev.errorRate | percent2) : 'no data' }}</dd>\n <dt>p95</dt><dd>{{ ev.p95Ms | duration }}</dd>\n <dt>Last seen</dt><dd>{{ ev.lastSeen ?? e.lastSeen | ago }}</dd>\n </dl>\n <h3>Representative traces</h3>\n @for (t of ev.errorTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-badge jc-health-unhealthy\">error</span></div> }\n @for (t of ev.slowestTraces; track t) { <div><a [routerLink]=\"['traces', t] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-mono\">{{ t.slice(0, 16) }}…</a> <span class=\"jc-muted jc-small\">slow</span></div> }\n @if (!ev.errorTraces.length && !ev.slowestTraces.length) { <p class=\"jc-muted\">No calls in this window.</p> }\n <a mat-stroked-button [routerLink]=\"['traces'] | jcLink\" [queryParams]=\"ctx.keep({ dependency: e.id })\">All traces through this edge</a>\n }\n <p><a [routerLink]=\"link(e.source)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.source) }}</a> ·\n <a [routerLink]=\"link(e.target)\" [queryParams]=\"ctx.keep()\">Open {{ name(e.target) }}</a></p>\n } @else if (selectedNode(); as n) {\n <h2>{{ n.displayName }}</h2>\n <p>{{ n.type }} <jc-health [state]=\"n.health\" /></p>\n <a mat-stroked-button [routerLink]=\"link(n.id)\" [queryParams]=\"ctx.keep()\">Open</a>\n } @else {\n <p class=\"jc-muted\">Select a node or an edge. Scroll to zoom, drag to pan.</p>\n }\n </aside>\n </div>`,\n})\nexport class TopologyPage implements OnDestroy {\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 edge = input<string>();\n readonly node = input<string>();\n\n private readonly graph = viewChild.required<ElementRef<HTMLDivElement>>('graph');\n private cy?: Core;\n private stopThemeWatch?: () => void;\n\n protected readonly topology = apiResource({ params: () => this.ctx.environment(), stream: ({ params }) => this.api.topology(params || null) });\n protected readonly error = computed(() => problemText(this.topology.error()));\n private readonly nodes = computed(() => new Map((this.topology.value()?.nodes ?? []).map(n => [n.id, n])));\n protected readonly selectedEdge = computed(() => this.topology.value()?.edges.find(e => e.id === this.edge()) ?? null);\n protected readonly selectedNode = computed(() => (this.node() ? this.nodes().get(this.node()!) : null) ?? null);\n protected readonly evidence = apiResource({\n // No edge selected: undefined params leave the resource idle.\n params: () => this.edge() ? { edge: this.edge()!, env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() } : undefined,\n stream: ({ params }) => this.api.dependency(params.edge, { environment: params.env, ...this.ctx.window() }),\n });\n\n constructor() {\n afterNextRender(async () => {\n const cytoscape = (await import('cytoscape')).default;\n this.cy = cytoscape({\n container: this.graph().nativeElement,\n wheelSensitivity: 0.3,\n style: this.graphStyle(),\n });\n // The app switched theme (light/dark): restyle and recolour the nodes.\n this.stopThemeWatch = onThemeChange(() => {\n this.cy?.style(this.graphStyle());\n this.draw(this.topology.value());\n });\n this.cy.on('tap', 'edge', e => this.choose({ edge: e.target.id(), node: null }));\n this.cy.on('tap', 'node', e => this.choose({ node: e.target.id(), edge: null }));\n this.cy.on('tap', e => { if (e.target === this.cy) this.choose({ node: null, edge: null }); });\n this.draw(this.topology.value());\n });\n effect(() => this.draw(this.topology.value()));\n effect(() => {\n const id = this.edge() ?? this.node();\n this.cy?.elements().unselect();\n if (id) this.cy?.getElementById(id).select();\n });\n }\n\n private draw(topology: Topology | undefined) {\n if (!this.cy || !topology) return;\n this.cy.elements().remove();\n this.cy.add([\n ...topology.nodes.map(n => ({ data: {\n id: n.id, label: n.displayName, type: n.type,\n color: this.color(n.type === 'service' ? HEALTH_TOKENS[n.health ?? 'Unknown'] ?? 'none' : 'muted') } })),\n ...topology.edges.filter(e => this.nodes().has(e.source) && this.nodes().has(e.target)).map(e => ({ data: {\n id: e.id, source: e.source, target: e.target, confidence: e.confidence.toLowerCase(),\n label: e.confidence.toLowerCase() === 'inferred' ? `${e.relation} (inferred)` : e.relation } })),\n ]);\n this.cy.layout({ name: 'breadthfirst', directed: true, padding: 30, spacingFactor: 1.2 }).run();\n const id = this.edge() ?? this.node();\n if (id) this.cy.getElementById(id).select();\n }\n\n private color(token: string) { return themeColor(token, this.graph().nativeElement); }\n\n /** The graph's look, in the app's theme colours. */\n private graphStyle() {\n const [text, muted, line, surface, selected] = [this.color('text'), this.color('muted'), this.color('border'),\n this.color('surface'), this.color('accent')];\n return [\n { selector: 'node', style: {\n label: 'data(label)', 'font-size': 11, color: text, 'text-valign': 'bottom', 'text-margin-y': 4,\n 'background-color': 'data(color)', width: 26, height: 26, 'border-width': 2, 'border-color': surface } },\n { selector: 'node[type = \"database\"]', style: { shape: 'diamond' } },\n { selector: 'node[type = \"messaging-destination\"]', style: { shape: 'hexagon' } },\n { selector: 'node[type = \"service\"]', style: { shape: 'round-rectangle' } },\n { selector: 'edge', style: {\n width: 2, 'curve-style': 'bezier', 'target-arrow-shape': 'triangle', 'line-color': muted,\n 'target-arrow-color': muted, label: 'data(label)', 'font-size': 9, color: muted, 'text-rotation': 'autorotate',\n 'text-background-color': surface, 'text-background-opacity': 0.8, 'text-background-padding': '1px' } },\n { selector: 'edge[confidence = \"inferred\"]', style: { 'line-style': 'dashed', 'line-color': line, 'target-arrow-color': line } },\n { selector: ':selected', style: { 'line-color': selected, 'target-arrow-color': selected, 'border-color': selected, 'border-width': 3 } },\n ] as StylesheetJson;\n }\n\n fit() { this.cy?.fit(undefined, 30); }\n\n private choose(selection: { edge?: string | null; node?: string | null }) {\n void this.router.navigate([], { queryParams: selection, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n name(id: string) { return this.nodes().get(id)?.displayName ?? id; }\n\n link(id: string) { return this.links.to(this.nodes().get(id)?.type === 'service' ? 'services' : 'resources', id); }\n\n ngOnDestroy() {\n this.stopThemeWatch?.();\n this.cy?.destroy();\n }\n}\n"],"mappings":"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"}