@juice-js/observability 1.0.0-alpha.1 → 1.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/fesm2022/{juice-js-observability-alert.page-D2OL0zTT.mjs → juice-js-observability-alert.page-TKwqJT2d.mjs} +3 -4
- package/fesm2022/juice-js-observability-alert.page-TKwqJT2d.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-alerts.page-BLJvD_op.mjs → juice-js-observability-alerts.page-B-2uS9wr.mjs} +6 -3
- package/fesm2022/{juice-js-observability-alerts.page-BLJvD_op.mjs.map → juice-js-observability-alerts.page-B-2uS9wr.mjs.map} +1 -1
- package/fesm2022/{juice-js-observability-logs.page-BWFLleyX.mjs → juice-js-observability-logs.page-CZNbTl2Q.mjs} +3 -4
- package/fesm2022/juice-js-observability-logs.page-CZNbTl2Q.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-metrics.page-D1caPbQq.mjs → juice-js-observability-metrics.page-DoVED9MT.mjs} +4 -5
- package/fesm2022/juice-js-observability-metrics.page-DoVED9MT.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-overview.page-UBwHjlyq.mjs → juice-js-observability-overview.page-Cod7-cIy.mjs} +49 -13
- package/fesm2022/juice-js-observability-overview.page-Cod7-cIy.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-resource.page-BZwLK_vU.mjs → juice-js-observability-resource.page-C8RhUipF.mjs} +4 -5
- package/fesm2022/juice-js-observability-resource.page-C8RhUipF.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-resources.page-C0xWOb5I.mjs → juice-js-observability-resources.page-KadGxEAh.mjs} +3 -4
- package/fesm2022/juice-js-observability-resources.page-KadGxEAh.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-rules.page-Db_U5rRu.mjs → juice-js-observability-rules.page-DKWvNpu9.mjs} +3 -4
- package/fesm2022/juice-js-observability-rules.page-DKWvNpu9.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-service.page-B-SWJ9I5.mjs → juice-js-observability-service.page-_yK0kVYS.mjs} +7 -8
- package/fesm2022/juice-js-observability-service.page-_yK0kVYS.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-services.page-BmWgFnC8.mjs → juice-js-observability-services.page-BtG8zyiI.mjs} +3 -4
- package/fesm2022/juice-js-observability-services.page-BtG8zyiI.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-telemetry-VA4_3nzr.mjs → juice-js-observability-telemetry-B5jMJYfn.mjs} +73 -20
- package/fesm2022/juice-js-observability-telemetry-B5jMJYfn.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-topology.page-DEUNp10U.mjs → juice-js-observability-topology.page-B7aXWOYV.mjs} +97 -69
- package/fesm2022/juice-js-observability-topology.page-B7aXWOYV.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-trace.page-gBhMG2eQ.mjs → juice-js-observability-trace.page-CkLNNnKf.mjs} +16 -18
- package/fesm2022/juice-js-observability-trace.page-CkLNNnKf.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-traces.page-BCQg4V4o.mjs → juice-js-observability-traces.page-DgvNixIc.mjs} +7 -8
- package/fesm2022/juice-js-observability-traces.page-DgvNixIc.mjs.map +1 -0
- package/fesm2022/juice-js-observability.mjs +25 -16
- package/fesm2022/juice-js-observability.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/_observability.scss +216 -129
- package/types/juice-js-observability.d.ts +14 -1
- package/fesm2022/juice-js-observability-alert.page-D2OL0zTT.mjs.map +0 -1
- package/fesm2022/juice-js-observability-logs.page-BWFLleyX.mjs.map +0 -1
- package/fesm2022/juice-js-observability-metrics.page-D1caPbQq.mjs.map +0 -1
- package/fesm2022/juice-js-observability-overview.page-UBwHjlyq.mjs.map +0 -1
- package/fesm2022/juice-js-observability-resource.page-BZwLK_vU.mjs.map +0 -1
- package/fesm2022/juice-js-observability-resources.page-C0xWOb5I.mjs.map +0 -1
- package/fesm2022/juice-js-observability-rules.page-Db_U5rRu.mjs.map +0 -1
- package/fesm2022/juice-js-observability-service.page-B-SWJ9I5.mjs.map +0 -1
- package/fesm2022/juice-js-observability-services.page-BmWgFnC8.mjs.map +0 -1
- package/fesm2022/juice-js-observability-telemetry-VA4_3nzr.mjs.map +0 -1
- package/fesm2022/juice-js-observability-topology.page-DEUNp10U.mjs.map +0 -1
- package/fesm2022/juice-js-observability-trace.page-gBhMG2eQ.mjs.map +0 -1
- package/fesm2022/juice-js-observability-traces.page-BCQg4V4o.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"juice-js-observability.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/core/options.ts","../../../../projects/juice-js/observability/src/lib/core/api.service.ts","../../../../projects/juice-js/observability/src/lib/core/context.service.ts","../../../../projects/juice-js/observability/src/lib/observability.routes.ts"],"sourcesContent":["import { EnvironmentProviders, Injectable, InjectionToken, Pipe, PipeTransform, inject, makeEnvironmentProviders } from '@angular/core';\n\n/** How the host app mounts Juice Observability. */\nexport interface ObservabilityOptions {\n /** The Juice API origin; empty when the API serves the app (same origin). */\n apiBase: string;\n /** Where the host mounts {@link OBSERVABILITY_ROUTES}: `/` for the whole app, or a path such as `/observability`. */\n basePath: string;\n}\n\nexport const OBSERVABILITY_OPTIONS = new InjectionToken<ObservabilityOptions>('OBSERVABILITY_OPTIONS', {\n providedIn: 'root',\n factory: () => ({ apiBase: '', basePath: '/' }),\n});\n\n/** Configures the pages: where the API is and where the host mounted the routes. */\nexport function provideJuiceObservability(options: Partial<ObservabilityOptions> = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: OBSERVABILITY_OPTIONS, useValue: { apiBase: options.apiBase ?? '', basePath: options.basePath ?? '/' } },\n ]);\n}\n\n/** `/observability/` → `/observability`; `` and `/` → `/`. */\nexport function normalizeBasePath(basePath: string): string {\n const trimmed = basePath.trim().replace(/^\\/+|\\/+$/g, '');\n return trimmed ? `/${trimmed}` : '/';\n}\n\n/** Router links to the pages, under the host's mount path. */\n@Injectable({ providedIn: 'root' })\nexport class ObservabilityLinks {\n readonly base = normalizeBasePath(inject(OBSERVABILITY_OPTIONS).basePath);\n\n /** `to('services', id)` → `['/observability', 'services', id]`. */\n to(...segments: unknown[]): unknown[] { return [this.base, ...segments]; }\n}\n\n/** `['services', id] | jcLink`: the same as {@link ObservabilityLinks.to} in a template. */\n@Pipe({ name: 'jcLink' })\nexport class LinkPipe implements PipeTransform {\n private readonly links = inject(ObservabilityLinks);\n transform(segments: unknown[]): unknown[] { return this.links.to(...segments); }\n}\n","import { HttpClient, HttpParams } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { EMPTY, Observable, expand, map, reduce, shareReplay, switchMap } from 'rxjs';\nimport { OBSERVABILITY_OPTIONS } from './options';\nimport {\n AlertDetail, Alert, Application, DependencyEvidence, DeploymentEnvironment, Health, LogRecord, MetricInfo, Operation,\n Page, ResourceDetail, ResourceSummary, Rule, SearchHit, Series, ServiceStats, Tenant, TelemetryPage, Topology, Trace,\n TraceSummary,\n} from './models';\n\nexport type Query = Record<string, string | number | boolean | string[] | null | undefined>;\n\n/**\n * The Console's only backend: the Juice API (Console requirements §3). One deployment serves one tenant, the one the\n * signed-in user's token names.\n */\n@Injectable({ providedIn: 'root' })\nexport class ApiService {\n private readonly http = inject(HttpClient);\n private readonly base = `${inject(OBSERVABILITY_OPTIONS).apiBase}/api/v1`;\n\n /** The caller's tenant, fetched once. */\n readonly tenant$: Observable<Tenant> = this.http.get<Page<Tenant>>(`${this.base}/tenants`).pipe(\n map(page => {\n if (page.items.length === 0) throw new Error('No tenant is authorized for this user.');\n return page.items[0];\n }),\n shareReplay(1),\n );\n\n private get<T>(path: string, query: Query = {}): Observable<T> {\n let params = new HttpParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === null || value === undefined || value === '') continue;\n for (const item of Array.isArray(value) ? value : [value]) params = params.append(key, String(item));\n }\n return this.tenant$.pipe(switchMap(t => this.http.get<T>(`${this.base}/tenants/${t.id}${path}`, { params })));\n }\n\n /** Every page of a cursor-paged list (bounded: at most `maxPages` requests). */\n private all<T>(path: string, query: Query = {}, maxPages = 25): Observable<Page<T>> {\n let pages = 0;\n return this.get<Page<T>>(path, { ...query, limit: 200 }).pipe(\n expand(page => page.nextCursor && ++pages < maxPages ? this.get<Page<T>>(path, { ...query, limit: 200, cursor: page.nextCursor }) : EMPTY),\n reduce((all, page) => ({ items: [...all.items, ...page.items], nextCursor: page.nextCursor }), { items: [] as T[], nextCursor: null as string | null }),\n );\n }\n\n environments() { return this.get<Page<DeploymentEnvironment>>('/environments'); }\n applications() { return this.get<Page<Application>>('/applications', { limit: 200 }); }\n resources(type?: string) { return this.all<ResourceSummary>('/resources', { type }); }\n resource(id: string) { return this.get<ResourceDetail>(`/resources/${id}`); }\n health(query: Query) { return this.all<Health>('/health', query); }\n topology(environment?: string | null) { return this.get<Topology>('/topology', { environment }); }\n\n rules() { return this.get<Page<Rule>>('/rules', { limit: 200 }); }\n /** One page of alerts, newest first; pass the previous page's `nextCursor` as `cursor` for the next. */\n alerts(query: Query) { return this.get<Page<Alert>>('/alerts', { limit: 100, ...query }); }\n /** Every alert matching the query (bounded, like the other complete lists). */\n allAlerts(query: Query) { return this.all<Alert>('/alerts', query); }\n alert(id: string) { return this.get<AlertDetail>(`/alerts/${id}`); }\n acknowledge(id: string, comment: string): Observable<Alert> {\n return this.tenant$.pipe(switchMap(t => this.http.post<Alert>(`${this.base}/tenants/${t.id}/alerts/${id}/acknowledgement`,\n { comment }, { headers: { 'Idempotency-Key': crypto.randomUUID() } })));\n }\n\n traces(query: Query) { return this.get<TelemetryPage<TraceSummary>>('/telemetry/traces', query); }\n trace(traceId: string) { return this.get<Trace>(`/telemetry/traces/${traceId}`); }\n logs(query: Query) { return this.get<TelemetryPage<LogRecord>>('/telemetry/logs', query); }\n operations(query: Query) { return this.get<TelemetryPage<Operation>>('/telemetry/operations', query); }\n metrics(query: Query) { return this.get<TelemetryPage<MetricInfo>>('/telemetry/metrics', query); }\n series(query: Query) { return this.get<Series>('/telemetry/metrics/series', query); }\n serviceStats(query: Query) { return this.get<ServiceStats>('/telemetry/service-stats', query); }\n dependency(id: string, query: Query) { return this.get<DependencyEvidence>(`/telemetry/dependencies/${id}`, query); }\n search(q: string) { return this.get<TelemetryPage<SearchHit>>('/search', { q }); }\n}\n","import { Injectable, computed, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router';\nimport { filter, map, startWith } from 'rxjs';\n\nexport const RANGES: Record<string, number> = {\n '15m': 15 * 60_000, '1h': 3_600_000, '6h': 6 * 3_600_000, '24h': 24 * 3_600_000, '7d': 7 * 24 * 3_600_000,\n};\n\n/** Search ranges are at most 24 hours (the API's bound); series and operations take up to 7 days. */\nexport const MAX_SEARCH_MS = RANGES['24h'];\n\n/**\n * The investigation context that follows the user between pages (Console requirements §17): environment and time\n * range. It lives in the URL (`env`, and `range` or `from`/`to`), so every page is a deep link and the browser's back\n * button restores it.\n */\n@Injectable({ providedIn: 'root' })\nexport class ContextService {\n private readonly router = inject(Router);\n\n // Created on first use, after the first navigation may already have ended: start from the current URL.\n private readonly params = toSignal(this.router.events.pipe(\n filter(e => e instanceof NavigationEnd),\n startWith(null),\n map(() => this.leaf().snapshot.queryParams),\n ), { initialValue: {} as Params });\n\n readonly environment = computed(() => (this.params()['env'] as string | undefined) ?? '');\n readonly range = computed(() => {\n const p = this.params();\n return p['from'] && p['to'] ? 'custom' : ((p['range'] as string | undefined) ?? '1h');\n });\n readonly absolute = computed(() => {\n const p = this.params();\n return p['from'] && p['to'] ? { from: p['from'] as string, to: p['to'] as string } : null;\n });\n\n /** The window as ISO strings, evaluated now for a relative range. */\n window(maxMs?: number): { from: string; to: string } {\n const absolute = this.absolute();\n if (absolute) return absolute;\n const to = Date.now();\n const span = Math.min(RANGES[this.range()] ?? RANGES['1h'], maxMs ?? Number.MAX_SAFE_INTEGER);\n return { from: new Date(to - span).toISOString(), to: new Date(to).toISOString() };\n }\n\n /** Query parameters for a link: the context plus the target page's own. */\n keep(extra: Params = {}): Params {\n const p = this.params();\n const context: Params = {};\n for (const key of ['env', 'range', 'from', 'to']) if (p[key]) context[key] = p[key];\n return { ...context, ...extra };\n }\n\n /** Changes the context on the current page, keeping its other parameters. */\n set(change: { env?: string | null; range?: string | null; from?: string | null; to?: string | null }) {\n const queryParams: Params = { ...change };\n if (change.range) Object.assign(queryParams, { from: null, to: null });\n void this.router.navigate([], { relativeTo: this.leaf(), queryParams, queryParamsHandling: 'merge' });\n }\n\n /** A time window centred on a moment, for \"around this span\" links. */\n around(at: string, durationMs: number, paddingMs = 5 * 60_000): Params {\n const start = new Date(at).getTime();\n return this.keep({\n from: new Date(start - paddingMs).toISOString(),\n to: new Date(start + durationMs + paddingMs).toISOString(),\n range: null,\n });\n }\n\n private leaf(): ActivatedRoute {\n let route = this.router.routerState.root;\n while (route.firstChild) route = route.firstChild;\n return route;\n }\n}\n","import { Routes } from '@angular/router';\nimport { Menus } from '@juice-js/core';\nimport { normalizeBasePath } from './core/options';\n\nfunction page(path: string, title: string, load: Routes[number]['loadComponent']): Routes[number] {\n return { path, title: `${title} · Juice Observability`, loadComponent: load };\n}\n\n/**\n * The pages, investigation first (Console requirements §4): services, traces, topology, logs and metrics; alerts and\n * rules after. Detail pages are deep links (§17) and carry the environment and time range in their query parameters.\n * The host mounts them at its `basePath` (see {@link provideJuiceObservability}) and adds its own guards.\n */\nexport const OBSERVABILITY_ROUTES: Routes = [\n page('', 'Overview', () => import('./pages/overview.page').then(m => m.OverviewPage)),\n page('services', 'Services', () => import('./pages/services.page').then(m => m.ServicesPage)),\n page('services/:id', 'Service', () => import('./pages/service.page').then(m => m.ServicePage)),\n page('traces', 'Traces', () => import('./pages/traces.page').then(m => m.TracesPage)),\n page('traces/:traceId', 'Trace', () => import('./pages/trace.page').then(m => m.TracePage)),\n page('topology', 'Dependencies', () => import('./pages/topology.page').then(m => m.TopologyPage)),\n page('logs', 'Logs', () => import('./pages/logs.page').then(m => m.LogsPage)),\n page('metrics', 'Metrics', () => import('./pages/metrics.page').then(m => m.MetricsPage)),\n page('resources', 'Resources', () => import('./pages/resources.page').then(m => m.ResourcesPage)),\n page('resources/:id', 'Resource', () => import('./pages/resource.page').then(m => m.ResourcePage)),\n page('alerts', 'Alerts', () => import('./pages/alerts.page').then(m => m.AlertsPage)),\n page('alerts/:id', 'Alert', () => import('./pages/alert.page').then(m => m.AlertPage)),\n page('rules', 'Rules', () => import('./pages/rules.page').then(m => m.RulesPage)),\n];\n\n/** The side menu entries (juice-layout), investigation pages first, linking under `basePath`. */\nexport function observabilityMenus(basePath = '/', order = 1): Menus {\n const base = normalizeBasePath(basePath);\n const link = (path: string) => (base === '/' ? `/${path}` : path ? `${base}/${path}` : base);\n return [\n { id: 'overview', label: 'Overview', icon: 'dashboard', routerLink: link('') },\n { id: 'services', label: 'Services', icon: 'hub', routerLink: link('services') },\n { id: 'traces', label: 'Traces', icon: 'timeline', routerLink: link('traces') },\n { id: 'topology', label: 'Dependencies', icon: 'account_tree', routerLink: link('topology') },\n { id: 'logs', label: 'Logs', icon: 'notes', routerLink: link('logs') },\n { id: 'metrics', label: 'Metrics', icon: 'show_chart', routerLink: link('metrics') },\n { id: 'resources', label: 'Resources', icon: 'dns', routerLink: link('resources') },\n { id: 'alerts', label: 'Alerts', icon: 'notifications', routerLink: link('alerts') },\n { id: 'rules', label: 'Rules', icon: 'rule', routerLink: link('rules') },\n ].map((menu, index) => ({ ...menu, order: order + index }));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"juice-js-observability.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/core/options.ts","../../../../projects/juice-js/observability/src/lib/core/api.service.ts","../../../../projects/juice-js/observability/src/lib/core/context.service.ts","../../../../projects/juice-js/observability/src/lib/observability.routes.ts"],"sourcesContent":["import { EnvironmentProviders, Injectable, InjectionToken, Pipe, PipeTransform, inject, makeEnvironmentProviders } from '@angular/core';\n\n/** How the host app mounts Juice Observability. */\nexport interface ObservabilityOptions {\n /** The Juice API origin; empty when the API serves the app (same origin). */\n apiBase: string;\n /** Where the host mounts {@link OBSERVABILITY_ROUTES}: `/` for the whole app, or a path such as `/observability`. */\n basePath: string;\n /**\n * A link to the audit records of a trace, shown on the trace page: for the Juice Audit pages mounted at `/audit`,\n * `id => ['/audit/traces', id]`. A string is an external URL. Without it, no link.\n */\n auditLink?: ((traceId: string) => unknown[] | string | null) | null;\n}\n\nexport const OBSERVABILITY_OPTIONS = new InjectionToken<ObservabilityOptions>('OBSERVABILITY_OPTIONS', {\n providedIn: 'root',\n factory: () => ({ apiBase: '', basePath: '/', auditLink: null }),\n});\n\n/** Configures the pages: where the API is, where the host mounted the routes, and where a trace's audit records open. */\nexport function provideJuiceObservability(options: Partial<ObservabilityOptions> = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: OBSERVABILITY_OPTIONS,\n useValue: {\n apiBase: options.apiBase ?? '',\n basePath: options.basePath ?? '/',\n auditLink: options.auditLink ?? null,\n } satisfies ObservabilityOptions,\n },\n ]);\n}\n\n/** `/observability/` → `/observability`; `` and `/` → `/`. */\nexport function normalizeBasePath(basePath: string): string {\n const trimmed = basePath.trim().replace(/^\\/+|\\/+$/g, '');\n return trimmed ? `/${trimmed}` : '/';\n}\n\n/** Router links to the pages, under the host's mount path. */\n@Injectable({ providedIn: 'root' })\nexport class ObservabilityLinks {\n private readonly options = inject(OBSERVABILITY_OPTIONS);\n readonly base = normalizeBasePath(this.options.basePath);\n\n /** `to('services', id)` → `['/observability', 'services', id]`. */\n to(...segments: unknown[]): unknown[] { return [this.base, ...segments]; }\n\n /** The host's link to the audit records of a trace, if it gave one. */\n audit(traceId: string | null | undefined): { route: unknown[] } | { href: string } | null {\n if (!traceId || !this.options.auditLink) return null;\n const link = this.options.auditLink(traceId);\n if (!link) return null;\n return typeof link === 'string' ? { href: link } : { route: link };\n }\n}\n\n/** `['services', id] | jcLink`: the same as {@link ObservabilityLinks.to} in a template. */\n@Pipe({ name: 'jcLink' })\nexport class LinkPipe implements PipeTransform {\n private readonly links = inject(ObservabilityLinks);\n transform(segments: unknown[]): unknown[] { return this.links.to(...segments); }\n}\n","import { HttpClient, HttpParams } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { EMPTY, Observable, expand, map, reduce, shareReplay, switchMap } from 'rxjs';\nimport { OBSERVABILITY_OPTIONS } from './options';\nimport {\n AlertDetail, Alert, Application, DependencyEvidence, DeploymentEnvironment, Health, LogRecord, MetricInfo, Operation,\n Page, ResourceDetail, ResourceSummary, Rule, SearchHit, Series, ServiceStats, Tenant, TelemetryPage, Topology, Trace,\n TraceSummary,\n} from './models';\n\nexport type Query = Record<string, string | number | boolean | string[] | null | undefined>;\n\n/**\n * The Console's only backend: the Juice API (Console requirements §3). One deployment serves one tenant, the one the\n * signed-in user's token names.\n */\n@Injectable({ providedIn: 'root' })\nexport class ApiService {\n private readonly http = inject(HttpClient);\n private readonly base = `${inject(OBSERVABILITY_OPTIONS).apiBase}/api/v1`;\n\n /** The caller's tenant, fetched once. */\n readonly tenant$: Observable<Tenant> = this.http.get<Page<Tenant>>(`${this.base}/tenants`).pipe(\n map(page => {\n if (page.items.length === 0) throw new Error('No tenant is authorized for this user.');\n return page.items[0];\n }),\n shareReplay(1),\n );\n\n private get<T>(path: string, query: Query = {}): Observable<T> {\n let params = new HttpParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === null || value === undefined || value === '') continue;\n for (const item of Array.isArray(value) ? value : [value]) params = params.append(key, String(item));\n }\n return this.tenant$.pipe(switchMap(t => this.http.get<T>(`${this.base}/tenants/${t.id}${path}`, { params })));\n }\n\n /** Every page of a cursor-paged list (bounded: at most `maxPages` requests). */\n private all<T>(path: string, query: Query = {}, maxPages = 25): Observable<Page<T>> {\n let pages = 0;\n return this.get<Page<T>>(path, { ...query, limit: 200 }).pipe(\n expand(page => page.nextCursor && ++pages < maxPages ? this.get<Page<T>>(path, { ...query, limit: 200, cursor: page.nextCursor }) : EMPTY),\n reduce((all, page) => ({ items: [...all.items, ...page.items], nextCursor: page.nextCursor }), { items: [] as T[], nextCursor: null as string | null }),\n );\n }\n\n environments() { return this.get<Page<DeploymentEnvironment>>('/environments'); }\n applications() { return this.get<Page<Application>>('/applications', { limit: 200 }); }\n resources(type?: string) { return this.all<ResourceSummary>('/resources', { type }); }\n resource(id: string) { return this.get<ResourceDetail>(`/resources/${id}`); }\n health(query: Query) { return this.all<Health>('/health', query); }\n topology(environment?: string | null) { return this.get<Topology>('/topology', { environment }); }\n\n rules() { return this.get<Page<Rule>>('/rules', { limit: 200 }); }\n /** One page of alerts, newest first; pass the previous page's `nextCursor` as `cursor` for the next. */\n alerts(query: Query) { return this.get<Page<Alert>>('/alerts', { limit: 100, ...query }); }\n /** Every alert matching the query (bounded, like the other complete lists). */\n allAlerts(query: Query) { return this.all<Alert>('/alerts', query); }\n alert(id: string) { return this.get<AlertDetail>(`/alerts/${id}`); }\n acknowledge(id: string, comment: string): Observable<Alert> {\n return this.tenant$.pipe(switchMap(t => this.http.post<Alert>(`${this.base}/tenants/${t.id}/alerts/${id}/acknowledgement`,\n { comment }, { headers: { 'Idempotency-Key': crypto.randomUUID() } })));\n }\n\n traces(query: Query) { return this.get<TelemetryPage<TraceSummary>>('/telemetry/traces', query); }\n trace(traceId: string) { return this.get<Trace>(`/telemetry/traces/${traceId}`); }\n logs(query: Query) { return this.get<TelemetryPage<LogRecord>>('/telemetry/logs', query); }\n operations(query: Query) { return this.get<TelemetryPage<Operation>>('/telemetry/operations', query); }\n metrics(query: Query) { return this.get<TelemetryPage<MetricInfo>>('/telemetry/metrics', query); }\n series(query: Query) { return this.get<Series>('/telemetry/metrics/series', query); }\n serviceStats(query: Query) { return this.get<ServiceStats>('/telemetry/service-stats', query); }\n dependency(id: string, query: Query) { return this.get<DependencyEvidence>(`/telemetry/dependencies/${id}`, query); }\n search(q: string) { return this.get<TelemetryPage<SearchHit>>('/search', { q }); }\n}\n","import { Injectable, computed, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router';\nimport { filter, map, startWith } from 'rxjs';\n\nexport const RANGES: Record<string, number> = {\n '15m': 15 * 60_000, '1h': 3_600_000, '6h': 6 * 3_600_000, '24h': 24 * 3_600_000, '7d': 7 * 24 * 3_600_000,\n};\n\n/** Search ranges are at most 24 hours (the API's bound); series and operations take up to 7 days. */\nexport const MAX_SEARCH_MS = RANGES['24h'];\n\n/**\n * The investigation context that follows the user between pages (Console requirements §17): environment and time\n * range. It lives in the URL (`env`, and `range` or `from`/`to`), so every page is a deep link and the browser's back\n * button restores it.\n */\n@Injectable({ providedIn: 'root' })\nexport class ContextService {\n private readonly router = inject(Router);\n\n // Created on first use, after the first navigation may already have ended: start from the current URL.\n private readonly params = toSignal(this.router.events.pipe(\n filter(e => e instanceof NavigationEnd),\n startWith(null),\n map(() => this.leaf().snapshot.queryParams),\n ), { initialValue: {} as Params });\n\n readonly environment = computed(() => (this.params()['env'] as string | undefined) ?? '');\n readonly range = computed(() => {\n const p = this.params();\n return p['from'] && p['to'] ? 'custom' : ((p['range'] as string | undefined) ?? '1h');\n });\n readonly absolute = computed(() => {\n const p = this.params();\n return p['from'] && p['to'] ? { from: p['from'] as string, to: p['to'] as string } : null;\n });\n\n /** The window as ISO strings, evaluated now for a relative range. */\n window(maxMs?: number): { from: string; to: string } {\n const absolute = this.absolute();\n if (absolute) return absolute;\n const to = Date.now();\n const span = Math.min(RANGES[this.range()] ?? RANGES['1h'], maxMs ?? Number.MAX_SAFE_INTEGER);\n return { from: new Date(to - span).toISOString(), to: new Date(to).toISOString() };\n }\n\n /** Query parameters for a link: the context plus the target page's own. */\n keep(extra: Params = {}): Params {\n const p = this.params();\n const context: Params = {};\n for (const key of ['env', 'range', 'from', 'to']) if (p[key]) context[key] = p[key];\n return { ...context, ...extra };\n }\n\n /** Changes the context on the current page, keeping its other parameters. */\n set(change: { env?: string | null; range?: string | null; from?: string | null; to?: string | null }) {\n const queryParams: Params = { ...change };\n if (change.range) Object.assign(queryParams, { from: null, to: null });\n void this.router.navigate([], { relativeTo: this.leaf(), queryParams, queryParamsHandling: 'merge' });\n }\n\n /** A time window centred on a moment, for \"around this span\" links. */\n around(at: string, durationMs: number, paddingMs = 5 * 60_000): Params {\n const start = new Date(at).getTime();\n return this.keep({\n from: new Date(start - paddingMs).toISOString(),\n to: new Date(start + durationMs + paddingMs).toISOString(),\n range: null,\n });\n }\n\n private leaf(): ActivatedRoute {\n let route = this.router.routerState.root;\n while (route.firstChild) route = route.firstChild;\n return route;\n }\n}\n","import { Routes } from '@angular/router';\nimport { Menus } from '@juice-js/core';\nimport { normalizeBasePath } from './core/options';\n\nfunction page(path: string, title: string, load: Routes[number]['loadComponent']): Routes[number] {\n return { path, title: `${title} · Juice Observability`, loadComponent: load };\n}\n\n/**\n * The pages, investigation first (Console requirements §4): services, traces, topology, logs and metrics; alerts and\n * rules after. Detail pages are deep links (§17) and carry the environment and time range in their query parameters.\n * The host mounts them at its `basePath` (see {@link provideJuiceObservability}) and adds its own guards.\n */\nexport const OBSERVABILITY_ROUTES: Routes = [\n page('', 'Overview', () => import('./pages/overview.page').then(m => m.OverviewPage)),\n page('services', 'Services', () => import('./pages/services.page').then(m => m.ServicesPage)),\n page('services/:id', 'Service', () => import('./pages/service.page').then(m => m.ServicePage)),\n page('traces', 'Traces', () => import('./pages/traces.page').then(m => m.TracesPage)),\n page('traces/:traceId', 'Trace', () => import('./pages/trace.page').then(m => m.TracePage)),\n page('topology', 'Dependencies', () => import('./pages/topology.page').then(m => m.TopologyPage)),\n page('logs', 'Logs', () => import('./pages/logs.page').then(m => m.LogsPage)),\n page('metrics', 'Metrics', () => import('./pages/metrics.page').then(m => m.MetricsPage)),\n page('resources', 'Resources', () => import('./pages/resources.page').then(m => m.ResourcesPage)),\n page('resources/:id', 'Resource', () => import('./pages/resource.page').then(m => m.ResourcePage)),\n page('alerts', 'Alerts', () => import('./pages/alerts.page').then(m => m.AlertsPage)),\n page('alerts/:id', 'Alert', () => import('./pages/alert.page').then(m => m.AlertPage)),\n page('rules', 'Rules', () => import('./pages/rules.page').then(m => m.RulesPage)),\n];\n\n/** The side menu entries (juice-layout), investigation pages first, linking under `basePath`. */\nexport function observabilityMenus(basePath = '/', order = 1): Menus {\n const base = normalizeBasePath(basePath);\n const link = (path: string) => (base === '/' ? `/${path}` : path ? `${base}/${path}` : base);\n return [\n { id: 'overview', label: 'Overview', icon: 'dashboard', routerLink: link('') },\n { id: 'services', label: 'Services', icon: 'hub', routerLink: link('services') },\n { id: 'traces', label: 'Traces', icon: 'timeline', routerLink: link('traces') },\n { id: 'topology', label: 'Dependencies', icon: 'account_tree', routerLink: link('topology') },\n { id: 'logs', label: 'Logs', icon: 'notes', routerLink: link('logs') },\n { id: 'metrics', label: 'Metrics', icon: 'show_chart', routerLink: link('metrics') },\n { id: 'resources', label: 'Resources', icon: 'dns', routerLink: link('resources') },\n { id: 'alerts', label: 'Alerts', icon: 'notifications', routerLink: link('alerts') },\n { id: 'rules', label: 'Rules', icon: 'rule', routerLink: link('rules') },\n ].map((menu, index) => ({ ...menu, order: order + index }));\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juice-js/observability",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.11",
|
|
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,133 +1,220 @@
|
|
|
1
|
-
// Juice Observability page styles
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
// Juice Observability page styles. `@use` the module once, in the app's global styles, and the pages are styled:
|
|
2
|
+
//
|
|
3
|
+
// @use '@juice-js/observability'; // layout, and a default light and dark (.dark) theme
|
|
4
|
+
// :root { @include observability.colors($light-theme); } // optional: colours from the app's own theme
|
|
5
|
+
// .dark { @include observability.colors($dark-theme); } // again under the app's own dark-mode selector
|
|
6
|
+
//
|
|
7
|
+
// colors() writes the --jc-* custom properties the pages (and their charts and graph) read. Every value comes from the
|
|
8
|
+
// theme: text, background, card and divider from its background and foreground palettes; links and selection from
|
|
9
|
+
// primary; errors from warn. A theme has no slot for "healthy", "degraded" or a categorical palette, so pass those in
|
|
10
|
+
// $status and $series; without them they use Material's green, amber and a mix of its palettes at the theme's lightness.
|
|
11
|
+
//
|
|
12
|
+
// Without colors(), the pages use the Juice Console's teal, amber and red theme. They sit under zero-specificity :where()
|
|
13
|
+
// selectors, so the app's colors() wins whatever the order.
|
|
14
|
+
@use '@angular/material' as mat;
|
|
15
|
+
@use 'sass:color';
|
|
16
|
+
@use 'sass:list';
|
|
17
|
+
@use 'sass:map';
|
|
18
|
+
@use 'sass:meta';
|
|
19
|
+
|
|
20
|
+
@function _hue($palette, $light, $dark, $is-dark) {
|
|
21
|
+
@return mat.m2-get-color-from-palette($palette, if(sass($is-dark): $dark; else: $light));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@function _tint($color, $surface, $amount) {
|
|
25
|
+
@return color.mix($color, $surface, $amount);
|
|
11
26
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
|
|
28
|
+
/// The --jc-* colour properties from an Angular Material (M2) theme. Include it inside a selector: :root for the
|
|
29
|
+
/// default theme, and the app's dark-mode selector (juice-layout: .dark) for the dark one.
|
|
30
|
+
/// @param $theme the app's theme, from mat.m2-define-light-theme or mat.m2-define-dark-theme
|
|
31
|
+
/// @param $status optional overrides: (ok, warn, bad, crit, off, none) — each a colour; backgrounds are tinted from them
|
|
32
|
+
/// @param $series optional list of colours for services in a trace and lines in a chart
|
|
33
|
+
@mixin colors($theme, $status: (), $series: null) {
|
|
34
|
+
$config: mat.m2-get-color-config($theme);
|
|
35
|
+
$is-dark: map.get($config, is-dark);
|
|
36
|
+
$background: map.get($config, background);
|
|
37
|
+
$foreground: map.get($config, foreground);
|
|
38
|
+
$primary: map.get($config, primary);
|
|
39
|
+
$warn: map.get($config, warn);
|
|
40
|
+
$surface: map.get($background, card);
|
|
41
|
+
$tint: if(sass($is-dark): 22%; else: 14%);
|
|
42
|
+
|
|
43
|
+
$defaults: (
|
|
44
|
+
ok: _hue(mat.m2-define-palette(mat.$m2-green-palette), 800, 300, $is-dark),
|
|
45
|
+
warn: if(sass($is-dark): _hue(mat.m2-define-palette(mat.$m2-amber-palette), 300, 300, true); else: _hue(mat.m2-define-palette(mat.$m2-deep-orange-palette), 900, 900, false)),
|
|
46
|
+
bad: _hue($warn, 700, 300, $is-dark),
|
|
47
|
+
crit: _hue($warn, 900, 100, $is-dark),
|
|
48
|
+
off: map.get($foreground, secondary-text),
|
|
49
|
+
none: map.get($foreground, hint-text),
|
|
50
|
+
);
|
|
51
|
+
$status: map.merge($defaults, $status);
|
|
52
|
+
|
|
53
|
+
--jc-bg: #{map.get($background, background)};
|
|
54
|
+
--jc-surface: #{$surface};
|
|
55
|
+
--jc-border: #{map.get($foreground, divider)};
|
|
56
|
+
--jc-text: #{map.get($foreground, text)};
|
|
57
|
+
--jc-muted: #{map.get($foreground, secondary-text)};
|
|
58
|
+
--jc-accent: #{_hue($primary, 700, 200, $is-dark)};
|
|
59
|
+
--jc-select: #{_tint(_hue($primary, 500, 300, $is-dark), $surface, $tint)};
|
|
60
|
+
@each $name, $value in $status {
|
|
61
|
+
@if meta.type-of($value) == color and color.alpha($value) < 1 {
|
|
62
|
+
// Foreground palette entries are translucent: flatten them on the surface so the tint below is solid.
|
|
63
|
+
$value: color.mix(color.change($value, $alpha: 1), $surface, color.alpha($value) * 100%);
|
|
64
|
+
}
|
|
65
|
+
--jc-#{$name}: #{$value};
|
|
66
|
+
--jc-#{$name}-bg: #{_tint($value, $surface, $tint)};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
$series: if(sass($series != null): $series; else: (
|
|
70
|
+
_hue(mat.m2-define-palette(mat.$m2-blue-palette), 600, 300, $is-dark),
|
|
71
|
+
_hue(mat.m2-define-palette(mat.$m2-deep-purple-palette), 400, 200, $is-dark),
|
|
72
|
+
_hue(mat.m2-define-palette(mat.$m2-teal-palette), 500, 200, $is-dark),
|
|
73
|
+
_hue(mat.m2-define-palette(mat.$m2-amber-palette), 700, 300, $is-dark),
|
|
74
|
+
_hue(mat.m2-define-palette(mat.$m2-pink-palette), 400, 200, $is-dark),
|
|
75
|
+
_hue(mat.m2-define-palette(mat.$m2-green-palette), 600, 300, $is-dark),
|
|
76
|
+
_hue(mat.m2-define-palette(mat.$m2-cyan-palette), 600, 300, $is-dark),
|
|
77
|
+
_hue(mat.m2-define-palette(mat.$m2-purple-palette), 400, 200, $is-dark),
|
|
78
|
+
_hue(mat.m2-define-palette(mat.$m2-light-green-palette), 700, 300, $is-dark),
|
|
79
|
+
_hue(mat.m2-define-palette(mat.$m2-deep-orange-palette), 500, 300, $is-dark),
|
|
80
|
+
));
|
|
81
|
+
@for $i from 1 through list.length($series) {
|
|
82
|
+
--jc-series-#{$i}: #{list.nth($series, $i)};
|
|
83
|
+
}
|
|
84
|
+
--jc-series-count: #{list.length($series)};
|
|
18
85
|
}
|
|
19
86
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.jc-
|
|
23
|
-
.jc-
|
|
24
|
-
.jc-
|
|
25
|
-
.jc-
|
|
26
|
-
|
|
27
|
-
.jc-
|
|
28
|
-
|
|
29
|
-
.jc-
|
|
30
|
-
|
|
31
|
-
.jc-
|
|
32
|
-
|
|
33
|
-
.jc-
|
|
34
|
-
.jc-
|
|
35
|
-
.jc-
|
|
36
|
-
.jc-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.jc-lookup
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.jc-
|
|
43
|
-
|
|
44
|
-
.jc-
|
|
45
|
-
.jc-
|
|
46
|
-
.jc-
|
|
47
|
-
.jc-
|
|
48
|
-
.jc-
|
|
49
|
-
.jc-
|
|
50
|
-
.jc-
|
|
51
|
-
.jc-
|
|
52
|
-
.jc-
|
|
53
|
-
.jc-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
.jc-
|
|
57
|
-
.jc-
|
|
58
|
-
.jc-
|
|
59
|
-
.jc-
|
|
60
|
-
.jc-
|
|
61
|
-
|
|
62
|
-
.jc-
|
|
63
|
-
.jc-
|
|
64
|
-
.jc-
|
|
65
|
-
.jc-
|
|
66
|
-
|
|
67
|
-
.jc-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.jc-
|
|
72
|
-
|
|
73
|
-
.jc-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.jc-
|
|
78
|
-
.jc-
|
|
79
|
-
|
|
80
|
-
.jc-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.jc-
|
|
85
|
-
|
|
86
|
-
.jc-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.jc-
|
|
91
|
-
.jc-
|
|
92
|
-
.jc-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.jc-
|
|
96
|
-
.jc-
|
|
97
|
-
.jc-
|
|
98
|
-
.jc-
|
|
99
|
-
.jc-wf-
|
|
100
|
-
.jc-wf-
|
|
101
|
-
.jc-wf-
|
|
102
|
-
.jc-wf-
|
|
103
|
-
.jc-wf-
|
|
104
|
-
.jc-
|
|
105
|
-
.jc-wf-
|
|
106
|
-
.jc-wf-
|
|
107
|
-
.jc-wf-
|
|
108
|
-
.jc-wf-
|
|
109
|
-
.jc-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.jc-
|
|
113
|
-
.jc-
|
|
114
|
-
.jc-
|
|
115
|
-
.jc-span-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.jc-
|
|
121
|
-
.jc-
|
|
122
|
-
.jc-
|
|
123
|
-
.jc-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.jc-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.jc-
|
|
132
|
-
|
|
87
|
+
/// The layout of the jc-* classes the pages use. No colours: they read the --jc-* properties set by colors().
|
|
88
|
+
@mixin _layout() {
|
|
89
|
+
.jc-muted { color: var(--jc-muted); }
|
|
90
|
+
.jc-small { font-size: 12px; }
|
|
91
|
+
.jc-mono { font-family: 'JetBrains Mono', Consolas, monospace; font-size: 13px; }
|
|
92
|
+
.jc-nowrap { white-space: nowrap; }
|
|
93
|
+
.jc-bad { color: var(--jc-bad); }
|
|
94
|
+
.jc-error { color: var(--jc-bad); }
|
|
95
|
+
// Under a paged list: how many rows are shown, and the button for the next page.
|
|
96
|
+
.jc-more { display: flex; align-items: center; gap: 12px; padding: 8px 0; font-size: 13px; }
|
|
97
|
+
.jc-notice { padding: 8px 12px; border-left: 3px solid var(--jc-warn); background: var(--jc-warn-bg); font-size: 13px; }
|
|
98
|
+
.jc-spacer { flex: 1; }
|
|
99
|
+
|
|
100
|
+
.jc-context { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 4px 0 12px; border-bottom: 1px solid var(--jc-border); margin-bottom: 12px; }
|
|
101
|
+
.jc-crumb { color: var(--jc-muted); }
|
|
102
|
+
.jc-crumb-sep { color: var(--jc-muted); }
|
|
103
|
+
.jc-field-sm { width: 150px; }
|
|
104
|
+
.jc-field-md { width: 180px; }
|
|
105
|
+
.jc-field-wide { min-width: 320px; flex: 1; }
|
|
106
|
+
.jc-lookup { display: flex; align-items: center; gap: 4px; border: 1px solid var(--jc-border); border-radius: 6px; padding: 2px 8px; background: var(--jc-surface);
|
|
107
|
+
input { border: 0; outline: 0; background: transparent; color: inherit; width: 240px; font: inherit; padding: 6px 0; }
|
|
108
|
+
mat-icon { color: var(--jc-muted); font-size: 18px; height: 18px; width: 18px; } }
|
|
109
|
+
.jc-lookup-message { text-align: right; margin: -8px 0 8px; }
|
|
110
|
+
|
|
111
|
+
.jc-card { background: var(--jc-surface); border: 1px solid var(--jc-border); border-radius: 8px; padding: 12px 16px; margin-bottom: 12px; }
|
|
112
|
+
.jc-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
113
|
+
.jc-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 12px; }
|
|
114
|
+
.jc-grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; }
|
|
115
|
+
.jc-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin-bottom: 12px; }
|
|
116
|
+
.jc-tile { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; strong { font-size: 24px; } }
|
|
117
|
+
.jc-list-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--jc-border); }
|
|
118
|
+
.jc-list-row:last-child { border-bottom: 0; }
|
|
119
|
+
.jc-summary { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; margin-bottom: 8px; }
|
|
120
|
+
.jc-actions { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
|
|
121
|
+
// The filters, the context bar and every other field the pages size label each field. An app theme denser than -1 hides a fill field's label (and
|
|
122
|
+
// these fields have no placeholder to fall back on), so keep them at -1 whatever the app's density.
|
|
123
|
+
.jc-context, .jc-filter-form, mat-form-field[class*='jc-field-'] { @include mat.form-field-density(-1); }
|
|
124
|
+
.jc-filter-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 12px; }
|
|
125
|
+
.jc-filters { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
|
|
126
|
+
.jc-chip { display: inline-flex; gap: 6px; align-items: center; border: 1px solid var(--jc-border); border-radius: 14px; padding: 2px 10px; font-size: 13px; color: var(--jc-text); }
|
|
127
|
+
.jc-chip.jc-selected { background: var(--jc-select); border-color: var(--jc-accent); }
|
|
128
|
+
|
|
129
|
+
.jc-badge { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 12px; font-weight: 500; white-space: nowrap; background: var(--jc-none-bg); color: var(--jc-none); }
|
|
130
|
+
.jc-health-healthy { background: var(--jc-ok-bg); color: var(--jc-ok); }
|
|
131
|
+
.jc-health-degraded { background: var(--jc-warn-bg); color: var(--jc-warn); }
|
|
132
|
+
.jc-health-unhealthy { background: var(--jc-bad-bg); color: var(--jc-bad); }
|
|
133
|
+
.jc-health-critical { background: var(--jc-crit-bg); color: var(--jc-crit); }
|
|
134
|
+
.jc-health-offline { background: var(--jc-off-bg); color: var(--jc-off); }
|
|
135
|
+
.jc-health-unknown, .jc-health-none { background: var(--jc-none-bg); color: var(--jc-none); }
|
|
136
|
+
.jc-stale { font-weight: 400; }
|
|
137
|
+
.jc-observed { background: var(--jc-select); color: var(--jc-accent); }
|
|
138
|
+
.jc-inferred { background: transparent; color: var(--jc-muted); border: 1px dashed var(--jc-muted); }
|
|
139
|
+
|
|
140
|
+
.jc-table { width: 100%; border-collapse: collapse; background: var(--jc-surface); font-size: 13px; margin-bottom: 8px;
|
|
141
|
+
th { text-align: left; font-weight: 500; color: var(--jc-muted); padding: 6px 8px; border-bottom: 1px solid var(--jc-border); white-space: nowrap; }
|
|
142
|
+
td { padding: 6px 8px; border-bottom: 1px solid var(--jc-border); vertical-align: top; }
|
|
143
|
+
tr.jc-selected td { background: var(--jc-select); } }
|
|
144
|
+
.jc-bar { display: inline-block; height: 6px; border-radius: 3px; background: var(--jc-accent); opacity: .5; margin-right: 6px; max-width: 80px; vertical-align: middle; }
|
|
145
|
+
.jc-envs { display: flex; flex-wrap: wrap; gap: 4px 12px; }
|
|
146
|
+
.jc-body { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: 'JetBrains Mono', Consolas, monospace; font-size: 12px; max-height: 8em; overflow: auto; }
|
|
147
|
+
.jc-kv { width: 100%; border-collapse: collapse; font-size: 12px;
|
|
148
|
+
th { text-align: left; color: var(--jc-muted); font-weight: 400; padding: 3px 8px 3px 0; vertical-align: top; width: 38%; word-break: break-all; }
|
|
149
|
+
td { padding: 3px 0; word-break: break-word; font-family: 'JetBrains Mono', Consolas, monospace; } }
|
|
150
|
+
.jc-facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; margin: 8px 0; dt { color: var(--jc-muted); } dd { margin: 0; } }
|
|
151
|
+
.jc-reasons { margin: 6px 0; padding-left: 18px; font-size: 13px; }
|
|
152
|
+
|
|
153
|
+
.jc-health-strip { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 8px; }
|
|
154
|
+
.jc-health-card { min-width: 240px; flex: 1; max-width: 420px; }
|
|
155
|
+
.jc-health-card.jc-selected { border-color: var(--jc-accent); }
|
|
156
|
+
|
|
157
|
+
.jc-chart-title { font-size: 13px; color: var(--jc-muted); margin: 4px 0; }
|
|
158
|
+
.jc-chart { height: 180px; width: 100%; background: var(--jc-surface); border: 1px solid var(--jc-border); border-radius: 6px; }
|
|
159
|
+
.jc-chart-empty { display: flex; align-items: center; justify-content: center; color: var(--jc-muted); font-size: 13px; }
|
|
160
|
+
|
|
161
|
+
// ─── Waterfall ───
|
|
162
|
+
.jc-legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 12px; margin-bottom: 6px; i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 4px; } }
|
|
163
|
+
.jc-trace { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
|
164
|
+
.jc-trace.jc-with-detail { grid-template-columns: minmax(0, 1.6fr) minmax(340px, 1fr); }
|
|
165
|
+
.jc-waterfall { background: var(--jc-surface); border: 1px solid var(--jc-border); border-radius: 8px; font-size: 12px; overflow: hidden; }
|
|
166
|
+
.jc-wf-head, .jc-wf-row { display: grid; grid-template-columns: minmax(260px, 40%) 1fr; align-items: center; }
|
|
167
|
+
.jc-wf-head { color: var(--jc-muted); border-bottom: 1px solid var(--jc-border); padding: 4px 8px; }
|
|
168
|
+
.jc-wf-scale { position: relative; height: 16px; span { position: absolute; transform: translateX(-2px); } }
|
|
169
|
+
.jc-wf-row { padding: 2px 8px; cursor: pointer; border-bottom: 1px solid transparent; }
|
|
170
|
+
.jc-wf-row:hover { background: var(--jc-select); }
|
|
171
|
+
.jc-wf-row.jc-selected { background: var(--jc-select); border-bottom-color: var(--jc-accent); }
|
|
172
|
+
.jc-wf-row.jc-wf-error .jc-wf-name { color: var(--jc-bad); }
|
|
173
|
+
.jc-wf-name { display: flex; align-items: center; gap: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
174
|
+
.jc-wf-service { color: var(--jc-muted); }
|
|
175
|
+
.jc-wf-toggle { width: 16px; border: 0; background: transparent; color: inherit; cursor: pointer; padding: 0; font-size: 11px; }
|
|
176
|
+
.jc-wf-icon { font-size: 14px; height: 14px; width: 14px; }
|
|
177
|
+
.jc-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; }
|
|
178
|
+
.jc-wf-track { position: relative; height: 18px; }
|
|
179
|
+
.jc-wf-bar { position: absolute; top: 4px; height: 10px; border-radius: 2px; opacity: .85; }
|
|
180
|
+
.jc-wf-error .jc-wf-bar { outline: 2px solid var(--jc-bad); }
|
|
181
|
+
.jc-wf-time { position: absolute; top: 2px; font-size: 10px; color: var(--jc-muted); transform: translateY(12px); }
|
|
182
|
+
.jc-span-detail { position: sticky; top: 8px; max-height: calc(100vh - 140px); overflow: auto; }
|
|
183
|
+
// The trace page fills the window and scrolls inside its panes (waterfall, span), never the whole page as well.
|
|
184
|
+
mat-sidenav-content > .jc-fill-page { box-sizing: border-box; height: 100%; display: flex; flex-direction: column; overflow: hidden; }
|
|
185
|
+
.jc-fill-page > .jc-trace { flex: 1; min-height: 0; grid-template-rows: minmax(0, 1fr); }
|
|
186
|
+
.jc-fill-page > .jc-trace > .jc-waterfall, .jc-fill-page > .jc-trace > .jc-span-detail { overflow: auto; max-height: none; position: static; }
|
|
187
|
+
.jc-fill-page .jc-wf-head { position: sticky; top: 0; z-index: 1; background: var(--jc-surface); }
|
|
188
|
+
.jc-span-facts { display: flex; gap: 12px; align-items: center; margin-bottom: 6px; }
|
|
189
|
+
.jc-event { border-left: 3px solid var(--jc-border); padding-left: 8px; margin: 6px 0; }
|
|
190
|
+
.jc-event.jc-wf-error { border-left-color: var(--jc-bad); }
|
|
191
|
+
|
|
192
|
+
// ─── Topology ───
|
|
193
|
+
.jc-topology { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 12px; }
|
|
194
|
+
.jc-graph { height: calc(100vh - 220px); min-height: 420px; background: var(--jc-surface); border: 1px solid var(--jc-border); border-radius: 8px; }
|
|
195
|
+
.jc-graph-legend { display: flex; gap: 16px; align-items: center; font-size: 12px; color: var(--jc-muted); padding: 4px; }
|
|
196
|
+
.jc-line { display: inline-block; width: 24px; border-top: 2px solid var(--jc-muted); vertical-align: middle; }
|
|
197
|
+
.jc-line.jc-dashed { border-top-style: dashed; }
|
|
198
|
+
.jc-side { align-self: start; }
|
|
199
|
+
|
|
200
|
+
@media (max-width: 900px) {
|
|
201
|
+
.jc-trace.jc-with-detail, .jc-topology { grid-template-columns: 1fr; }
|
|
202
|
+
// Stacked panes: the page scrolls as a whole again.
|
|
203
|
+
mat-sidenav-content > .jc-fill-page { height: auto; display: block; overflow: visible; }
|
|
204
|
+
.jc-fill-page > .jc-trace > .jc-waterfall, .jc-fill-page > .jc-trace > .jc-span-detail { overflow: visible; }
|
|
205
|
+
.jc-field-wide { min-width: 0; }
|
|
206
|
+
}
|
|
133
207
|
}
|
|
208
|
+
|
|
209
|
+
// ─── Emitted once, on @use: the layout, and the default colours ───
|
|
210
|
+
|
|
211
|
+
@include _layout();
|
|
212
|
+
|
|
213
|
+
// The Juice Console's own theme: teal, amber and red.
|
|
214
|
+
$_accent: mat.m2-define-palette(mat.$m2-amber-palette);
|
|
215
|
+
$_warn: mat.m2-define-palette(mat.$m2-red-palette);
|
|
216
|
+
|
|
217
|
+
:where(:root) { @include colors(mat.m2-define-light-theme((color: (
|
|
218
|
+
primary: mat.m2-define-palette(mat.$m2-teal-palette, 700), accent: $_accent, warn: $_warn)))); }
|
|
219
|
+
:where(.dark) { @include colors(mat.m2-define-dark-theme((color: (
|
|
220
|
+
primary: mat.m2-define-palette(mat.$m2-teal-palette, 300), accent: $_accent, warn: $_warn)))); }
|
|
@@ -9,17 +9,29 @@ export interface ObservabilityOptions {
|
|
|
9
9
|
apiBase: string;
|
|
10
10
|
/** Where the host mounts {@link OBSERVABILITY_ROUTES}: `/` for the whole app, or a path such as `/observability`. */
|
|
11
11
|
basePath: string;
|
|
12
|
+
/**
|
|
13
|
+
* A link to the audit records of a trace, shown on the trace page: for the Juice Audit pages mounted at `/audit`,
|
|
14
|
+
* `id => ['/audit/traces', id]`. A string is an external URL. Without it, no link.
|
|
15
|
+
*/
|
|
16
|
+
auditLink?: ((traceId: string) => unknown[] | string | null) | null;
|
|
12
17
|
}
|
|
13
18
|
export declare const OBSERVABILITY_OPTIONS: InjectionToken<ObservabilityOptions>;
|
|
14
|
-
/** Configures the pages: where the API is
|
|
19
|
+
/** Configures the pages: where the API is, where the host mounted the routes, and where a trace's audit records open. */
|
|
15
20
|
export declare function provideJuiceObservability(options?: Partial<ObservabilityOptions>): EnvironmentProviders;
|
|
16
21
|
/** `/observability/` → `/observability`; `` and `/` → `/`. */
|
|
17
22
|
export declare function normalizeBasePath(basePath: string): string;
|
|
18
23
|
/** Router links to the pages, under the host's mount path. */
|
|
19
24
|
export declare class ObservabilityLinks {
|
|
25
|
+
private readonly options;
|
|
20
26
|
readonly base: string;
|
|
21
27
|
/** `to('services', id)` → `['/observability', 'services', id]`. */
|
|
22
28
|
to(...segments: unknown[]): unknown[];
|
|
29
|
+
/** The host's link to the audit records of a trace, if it gave one. */
|
|
30
|
+
audit(traceId: string | null | undefined): {
|
|
31
|
+
route: unknown[];
|
|
32
|
+
} | {
|
|
33
|
+
href: string;
|
|
34
|
+
} | null;
|
|
23
35
|
static ɵfac: i0.ɵɵFactoryDeclaration<ObservabilityLinks, never>;
|
|
24
36
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
25
37
|
}
|
|
@@ -278,6 +290,7 @@ export interface Series {
|
|
|
278
290
|
export interface ServiceStatsPoint {
|
|
279
291
|
at: string;
|
|
280
292
|
requestsPerSecond: number;
|
|
293
|
+
messagesPerSecond: number;
|
|
281
294
|
errorRate: number;
|
|
282
295
|
p95Ms: number | null;
|
|
283
296
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"juice-js-observability-alert.page-D2OL0zTT.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/alert.page.ts"],"sourcesContent":["import { LinkPipe } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';\nimport { rxResource } from '@angular/core/rxjs-interop';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { RouterLink } from '@angular/router';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { AgoPipe, ReasonPipe } from '../shared/format';\nimport { ContextBar, HealthBadge, KeyValueTable } from '../shared/ui';\nimport { problemText } from '../shared/telemetry';\n\n/**\n * One alert (Console requirements §14): evidence, lifecycle history, the subject's health and reasons, the way into\n * the investigation, and acknowledgement for operators.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, DatePipe, FormsModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatProgressBarModule,\n ContextBar, HealthBadge, KeyValueTable, AgoPipe, ReasonPipe],\n template: `\n <jc-context-bar>\n <a [routerLink]=\"['alerts'] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-crumb\">Alerts</a><span class=\"jc-crumb-sep\">›</span>\n <h1>{{ detail.value()?.alert?.rule ?? '…' }}</h1>\n </jc-context-bar>\n @if (detail.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (detail.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n @if (detail.value(); as d) {\n <div class=\"jc-summary\">\n <span class=\"jc-badge\" [class.jc-health-unhealthy]=\"d.alert.severity === 'Critical'\" [class.jc-health-degraded]=\"d.alert.severity === 'Warning'\">{{ d.alert.severity }}</span>\n <strong>{{ d.alert.state }}</strong>\n <span>{{ d.alert.subjectKey }}</span>\n @if (d.alert.noData) { <span class=\"jc-muted\">no data at the last evaluation: state kept</span> }\n @if (d.alert.subjectType === 'service' && d.alert.subjectId) {\n <a mat-flat-button [routerLink]=\"['services', d.alert.subjectId] | jcLink\" [queryParams]=\"investigate(d.alert.environment)\">Investigate</a>\n <a mat-stroked-button [routerLink]=\"['services', d.alert.subjectId] | jcLink\" [queryParams]=\"investigate(d.alert.environment, { tab: 'traces', status: 'error' })\">Error traces</a>\n }\n @for (l of d.links; track l.url) { <a mat-button [href]=\"l.url\" target=\"_blank\" rel=\"noopener\">{{ l.title }} ↗</a> }\n </div>\n <div class=\"jc-grid-2\">\n <section class=\"jc-card\"><h2>Evidence</h2><jc-kv [values]=\"d.evidence\" /></section>\n <section class=\"jc-card\">\n <h2>Subject health</h2>\n @for (h of d.subjectHealth; track h.subjectId + h.environment) {\n <p><jc-health [state]=\"h.state\" [stale]=\"h.stale\" /> {{ h.environment }}</p>\n <ul class=\"jc-reasons\">@for (r of h.reasons; track r) { <li>{{ r | reason }}</li> }</ul>\n } @empty { <p class=\"jc-muted\">No health for this subject.</p> }\n </section>\n </div>\n <section class=\"jc-card\">\n <h2>History</h2>\n <table class=\"jc-table\"><tbody>\n @for (t of d.history; track t.at + t.to) {\n <tr><td>{{ t.at | date: 'MMM d, HH:mm:ss' }}</td><td>{{ t.from ?? '—' }} → <strong>{{ t.to }}</strong></td><td>{{ t.reason | reason }}</td><td>{{ t.value ?? '—' }}</td></tr>\n }\n </tbody></table>\n </section>\n <section class=\"jc-card\">\n <h2>Acknowledgement</h2>\n @if (d.alert.acknowledgedAt) {\n <p>By <strong>{{ d.alert.acknowledgedBy }}</strong> {{ d.alert.acknowledgedAt | ago }}@if (d.acknowledgeComment) {: “{{ d.acknowledgeComment }}”}</p>\n } @else if (d.alert.state !== 'Resolved') {\n <form (ngSubmit)=\"acknowledge()\" class=\"jc-filter-form\">\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\"><mat-label>Comment</mat-label><input matInput name=\"comment\" [(ngModel)]=\"comment\" maxlength=\"1000\"></mat-form-field>\n <button mat-flat-button type=\"submit\" [disabled]=\"sending()\">Acknowledge</button>\n </form>\n @if (ackError()) { <p class=\"jc-error\">{{ ackError() }}</p> }\n } @else { <p class=\"jc-muted\">Resolved without acknowledgement.</p> }\n </section>\n }`,\n})\nexport class AlertPage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n readonly id = input.required<string>();\n protected comment = '';\n protected readonly sending = signal(false);\n protected readonly ackError = signal('');\n\n protected readonly detail = rxResource({ params: () => this.id(), stream: ({ params }) => this.api.alert(params) });\n protected readonly error = computed(() => problemText(this.detail.error()));\n\n /** The service page over the alert's environment, from shortly before the breach. */\n investigate(environment: string | null, extra: Record<string, string> = {}) {\n const alert = this.detail.value()?.alert;\n const start = alert?.breachStartedAt ? new Date(alert.breachStartedAt).getTime() - 15 * 60_000 : null;\n const end = alert?.resolvedAt ? new Date(alert.resolvedAt).getTime() + 5 * 60_000 : Date.now();\n const window = start && end - start <= 24 * 3_600_000\n ? { from: new Date(start).toISOString(), to: new Date(end).toISOString(), range: null } : {};\n return this.ctx.keep({ env: environment, ...window, ...extra });\n }\n\n acknowledge() {\n this.sending.set(true);\n this.ackError.set('');\n this.api.acknowledge(this.id(), this.comment).subscribe({\n next: () => { this.sending.set(false); this.detail.reload(); },\n error: e => {\n this.sending.set(false);\n this.ackError.set((e as { status?: number }).status === 403 ? 'Acknowledging needs the operator or admin role.' : problemText(e));\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"juice-js-observability-logs.page-BWFLleyX.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/logs.page.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { rxResource } from '@angular/core/rxjs-interop';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { Router } from '@angular/router';\nimport { map } from 'rxjs';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { ContextBar } from '../shared/ui';\nimport { LogTable } from '../shared/telemetry';\n\n/** Log search (Console requirements §7): by service, severity, text, trace or span; each record links to its trace. */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FormsModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatSelectModule, ContextBar, LogTable],\n template: `\n <jc-context-bar><h1>Logs</h1></jc-context-bar>\n <form class=\"jc-filter-form\" (ngSubmit)=\"apply(f.value)\" #f=\"ngForm\">\n <mat-form-field subscriptSizing=\"dynamic\">\n <mat-label>Service</mat-label>\n <mat-select name=\"service\" [ngModel]=\"service() ?? ''\">\n <mat-option value=\"\">Any</mat-option>\n @for (s of services.value() ?? []; track s.id) { <mat-option [value]=\"s.id\">{{ s.key }}</mat-option> }\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\">\n <mat-label>Severity at least</mat-label>\n <mat-select name=\"severity\" [ngModel]=\"severity() ?? ''\">\n <mat-option value=\"\">Any</mat-option><mat-option value=\"5\">Debug</mat-option><mat-option value=\"9\">Info</mat-option>\n <mat-option value=\"13\">Warn</mat-option><mat-option value=\"17\">Error</mat-option><mat-option value=\"21\">Fatal</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\"><mat-label>Message contains</mat-label><input matInput name=\"q\" [ngModel]=\"q() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Trace ID</mat-label><input matInput name=\"trace\" [ngModel]=\"trace() ?? ''\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Span ID</mat-label><input matInput name=\"spanId\" [ngModel]=\"spanId() ?? ''\"></mat-form-field>\n <button mat-flat-button type=\"submit\">Search</button>\n <button mat-button type=\"button\" (click)=\"clear()\">Clear</button>\n </form>\n <jc-log-table [query]=\"query()\" />`,\n})\nexport class LogsPage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n\n readonly service = input<string>();\n readonly severity = input<string>();\n readonly q = input<string>();\n readonly trace = input<string>();\n readonly spanId = input<string>();\n readonly instance = input<string>();\n readonly resource = input<string>();\n\n protected readonly services = rxResource({ stream: () => this.api.resources('service').pipe(map(p => p.items)) });\n protected readonly query = computed(() => ({\n serviceId: this.service(), minSeverity: this.severity(), q: this.q(), traceId: this.trace(), spanId: this.spanId(),\n instanceId: this.instance(), resourceId: this.resource(),\n }));\n\n apply(value: Record<string, unknown>) {\n const queryParams: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(value)) queryParams[key] = v === '' || v === undefined ? null : v;\n void this.router.navigate([], { queryParams, queryParamsHandling: 'merge' });\n }\n\n clear() { void this.router.navigate([], { queryParams: this.ctx.keep() }); }\n}\n"],"mappings":"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"}
|