@juice-js/observability 0.0.0-stage → 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.
Files changed (34) hide show
  1. package/README.md +63 -2
  2. package/fesm2022/juice-js-observability-alert.page-TKwqJT2d.mjs +404 -0
  3. package/fesm2022/juice-js-observability-alert.page-TKwqJT2d.mjs.map +1 -0
  4. package/fesm2022/juice-js-observability-alerts.page-B-2uS9wr.mjs +211 -0
  5. package/fesm2022/juice-js-observability-alerts.page-B-2uS9wr.mjs.map +1 -0
  6. package/fesm2022/juice-js-observability-logs.page-CZNbTl2Q.mjs +413 -0
  7. package/fesm2022/juice-js-observability-logs.page-CZNbTl2Q.mjs.map +1 -0
  8. package/fesm2022/juice-js-observability-metrics.page-DoVED9MT.mjs +267 -0
  9. package/fesm2022/juice-js-observability-metrics.page-DoVED9MT.mjs.map +1 -0
  10. package/fesm2022/juice-js-observability-overview.page-Cod7-cIy.mjs +249 -0
  11. package/fesm2022/juice-js-observability-overview.page-Cod7-cIy.mjs.map +1 -0
  12. package/fesm2022/juice-js-observability-resource.page-C8RhUipF.mjs +274 -0
  13. package/fesm2022/juice-js-observability-resource.page-C8RhUipF.mjs.map +1 -0
  14. package/fesm2022/juice-js-observability-resources.page-KadGxEAh.mjs +209 -0
  15. package/fesm2022/juice-js-observability-resources.page-KadGxEAh.mjs.map +1 -0
  16. package/fesm2022/juice-js-observability-rules.page-DKWvNpu9.mjs +175 -0
  17. package/fesm2022/juice-js-observability-rules.page-DKWvNpu9.mjs.map +1 -0
  18. package/fesm2022/juice-js-observability-service.page-_yK0kVYS.mjs +726 -0
  19. package/fesm2022/juice-js-observability-service.page-_yK0kVYS.mjs.map +1 -0
  20. package/fesm2022/juice-js-observability-services.page-BtG8zyiI.mjs +251 -0
  21. package/fesm2022/juice-js-observability-services.page-BtG8zyiI.mjs.map +1 -0
  22. package/fesm2022/juice-js-observability-telemetry-B5jMJYfn.mjs +1801 -0
  23. package/fesm2022/juice-js-observability-telemetry-B5jMJYfn.mjs.map +1 -0
  24. package/fesm2022/juice-js-observability-topology.page-B7aXWOYV.mjs +486 -0
  25. package/fesm2022/juice-js-observability-topology.page-B7aXWOYV.mjs.map +1 -0
  26. package/fesm2022/juice-js-observability-trace.page-CkLNNnKf.mjs +528 -0
  27. package/fesm2022/juice-js-observability-trace.page-CkLNNnKf.mjs.map +1 -0
  28. package/fesm2022/juice-js-observability-traces.page-DgvNixIc.mjs +520 -0
  29. package/fesm2022/juice-js-observability-traces.page-DgvNixIc.mjs.map +1 -0
  30. package/fesm2022/juice-js-observability.mjs +405 -0
  31. package/fesm2022/juice-js-observability.mjs.map +1 -0
  32. package/package.json +48 -4
  33. package/styles/_observability.scss +220 -0
  34. package/types/juice-js-observability.d.ts +400 -0
@@ -0,0 +1,220 @@
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);
26
+ }
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)};
85
+ }
86
+
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
+ }
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)))); }
@@ -0,0 +1,400 @@
1
+ import * as i0 from "@angular/core";
2
+ import { EnvironmentProviders, InjectionToken, PipeTransform } from "@angular/core";
3
+ import { Observable } from "rxjs";
4
+ import { Params, Routes } from "@angular/router";
5
+ import { Menus } from "@juice-js/core";
6
+ /** How the host app mounts Juice Observability. */
7
+ export interface ObservabilityOptions {
8
+ /** The Juice API origin; empty when the API serves the app (same origin). */
9
+ apiBase: string;
10
+ /** Where the host mounts {@link OBSERVABILITY_ROUTES}: `/` for the whole app, or a path such as `/observability`. */
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;
17
+ }
18
+ export declare const OBSERVABILITY_OPTIONS: InjectionToken<ObservabilityOptions>;
19
+ /** Configures the pages: where the API is, where the host mounted the routes, and where a trace's audit records open. */
20
+ export declare function provideJuiceObservability(options?: Partial<ObservabilityOptions>): EnvironmentProviders;
21
+ /** `/observability/` → `/observability`; `` and `/` → `/`. */
22
+ export declare function normalizeBasePath(basePath: string): string;
23
+ /** Router links to the pages, under the host's mount path. */
24
+ export declare class ObservabilityLinks {
25
+ private readonly options;
26
+ readonly base: string;
27
+ /** `to('services', id)` → `['/observability', 'services', id]`. */
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;
35
+ static ɵfac: i0.ɵɵFactoryDeclaration<ObservabilityLinks, never>;
36
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
37
+ }
38
+ /** `['services', id] | jcLink`: the same as {@link ObservabilityLinks.to} in a template. */
39
+ export declare class LinkPipe implements PipeTransform {
40
+ private readonly links;
41
+ transform(segments: unknown[]): unknown[];
42
+ static ɵfac: i0.ɵɵFactoryDeclaration<LinkPipe, never>;
43
+ static ɵpipe: i0.ɵɵPipeDeclaration<LinkPipe, "jcLink", true>;
44
+ }
45
+ export interface Page<T> {
46
+ items: T[];
47
+ nextCursor: string | null;
48
+ }
49
+ export interface TelemetryPage<T> {
50
+ items: T[];
51
+ truncated: boolean;
52
+ nextCursor?: string | null;
53
+ }
54
+ export interface Tenant {
55
+ id: string;
56
+ key: string;
57
+ displayName: string;
58
+ }
59
+ export interface DeploymentEnvironment {
60
+ id: string;
61
+ key: string;
62
+ type: string;
63
+ firstSeen: string;
64
+ lastSeen: string;
65
+ }
66
+ export interface Application {
67
+ id: string;
68
+ key: string;
69
+ displayName: string;
70
+ services: number;
71
+ firstSeen: string;
72
+ lastSeen: string;
73
+ }
74
+ export interface ResourceSummary {
75
+ id: string;
76
+ type: string;
77
+ key: string;
78
+ displayName: string;
79
+ firstSeen: string;
80
+ lastSeen: string;
81
+ }
82
+ export type HealthState = 'Healthy' | 'Degraded' | 'Unhealthy' | 'Critical' | 'Offline' | 'Unknown';
83
+ export interface Health {
84
+ subjectType: string;
85
+ subjectId: string;
86
+ subjectName: string;
87
+ environment: string | null;
88
+ state: HealthState;
89
+ reasons: string[];
90
+ policyVersion: string;
91
+ evaluatedAt: string;
92
+ evidenceFrom: string;
93
+ evidenceTo: string;
94
+ lastEvidenceAt: string | null;
95
+ since: string;
96
+ stale: boolean;
97
+ }
98
+ export interface Instance {
99
+ id: string;
100
+ environment: string;
101
+ instanceKey: string;
102
+ keySource: string;
103
+ serviceVersion: string | null;
104
+ state: string;
105
+ hostResourceId: string | null;
106
+ firstSeen: string;
107
+ lastSeen: string;
108
+ health: HealthState | null;
109
+ }
110
+ export interface IntegrationLink {
111
+ integration: string;
112
+ title: string;
113
+ url: string;
114
+ }
115
+ export interface ResourceDetail {
116
+ id: string;
117
+ type: string;
118
+ key: string;
119
+ displayName: string;
120
+ firstSeen: string;
121
+ lastSeen: string;
122
+ namespace: string | null;
123
+ name: string | null;
124
+ applicationId: string | null;
125
+ attributes: Record<string, string>;
126
+ instances: Instance[];
127
+ instancesTruncated: boolean;
128
+ health: Health[];
129
+ links: IntegrationLink[];
130
+ }
131
+ export interface TopologyNode {
132
+ id: string;
133
+ type: string;
134
+ displayName: string;
135
+ health: HealthState | null;
136
+ }
137
+ export interface TopologyEdge {
138
+ id: string;
139
+ source: string;
140
+ target: string;
141
+ relation: string;
142
+ confidence: string;
143
+ evidenceSource: string;
144
+ spanCount: number;
145
+ firstSeen: string;
146
+ lastSeen: string;
147
+ }
148
+ export interface Topology {
149
+ environment: string | null;
150
+ nodes: TopologyNode[];
151
+ edges: TopologyEdge[];
152
+ truncated: boolean;
153
+ }
154
+ export interface Alert {
155
+ id: string;
156
+ rule: string;
157
+ subjectKey: string;
158
+ subjectType: string;
159
+ subjectId: string | null;
160
+ environment: string | null;
161
+ severity: string;
162
+ state: string;
163
+ breachStartedAt: string | null;
164
+ firedAt: string | null;
165
+ resolvedAt: string | null;
166
+ lastEvaluatedAt: string;
167
+ lastValue: number | null;
168
+ noData: boolean;
169
+ acknowledgedAt: string | null;
170
+ acknowledgedBy: string | null;
171
+ }
172
+ export interface AlertTransition {
173
+ from: string | null;
174
+ to: string;
175
+ at: string;
176
+ value: number | null;
177
+ reason: string;
178
+ }
179
+ export interface AlertDetail {
180
+ alert: Alert;
181
+ evidence: Record<string, string>;
182
+ acknowledgeComment: string | null;
183
+ history: AlertTransition[];
184
+ subjectHealth: Health[];
185
+ links: IntegrationLink[];
186
+ }
187
+ export interface Rule {
188
+ key: string;
189
+ name: string;
190
+ template: string;
191
+ severity: string;
192
+ enabled: boolean;
193
+ definition: Record<string, unknown>;
194
+ loadedAt: string;
195
+ }
196
+ export interface TraceSummary {
197
+ traceId: string;
198
+ startedAt: string;
199
+ durationMs: number;
200
+ rootService: string;
201
+ rootOperation: string;
202
+ environment: string | null;
203
+ rootInstance: string | null;
204
+ rootHost: string | null;
205
+ spanCount: number;
206
+ errorCount: number;
207
+ services: string[];
208
+ }
209
+ export interface SpanEvent {
210
+ name: string;
211
+ at: string;
212
+ attributes: Record<string, string>;
213
+ }
214
+ export interface SpanTarget {
215
+ kind: 'service' | 'resource';
216
+ id: string;
217
+ name: string;
218
+ }
219
+ export interface Span {
220
+ spanId: string;
221
+ parentSpanId: string | null;
222
+ serviceId: string | null;
223
+ service: string;
224
+ environment: string | null;
225
+ instance: string | null;
226
+ host: string | null;
227
+ name: string;
228
+ kind: string;
229
+ startedAt: string;
230
+ durationMs: number;
231
+ status: string;
232
+ statusMessage: string | null;
233
+ scope: string | null;
234
+ scopeVersion: string | null;
235
+ attributes: Record<string, string>;
236
+ events: SpanEvent[];
237
+ links: {
238
+ traceId: string;
239
+ spanId: string;
240
+ }[];
241
+ target: SpanTarget | null;
242
+ }
243
+ export interface Trace {
244
+ traceId: string;
245
+ spans: Span[];
246
+ truncated: boolean;
247
+ }
248
+ export interface LogRecord {
249
+ timestamp: string;
250
+ severity: string;
251
+ severityNumber: number;
252
+ service: string;
253
+ environment: string | null;
254
+ instance: string | null;
255
+ body: string;
256
+ bodyTruncated: boolean;
257
+ traceId: string | null;
258
+ spanId: string | null;
259
+ attributes: Record<string, string>;
260
+ }
261
+ export interface Operation {
262
+ name: string;
263
+ kind: string;
264
+ count: number;
265
+ requestsPerSecond: number;
266
+ errorRate: number;
267
+ p50Ms: number;
268
+ p95Ms: number;
269
+ p99Ms: number;
270
+ lastSeen: string;
271
+ }
272
+ export interface MetricInfo {
273
+ name: string;
274
+ type: string;
275
+ unit: string;
276
+ description: string;
277
+ }
278
+ export interface Point {
279
+ at: string;
280
+ value: number | null;
281
+ }
282
+ export interface Series {
283
+ name: string;
284
+ type: string;
285
+ unit: string;
286
+ aggregation: string;
287
+ stepSeconds: number;
288
+ points: Point[];
289
+ }
290
+ export interface ServiceStatsPoint {
291
+ at: string;
292
+ requestsPerSecond: number;
293
+ messagesPerSecond: number;
294
+ errorRate: number;
295
+ p95Ms: number | null;
296
+ }
297
+ export interface ServiceStats {
298
+ stepSeconds: number;
299
+ points: ServiceStatsPoint[];
300
+ }
301
+ export interface DependencyEvidence {
302
+ dependencyId: string;
303
+ relation: string;
304
+ confidence: string;
305
+ count: number;
306
+ errorRate: number;
307
+ p95Ms: number | null;
308
+ lastSeen: string | null;
309
+ slowestTraces: string[];
310
+ errorTraces: string[];
311
+ }
312
+ export interface SearchHit {
313
+ kind: 'trace' | 'span' | 'service';
314
+ id: string;
315
+ title: string;
316
+ traceId: string | null;
317
+ }
318
+ export type Query = Record<string, string | number | boolean | string[] | null | undefined>;
319
+ /**
320
+ * The Console's only backend: the Juice API (Console requirements §3). One deployment serves one tenant, the one the
321
+ * signed-in user's token names.
322
+ */
323
+ export declare class ApiService {
324
+ private readonly http;
325
+ private readonly base;
326
+ /** The caller's tenant, fetched once. */
327
+ readonly tenant$: Observable<Tenant>;
328
+ private get;
329
+ /** Every page of a cursor-paged list (bounded: at most `maxPages` requests). */
330
+ private all;
331
+ environments(): Observable<Page<DeploymentEnvironment>>;
332
+ applications(): Observable<Page<Application>>;
333
+ resources(type?: string): Observable<Page<ResourceSummary>>;
334
+ resource(id: string): Observable<ResourceDetail>;
335
+ health(query: Query): Observable<Page<Health>>;
336
+ topology(environment?: string | null): Observable<Topology>;
337
+ rules(): Observable<Page<Rule>>;
338
+ /** One page of alerts, newest first; pass the previous page's `nextCursor` as `cursor` for the next. */
339
+ alerts(query: Query): Observable<Page<Alert>>;
340
+ /** Every alert matching the query (bounded, like the other complete lists). */
341
+ allAlerts(query: Query): Observable<Page<Alert>>;
342
+ alert(id: string): Observable<AlertDetail>;
343
+ acknowledge(id: string, comment: string): Observable<Alert>;
344
+ traces(query: Query): Observable<TelemetryPage<TraceSummary>>;
345
+ trace(traceId: string): Observable<Trace>;
346
+ logs(query: Query): Observable<TelemetryPage<LogRecord>>;
347
+ operations(query: Query): Observable<TelemetryPage<Operation>>;
348
+ metrics(query: Query): Observable<TelemetryPage<MetricInfo>>;
349
+ series(query: Query): Observable<Series>;
350
+ serviceStats(query: Query): Observable<ServiceStats>;
351
+ dependency(id: string, query: Query): Observable<DependencyEvidence>;
352
+ search(q: string): Observable<TelemetryPage<SearchHit>>;
353
+ static ɵfac: i0.ɵɵFactoryDeclaration<ApiService, never>;
354
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
355
+ }
356
+ export declare const RANGES: Record<string, number>;
357
+ /** Search ranges are at most 24 hours (the API's bound); series and operations take up to 7 days. */
358
+ export declare const MAX_SEARCH_MS: number;
359
+ /**
360
+ * The investigation context that follows the user between pages (Console requirements §17): environment and time
361
+ * range. It lives in the URL (`env`, and `range` or `from`/`to`), so every page is a deep link and the browser's back
362
+ * button restores it.
363
+ */
364
+ export declare class ContextService {
365
+ private readonly router;
366
+ private readonly params;
367
+ readonly environment: import("@angular/core").Signal<string>;
368
+ readonly range: import("@angular/core").Signal<string>;
369
+ readonly absolute: import("@angular/core").Signal<{
370
+ from: string;
371
+ to: string;
372
+ } | null>;
373
+ /** The window as ISO strings, evaluated now for a relative range. */
374
+ window(maxMs?: number): {
375
+ from: string;
376
+ to: string;
377
+ };
378
+ /** Query parameters for a link: the context plus the target page's own. */
379
+ keep(extra?: Params): Params;
380
+ /** Changes the context on the current page, keeping its other parameters. */
381
+ set(change: {
382
+ env?: string | null;
383
+ range?: string | null;
384
+ from?: string | null;
385
+ to?: string | null;
386
+ }): void;
387
+ /** A time window centred on a moment, for "around this span" links. */
388
+ around(at: string, durationMs: number, paddingMs?: number): Params;
389
+ private leaf;
390
+ static ɵfac: i0.ɵɵFactoryDeclaration<ContextService, never>;
391
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
392
+ }
393
+ /**
394
+ * The pages, investigation first (Console requirements §4): services, traces, topology, logs and metrics; alerts and
395
+ * rules after. Detail pages are deep links (§17) and carry the environment and time range in their query parameters.
396
+ * The host mounts them at its `basePath` (see {@link provideJuiceObservability}) and adds its own guards.
397
+ */
398
+ export declare const OBSERVABILITY_ROUTES: Routes;
399
+ /** The side menu entries (juice-layout), investigation pages first, linking under `basePath`. */
400
+ export declare function observabilityMenus(basePath?: string, order?: number): Menus;