@juice-js/observability 1.1.0-alpha.20 → 1.1.0-alpha.22
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 +1 -0
- package/fesm2022/{juice-js-observability-alert.page-nEy5Gc6j.mjs → juice-js-observability-alert.page-DOAX9WWa.mjs} +154 -2
- package/fesm2022/juice-js-observability-alert.page-DOAX9WWa.mjs.map +1 -0
- package/fesm2022/juice-js-observability-incident.page-CCHEWXou.mjs +400 -0
- package/fesm2022/juice-js-observability-incident.page-CCHEWXou.mjs.map +1 -0
- package/fesm2022/juice-js-observability-incidents.page-CpNqYp2W.mjs +438 -0
- package/fesm2022/juice-js-observability-incidents.page-CpNqYp2W.mjs.map +1 -0
- package/fesm2022/juice-js-observability-load-runs.page-C2rk2bz9.mjs +349 -0
- package/fesm2022/juice-js-observability-load-runs.page-C2rk2bz9.mjs.map +1 -0
- package/fesm2022/juice-js-observability-notifications.page-BDqb10C6.mjs +725 -0
- package/fesm2022/juice-js-observability-notifications.page-BDqb10C6.mjs.map +1 -0
- package/fesm2022/juice-js-observability-rules.page-Df2uDVwz.mjs +580 -0
- package/fesm2022/juice-js-observability-rules.page-Df2uDVwz.mjs.map +1 -0
- package/fesm2022/{juice-js-observability-service.page-rGAvUK_k.mjs → juice-js-observability-service.page-BFXUe4mX.mjs} +38 -4
- package/fesm2022/juice-js-observability-service.page-BFXUe4mX.mjs.map +1 -0
- package/fesm2022/juice-js-observability-silences.page-Cg53dJAy.mjs +523 -0
- package/fesm2022/juice-js-observability-silences.page-Cg53dJAy.mjs.map +1 -0
- package/fesm2022/juice-js-observability-timeline.page-CtU2EoDN.mjs +213 -0
- package/fesm2022/juice-js-observability-timeline.page-CtU2EoDN.mjs.map +1 -0
- package/fesm2022/juice-js-observability.mjs +141 -3
- package/fesm2022/juice-js-observability.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/_observability.scss +6 -0
- package/types/juice-js-observability.d.ts +242 -0
- package/fesm2022/juice-js-observability-alert.page-nEy5Gc6j.mjs.map +0 -1
- package/fesm2022/juice-js-observability-rules.page-Crb07uWm.mjs +0 -175
- package/fesm2022/juice-js-observability-rules.page-Crb07uWm.mjs.map +0 -1
- package/fesm2022/juice-js-observability-service.page-rGAvUK_k.mjs.map +0 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { ApiService, ContextService, LinkPipe } from "./juice-js-observability.mjs";
|
|
2
|
+
import { S as apiResource, i as ServiceStatsCharts, o as problemText, u as ContextBar } from "./juice-js-observability-telemetry-BftWSOaQ.mjs";
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
import { ChangeDetectionStrategy, Component, computed, inject, input } from "@angular/core";
|
|
5
|
+
import { RouterLink } from "@angular/router";
|
|
6
|
+
import { DatePipe } from "@angular/common";
|
|
7
|
+
import * as i1 from "@angular/material/progress-bar";
|
|
8
|
+
import { MatProgressBarModule } from "@angular/material/progress-bar";
|
|
9
|
+
var TimelinePage = class TimelinePage {
|
|
10
|
+
ctx = inject(ContextService);
|
|
11
|
+
api = inject(ApiService);
|
|
12
|
+
service = input(...ngDevMode ? [void 0, { debugName: "service" }] : /* istanbul ignore next */ []);
|
|
13
|
+
timeline = apiResource({
|
|
14
|
+
params: () => ({
|
|
15
|
+
service: this.service(),
|
|
16
|
+
env: this.ctx.environment(),
|
|
17
|
+
range: this.ctx.range(),
|
|
18
|
+
abs: this.ctx.absolute()
|
|
19
|
+
}),
|
|
20
|
+
stream: ({ params }) => this.api.timeline({
|
|
21
|
+
serviceId: params.service ?? null,
|
|
22
|
+
environment: params.env,
|
|
23
|
+
...this.ctx.window(6048e5)
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
error = computed(() => problemText(this.timeline.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
|
|
27
|
+
label(type) {
|
|
28
|
+
return {
|
|
29
|
+
change: "Change",
|
|
30
|
+
health: "Health",
|
|
31
|
+
alert: "Alert"
|
|
32
|
+
}[type] ?? type;
|
|
33
|
+
}
|
|
34
|
+
bad(state) {
|
|
35
|
+
return !!state && [
|
|
36
|
+
"Unhealthy",
|
|
37
|
+
"Critical",
|
|
38
|
+
"Offline",
|
|
39
|
+
"Firing"
|
|
40
|
+
].includes(state);
|
|
41
|
+
}
|
|
42
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
43
|
+
minVersion: "12.0.0",
|
|
44
|
+
version: "22.2.4",
|
|
45
|
+
ngImport: i0,
|
|
46
|
+
type: TimelinePage,
|
|
47
|
+
deps: [],
|
|
48
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
49
|
+
});
|
|
50
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
51
|
+
minVersion: "17.0.0",
|
|
52
|
+
version: "22.2.4",
|
|
53
|
+
type: TimelinePage,
|
|
54
|
+
isStandalone: true,
|
|
55
|
+
selector: "ng-component",
|
|
56
|
+
inputs: { service: {
|
|
57
|
+
classPropertyName: "service",
|
|
58
|
+
publicName: "service",
|
|
59
|
+
isSignal: true,
|
|
60
|
+
isRequired: false,
|
|
61
|
+
transformFunction: null
|
|
62
|
+
} },
|
|
63
|
+
ngImport: i0,
|
|
64
|
+
template: `
|
|
65
|
+
<jc-context-bar>
|
|
66
|
+
@if (service()) { <a [routerLink]="['services', service()] | jcLink" [queryParams]="ctx.keep()" class="jc-crumb">Service</a><span class="jc-crumb-sep">›</span> }
|
|
67
|
+
<h1>Timeline</h1>
|
|
68
|
+
</jc-context-bar>
|
|
69
|
+
@if (timeline.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
|
|
70
|
+
@if (timeline.error()) { <p class="jc-error">{{ error() }}</p> }
|
|
71
|
+
@if (service(); as id) { <jc-service-stats [query]="{ serviceId: id }" /> }
|
|
72
|
+
@if (timeline.value(); as t) {
|
|
73
|
+
@if (t.truncated) { <p class="jc-muted">Showing the newest entries only. Choose a shorter window.</p> }
|
|
74
|
+
<table class="jc-table" data-testid="timeline">
|
|
75
|
+
<thead><tr><th>Time</th><th>What</th><th>Event</th><th>Details</th><th>Environment</th></tr></thead>
|
|
76
|
+
<tbody>
|
|
77
|
+
@for (e of t.entries; track e.at + e.type + e.title) {
|
|
78
|
+
<tr [class.jc-timeline-change]="e.type === 'change'">
|
|
79
|
+
<td class="jc-nowrap">{{ e.at | date: 'MMM d, HH:mm:ss' }}</td>
|
|
80
|
+
<td>{{ label(e.type) }}</td>
|
|
81
|
+
<td>
|
|
82
|
+
@if (e.type === 'alert' && e.id) { <a [routerLink]="['alerts', e.id] | jcLink" [queryParams]="ctx.keep()">{{ e.title }}</a> }
|
|
83
|
+
@else { <span [class.jc-bad]="bad(e.state)">{{ e.title }}</span> }
|
|
84
|
+
</td>
|
|
85
|
+
<td class="jc-muted">{{ e.detail }}</td>
|
|
86
|
+
<td>{{ e.environment ?? 'all' }}</td>
|
|
87
|
+
</tr>
|
|
88
|
+
} @empty { <tr><td colspan="5" class="jc-muted">Nothing in this window.</td></tr> }
|
|
89
|
+
</tbody>
|
|
90
|
+
</table>
|
|
91
|
+
}`,
|
|
92
|
+
isInline: true,
|
|
93
|
+
dependencies: [
|
|
94
|
+
{
|
|
95
|
+
kind: "directive",
|
|
96
|
+
type: RouterLink,
|
|
97
|
+
selector: "[routerLink]",
|
|
98
|
+
inputs: [
|
|
99
|
+
"target",
|
|
100
|
+
"queryParams",
|
|
101
|
+
"fragment",
|
|
102
|
+
"queryParamsHandling",
|
|
103
|
+
"state",
|
|
104
|
+
"info",
|
|
105
|
+
"relativeTo",
|
|
106
|
+
"preserveFragment",
|
|
107
|
+
"skipLocationChange",
|
|
108
|
+
"replaceUrl",
|
|
109
|
+
"browserUrl",
|
|
110
|
+
"routerLink"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
kind: "ngmodule",
|
|
115
|
+
type: MatProgressBarModule
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
kind: "component",
|
|
119
|
+
type: i1.MatProgressBar,
|
|
120
|
+
selector: "mat-progress-bar",
|
|
121
|
+
inputs: [
|
|
122
|
+
"color",
|
|
123
|
+
"value",
|
|
124
|
+
"bufferValue",
|
|
125
|
+
"mode"
|
|
126
|
+
],
|
|
127
|
+
outputs: ["animationEnd"],
|
|
128
|
+
exportAs: ["matProgressBar"]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
kind: "component",
|
|
132
|
+
type: ContextBar,
|
|
133
|
+
selector: "jc-context-bar"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
kind: "component",
|
|
137
|
+
type: ServiceStatsCharts,
|
|
138
|
+
selector: "jc-service-stats",
|
|
139
|
+
inputs: ["query"]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
kind: "pipe",
|
|
143
|
+
type: LinkPipe,
|
|
144
|
+
name: "jcLink"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
kind: "pipe",
|
|
148
|
+
type: DatePipe,
|
|
149
|
+
name: "date"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
156
|
+
minVersion: "12.0.0",
|
|
157
|
+
version: "22.2.4",
|
|
158
|
+
ngImport: i0,
|
|
159
|
+
type: TimelinePage,
|
|
160
|
+
decorators: [{
|
|
161
|
+
type: Component,
|
|
162
|
+
args: [{
|
|
163
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
164
|
+
imports: [
|
|
165
|
+
LinkPipe,
|
|
166
|
+
RouterLink,
|
|
167
|
+
DatePipe,
|
|
168
|
+
MatProgressBarModule,
|
|
169
|
+
ContextBar,
|
|
170
|
+
ServiceStatsCharts
|
|
171
|
+
],
|
|
172
|
+
template: `
|
|
173
|
+
<jc-context-bar>
|
|
174
|
+
@if (service()) { <a [routerLink]="['services', service()] | jcLink" [queryParams]="ctx.keep()" class="jc-crumb">Service</a><span class="jc-crumb-sep">›</span> }
|
|
175
|
+
<h1>Timeline</h1>
|
|
176
|
+
</jc-context-bar>
|
|
177
|
+
@if (timeline.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
|
|
178
|
+
@if (timeline.error()) { <p class="jc-error">{{ error() }}</p> }
|
|
179
|
+
@if (service(); as id) { <jc-service-stats [query]="{ serviceId: id }" /> }
|
|
180
|
+
@if (timeline.value(); as t) {
|
|
181
|
+
@if (t.truncated) { <p class="jc-muted">Showing the newest entries only. Choose a shorter window.</p> }
|
|
182
|
+
<table class="jc-table" data-testid="timeline">
|
|
183
|
+
<thead><tr><th>Time</th><th>What</th><th>Event</th><th>Details</th><th>Environment</th></tr></thead>
|
|
184
|
+
<tbody>
|
|
185
|
+
@for (e of t.entries; track e.at + e.type + e.title) {
|
|
186
|
+
<tr [class.jc-timeline-change]="e.type === 'change'">
|
|
187
|
+
<td class="jc-nowrap">{{ e.at | date: 'MMM d, HH:mm:ss' }}</td>
|
|
188
|
+
<td>{{ label(e.type) }}</td>
|
|
189
|
+
<td>
|
|
190
|
+
@if (e.type === 'alert' && e.id) { <a [routerLink]="['alerts', e.id] | jcLink" [queryParams]="ctx.keep()">{{ e.title }}</a> }
|
|
191
|
+
@else { <span [class.jc-bad]="bad(e.state)">{{ e.title }}</span> }
|
|
192
|
+
</td>
|
|
193
|
+
<td class="jc-muted">{{ e.detail }}</td>
|
|
194
|
+
<td>{{ e.environment ?? 'all' }}</td>
|
|
195
|
+
</tr>
|
|
196
|
+
} @empty { <tr><td colspan="5" class="jc-muted">Nothing in this window.</td></tr> }
|
|
197
|
+
</tbody>
|
|
198
|
+
</table>
|
|
199
|
+
}`
|
|
200
|
+
}]
|
|
201
|
+
}],
|
|
202
|
+
propDecorators: { service: [{
|
|
203
|
+
type: i0.Input,
|
|
204
|
+
args: [{
|
|
205
|
+
isSignal: true,
|
|
206
|
+
alias: "service",
|
|
207
|
+
required: false
|
|
208
|
+
}]
|
|
209
|
+
}] }
|
|
210
|
+
});
|
|
211
|
+
export { TimelinePage };
|
|
212
|
+
|
|
213
|
+
//# sourceMappingURL=juice-js-observability-timeline.page-CtU2EoDN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juice-js-observability-timeline.page-CtU2EoDN.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/timeline.page.ts"],"sourcesContent":["import { LinkPipe } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\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 { apiResource } from '../shared/resource';\nimport { ServiceStatsCharts, problemText } from '../shared/telemetry';\nimport { ContextBar } from '../shared/ui';\n\n/**\n * Changes, health and alerts in one order, with a service's telemetry above them over the same window. Things sharing a timeline\n * aren't said to be related; an alert's page ranks what may be, with its reasons.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, DatePipe, MatProgressBarModule, ContextBar, ServiceStatsCharts],\n template: `\n <jc-context-bar>\n @if (service()) { <a [routerLink]=\"['services', service()] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-crumb\">Service</a><span class=\"jc-crumb-sep\">›</span> }\n <h1>Timeline</h1>\n </jc-context-bar>\n @if (timeline.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (timeline.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n @if (service(); as id) { <jc-service-stats [query]=\"{ serviceId: id }\" /> }\n @if (timeline.value(); as t) {\n @if (t.truncated) { <p class=\"jc-muted\">Showing the newest entries only. Choose a shorter window.</p> }\n <table class=\"jc-table\" data-testid=\"timeline\">\n <thead><tr><th>Time</th><th>What</th><th>Event</th><th>Details</th><th>Environment</th></tr></thead>\n <tbody>\n @for (e of t.entries; track e.at + e.type + e.title) {\n <tr [class.jc-timeline-change]=\"e.type === 'change'\">\n <td class=\"jc-nowrap\">{{ e.at | date: 'MMM d, HH:mm:ss' }}</td>\n <td>{{ label(e.type) }}</td>\n <td>\n @if (e.type === 'alert' && e.id) { <a [routerLink]=\"['alerts', e.id] | jcLink\" [queryParams]=\"ctx.keep()\">{{ e.title }}</a> }\n @else { <span [class.jc-bad]=\"bad(e.state)\">{{ e.title }}</span> }\n </td>\n <td class=\"jc-muted\">{{ e.detail }}</td>\n <td>{{ e.environment ?? 'all' }}</td>\n </tr>\n } @empty { <tr><td colspan=\"5\" class=\"jc-muted\">Nothing in this window.</td></tr> }\n </tbody>\n </table>\n }`,\n})\nexport class TimelinePage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n /** Narrows the timeline to one service. */\n readonly service = input<string>();\n\n protected readonly timeline = apiResource({\n params: () => ({ service: this.service(), env: this.ctx.environment(), range: this.ctx.range(), abs: this.ctx.absolute() }),\n stream: ({ params }) => this.api.timeline({ serviceId: params.service ?? null, environment: params.env, ...this.ctx.window(7 * 24 * 3_600_000) }),\n });\n protected readonly error = computed(() => problemText(this.timeline.error()));\n\n protected label(type: string): string { return ({ change: 'Change', health: 'Health', alert: 'Alert' } as Record<string, string>)[type] ?? type; }\n protected bad(state: string | null): boolean { return !!state && ['Unhealthy', 'Critical', 'Offline', 'Firing'].includes(state); }\n}\n"],"mappings":";;;;;;;;AA+CA,IAAa,eAAb,MAAa,aAAY;CACJ,MAAM,OAAO,cAAc;CAC7B,MAAM,OAAO,UAAU;CAE/B,UAAU,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEf,WAAW,YAAY;EACxC,eAAe;GAAE,SAAS,KAAK,QAAO;GAAI,KAAK,KAAK,IAAI,YAAW;GAAI,OAAO,KAAK,IAAI,MAAK;GAAI,KAAK,KAAK,IAAI,SAAQ;EAAE;EACxH,SAAS,EAAE,aAAa,KAAK,IAAI,SAAS;GAAE,WAAW,OAAO,WAAW;GAAM,aAAa,OAAO;GAAK,GAAG,KAAK,IAAI,OAAO,MAAkB;EAAC,CAAE;CACjJ,CAAA;CACkB,QAAQ,eAAe,YAAY,KAAK,SAAS,MAAK,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEnE,MAAM,MAAY;EAAY,OAAQ;GAAE,QAAQ;GAAU,QAAQ;GAAU,OAAO;EAAO,EAA8B,SAAS;CAAM;CACvI,IAAI,OAAoB;EAAa,OAAO,CAAC,CAAC,SAAS;GAAC;GAAa;GAAY;GAAW;EAAQ,CAAC,CAAC,SAAS,KAAK;CAAG;CAbtH,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAY,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,SAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UA7Bb;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BN,UAAA;EAAA,cAAA;GAAA;IAAA,MAAA;IAAA,MA5BgB;IAAU,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAY;GAAoB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,cAAA;IAAA,UAAA,CAAA,gBAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAU,UAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAkB,UAAA;IAAA,QAAA,CAAA,OAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAApF;IAAQ,MAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAc;IAAQ,MAAA;GAAA;EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AA8B7B,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAY,YAAA,CAAA;EAhCxB,MAAA;EAAU,MAAA,CAAA;GACT,iBAAiB,wBAAwB;GACzC,SAAS;IAAC;IAAU;IAAY;IAAU;IAAsB;IAAY;GAAkB;GAC9F,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BX,CAAA"}
|
|
@@ -111,6 +111,15 @@ var ApiService = class ApiService {
|
|
|
111
111
|
}
|
|
112
112
|
return this.tenant$.pipe(switchMap((t) => this.http.get(`${this.base}/tenants/${t.id}${path}`, { params })));
|
|
113
113
|
}
|
|
114
|
+
send(method, path, body, headers = {}) {
|
|
115
|
+
return this.tenant$.pipe(switchMap((t) => this.http.request(method, `${this.base}/tenants/${t.id}${path}`, {
|
|
116
|
+
body,
|
|
117
|
+
headers
|
|
118
|
+
})));
|
|
119
|
+
}
|
|
120
|
+
once(path, body) {
|
|
121
|
+
return this.send("POST", path, body, { "Idempotency-Key": crypto.randomUUID() });
|
|
122
|
+
}
|
|
114
123
|
all(path, query = {}, maxPages = 25) {
|
|
115
124
|
let pages = 0;
|
|
116
125
|
return this.get(path, {
|
|
@@ -149,6 +158,87 @@ var ApiService = class ApiService {
|
|
|
149
158
|
rules() {
|
|
150
159
|
return this.get("/rules", { limit: 200 });
|
|
151
160
|
}
|
|
161
|
+
createRule(definition) {
|
|
162
|
+
return this.send("POST", "/rules", definition);
|
|
163
|
+
}
|
|
164
|
+
updateRule(key, definition, etag) {
|
|
165
|
+
return this.send("PUT", `/rules/${key}`, definition, { "If-Match": `"${etag}"` });
|
|
166
|
+
}
|
|
167
|
+
channels() {
|
|
168
|
+
return this.get("/webhooks");
|
|
169
|
+
}
|
|
170
|
+
createChannel(body) {
|
|
171
|
+
return this.send("POST", "/webhooks", body);
|
|
172
|
+
}
|
|
173
|
+
updateChannel(id, body) {
|
|
174
|
+
return this.send("PUT", `/webhooks/${id}`, body);
|
|
175
|
+
}
|
|
176
|
+
deliveries(id, query = {}) {
|
|
177
|
+
return this.get(`/webhooks/${id}/deliveries`, {
|
|
178
|
+
limit: 50,
|
|
179
|
+
...query
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
allowlist() {
|
|
183
|
+
return this.get("/webhooks/allowlist");
|
|
184
|
+
}
|
|
185
|
+
putAllowlist(destination, ports) {
|
|
186
|
+
return this.send("PUT", "/webhooks/allowlist", {
|
|
187
|
+
destination,
|
|
188
|
+
ports
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
removeAllowlist(destination) {
|
|
192
|
+
return this.send("DELETE", `/webhooks/allowlist?destination=${encodeURIComponent(destination)}`);
|
|
193
|
+
}
|
|
194
|
+
escalationPolicies() {
|
|
195
|
+
return this.get("/escalation-policies");
|
|
196
|
+
}
|
|
197
|
+
createEscalationPolicy(body) {
|
|
198
|
+
return this.send("POST", "/escalation-policies", body);
|
|
199
|
+
}
|
|
200
|
+
updateEscalationPolicy(key, body) {
|
|
201
|
+
return this.send("PUT", `/escalation-policies/${key}`, body);
|
|
202
|
+
}
|
|
203
|
+
silences(query = {}) {
|
|
204
|
+
return this.get("/silences", {
|
|
205
|
+
limit: 200,
|
|
206
|
+
...query
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
createSilence(body) {
|
|
210
|
+
return this.once("/silences", body);
|
|
211
|
+
}
|
|
212
|
+
cancelSilence(id) {
|
|
213
|
+
return this.send("DELETE", `/silences/${id}`);
|
|
214
|
+
}
|
|
215
|
+
incidents(query = {}) {
|
|
216
|
+
return this.get("/incidents", {
|
|
217
|
+
limit: 100,
|
|
218
|
+
...query
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
incident(id) {
|
|
222
|
+
return this.get(`/incidents/${id}`);
|
|
223
|
+
}
|
|
224
|
+
createIncident(body) {
|
|
225
|
+
return this.once("/incidents", body);
|
|
226
|
+
}
|
|
227
|
+
moveIncident(id, to, comment) {
|
|
228
|
+
return this.once(`/incidents/${id}/transitions`, {
|
|
229
|
+
to,
|
|
230
|
+
comment
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
addIncidentNote(id, text) {
|
|
234
|
+
return this.once(`/incidents/${id}/notes`, { text });
|
|
235
|
+
}
|
|
236
|
+
removeIncidentAlert(id, alertId) {
|
|
237
|
+
return this.send("DELETE", `/incidents/${id}/alerts/${alertId}`);
|
|
238
|
+
}
|
|
239
|
+
alertNotifications(id) {
|
|
240
|
+
return this.get(`/alerts/${id}/notifications`);
|
|
241
|
+
}
|
|
152
242
|
alerts(query) {
|
|
153
243
|
return this.get("/alerts", {
|
|
154
244
|
limit: 100,
|
|
@@ -188,6 +278,18 @@ var ApiService = class ApiService {
|
|
|
188
278
|
capacity(query) {
|
|
189
279
|
return this.get("/telemetry/capacity", query);
|
|
190
280
|
}
|
|
281
|
+
timeline(query) {
|
|
282
|
+
return this.get("/timeline", query);
|
|
283
|
+
}
|
|
284
|
+
relatedToAlert(id) {
|
|
285
|
+
return this.get(`/alerts/${id}/related`);
|
|
286
|
+
}
|
|
287
|
+
loadRuns(query) {
|
|
288
|
+
return this.get("/load-runs", query);
|
|
289
|
+
}
|
|
290
|
+
loadRunResults(id) {
|
|
291
|
+
return this.get(`/load-runs/${id}/results`);
|
|
292
|
+
}
|
|
191
293
|
dependencyStats(query) {
|
|
192
294
|
return this.get("/telemetry/dependencies", query);
|
|
193
295
|
}
|
|
@@ -331,7 +433,7 @@ function page(path, title, load) {
|
|
|
331
433
|
const OBSERVABILITY_ROUTES = [
|
|
332
434
|
page("", "Overview", () => import("./juice-js-observability-overview.page-DXZl_fUh.mjs").then((m) => m.OverviewPage)),
|
|
333
435
|
page("services", "Services", () => import("./juice-js-observability-services.page-B1huLe_a.mjs").then((m) => m.ServicesPage)),
|
|
334
|
-
page("services/:id", "Service", () => import("./juice-js-observability-service.page-
|
|
436
|
+
page("services/:id", "Service", () => import("./juice-js-observability-service.page-BFXUe4mX.mjs").then((m) => m.ServicePage)),
|
|
335
437
|
page("traces", "Traces", () => import("./juice-js-observability-traces.page-DXSDdeTH.mjs").then((m) => m.TracesPage)),
|
|
336
438
|
page("traces/:traceId", "Trace", () => import("./juice-js-observability-trace.page-ByxGFLpE.mjs").then((m) => m.TracePage)),
|
|
337
439
|
page("topology", "Dependencies", () => import("./juice-js-observability-topology.page-CUayiRVT.mjs").then((m) => m.TopologyPage)),
|
|
@@ -340,8 +442,14 @@ const OBSERVABILITY_ROUTES = [
|
|
|
340
442
|
page("resources", "Resources", () => import("./juice-js-observability-resources.page-CukW8vVc.mjs").then((m) => m.ResourcesPage)),
|
|
341
443
|
page("resources/:id", "Resource", () => import("./juice-js-observability-resource.page-CauDE6kK.mjs").then((m) => m.ResourcePage)),
|
|
342
444
|
page("alerts", "Alerts", () => import("./juice-js-observability-alerts.page-DUXXBanH.mjs").then((m) => m.AlertsPage)),
|
|
343
|
-
page("alerts/:id", "Alert", () => import("./juice-js-observability-alert.page-
|
|
344
|
-
page("rules", "Rules", () => import("./juice-js-observability-rules.page-
|
|
445
|
+
page("alerts/:id", "Alert", () => import("./juice-js-observability-alert.page-DOAX9WWa.mjs").then((m) => m.AlertPage)),
|
|
446
|
+
page("rules", "Rules", () => import("./juice-js-observability-rules.page-Df2uDVwz.mjs").then((m) => m.RulesPage)),
|
|
447
|
+
page("notifications", "Notifications", () => import("./juice-js-observability-notifications.page-BDqb10C6.mjs").then((m) => m.NotificationsPage)),
|
|
448
|
+
page("silences", "Silences", () => import("./juice-js-observability-silences.page-Cg53dJAy.mjs").then((m) => m.SilencesPage)),
|
|
449
|
+
page("incidents", "Incidents", () => import("./juice-js-observability-incidents.page-CpNqYp2W.mjs").then((m) => m.IncidentsPage)),
|
|
450
|
+
page("incidents/:id", "Incident", () => import("./juice-js-observability-incident.page-CCHEWXou.mjs").then((m) => m.IncidentPage)),
|
|
451
|
+
page("timeline", "Timeline", () => import("./juice-js-observability-timeline.page-CtU2EoDN.mjs").then((m) => m.TimelinePage)),
|
|
452
|
+
page("load-runs", "Load runs", () => import("./juice-js-observability-load-runs.page-C2rk2bz9.mjs").then((m) => m.LoadRunsPage))
|
|
345
453
|
];
|
|
346
454
|
function observabilityMenus(basePath = "/", order = 1) {
|
|
347
455
|
const base = normalizeBasePath(basePath);
|
|
@@ -395,11 +503,41 @@ function observabilityMenus(basePath = "/", order = 1) {
|
|
|
395
503
|
icon: "notifications",
|
|
396
504
|
routerLink: link("alerts")
|
|
397
505
|
},
|
|
506
|
+
{
|
|
507
|
+
id: "incidents",
|
|
508
|
+
label: "Incidents",
|
|
509
|
+
icon: "emergency",
|
|
510
|
+
routerLink: link("incidents")
|
|
511
|
+
},
|
|
398
512
|
{
|
|
399
513
|
id: "rules",
|
|
400
514
|
label: "Rules",
|
|
401
515
|
icon: "rule",
|
|
402
516
|
routerLink: link("rules")
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
id: "silences",
|
|
520
|
+
label: "Silences",
|
|
521
|
+
icon: "notifications_paused",
|
|
522
|
+
routerLink: link("silences")
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
id: "notifications",
|
|
526
|
+
label: "Notifications",
|
|
527
|
+
icon: "campaign",
|
|
528
|
+
routerLink: link("notifications")
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
id: "timeline",
|
|
532
|
+
label: "Timeline",
|
|
533
|
+
icon: "history",
|
|
534
|
+
routerLink: link("timeline")
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
id: "load-runs",
|
|
538
|
+
label: "Load runs",
|
|
539
|
+
icon: "speed",
|
|
540
|
+
routerLink: link("load-runs")
|
|
403
541
|
}
|
|
404
542
|
].map((menu, index) => ({
|
|
405
543
|
...menu,
|
|
@@ -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 * 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, DependencyStatsPage, DeploymentEnvironment, Health, LogRecord, MetricInfo, Operation,\n Page, ResourceDetail, ResourceSummary, Rule, SearchHit, Series, Capacity, 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 capacity(query: Query) { return this.get<Capacity>('/telemetry/capacity', query); }\n dependencyStats(query: Query) { return this.get<DependencyStatsPage>('/telemetry/dependencies', 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, AlertNotifications, Alert, AllowlistEntry, Application, Channel, Delivery, EscalationPolicy, IncidentDetail, IncidentEvent, IncidentSummary, Silence, DependencyEvidence, DependencyStatsPage, DeploymentEnvironment, LoadRun, LoadRunResults, Related, Timeline, Health, LogRecord, MetricInfo, Operation,\n Page, ResourceDetail, ResourceSummary, Rule, SearchHit, Series, Capacity, 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 /** A change. `Idempotency-Key` on what can be retried, so a repeated click or a lost response doesn't do it twice. */\n private send<T>(method: 'POST' | 'PUT' | 'DELETE', path: string, body?: unknown, headers: Record<string, string> = {}): Observable<T> {\n return this.tenant$.pipe(switchMap(t => this.http.request<T>(method, `${this.base}/tenants/${t.id}${path}`, { body, headers })));\n }\n private once<T>(path: string, body: unknown): Observable<T> { return this.send<T>('POST', path, body, { 'Idempotency-Key': crypto.randomUUID() }); }\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 createRule(definition: unknown) { return this.send<Rule>('POST', '/rules', definition); }\n /** With the rule's etag, so a change made since it was read isn't overwritten. */\n updateRule(key: string, definition: unknown, etag: string) { return this.send<Rule>('PUT', `/rules/${key}`, definition, { 'If-Match': `\"${etag}\"` }); }\n\n channels() { return this.get<Page<Channel>>('/webhooks'); }\n createChannel(body: unknown) { return this.send<Channel>('POST', '/webhooks', body); }\n updateChannel(id: string, body: unknown) { return this.send<Channel>('PUT', `/webhooks/${id}`, body); }\n deliveries(id: string, query: Query = {}) { return this.get<Page<Delivery>>(`/webhooks/${id}/deliveries`, { limit: 50, ...query }); }\n allowlist() { return this.get<Page<AllowlistEntry>>('/webhooks/allowlist'); }\n putAllowlist(destination: string, ports: number[]) { return this.send<AllowlistEntry>('PUT', '/webhooks/allowlist', { destination, ports }); }\n removeAllowlist(destination: string) { return this.send<void>('DELETE', `/webhooks/allowlist?destination=${encodeURIComponent(destination)}`); }\n\n escalationPolicies() { return this.get<Page<EscalationPolicy>>('/escalation-policies'); }\n createEscalationPolicy(body: unknown) { return this.send<EscalationPolicy>('POST', '/escalation-policies', body); }\n updateEscalationPolicy(key: string, body: unknown) { return this.send<EscalationPolicy>('PUT', `/escalation-policies/${key}`, body); }\n\n silences(query: Query = {}) { return this.get<Page<Silence>>('/silences', { limit: 200, ...query }); }\n createSilence(body: unknown) { return this.once<Silence>('/silences', body); }\n cancelSilence(id: string) { return this.send<void>('DELETE', `/silences/${id}`); }\n\n incidents(query: Query = {}) { return this.get<Page<IncidentSummary>>('/incidents', { limit: 100, ...query }); }\n incident(id: string) { return this.get<IncidentDetail>(`/incidents/${id}`); }\n createIncident(body: unknown) { return this.once<IncidentSummary>('/incidents', body); }\n moveIncident(id: string, to: string, comment: string) { return this.once<IncidentSummary>(`/incidents/${id}/transitions`, { to, comment }); }\n addIncidentNote(id: string, text: string) { return this.once<IncidentEvent>(`/incidents/${id}/notes`, { text }); }\n removeIncidentAlert(id: string, alertId: string) { return this.send<void>('DELETE', `/incidents/${id}/alerts/${alertId}`); }\n alertNotifications(id: string) { return this.get<AlertNotifications>(`/alerts/${id}/notifications`); }\n\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 capacity(query: Query) { return this.get<Capacity>('/telemetry/capacity', query); }\n timeline(query: Query) { return this.get<Timeline>('/timeline', query); }\n relatedToAlert(id: string) { return this.get<Related>(`/alerts/${id}/related`); }\n loadRuns(query: Query) { return this.get<Page<LoadRun>>('/load-runs', query); }\n loadRunResults(id: string) { return this.get<LoadRunResults>(`/load-runs/${id}/results`); }\n dependencyStats(query: Query) { return this.get<DependencyStatsPage>('/telemetry/dependencies', 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 page('notifications', 'Notifications', () => import('./pages/notifications.page').then(m => m.NotificationsPage)),\n page('silences', 'Silences', () => import('./pages/silences.page').then(m => m.SilencesPage)),\n page('incidents', 'Incidents', () => import('./pages/incidents.page').then(m => m.IncidentsPage)),\n page('incidents/:id', 'Incident', () => import('./pages/incident.page').then(m => m.IncidentPage)),\n page('timeline', 'Timeline', () => import('./pages/timeline.page').then(m => m.TimelinePage)),\n page('load-runs', 'Load runs', () => import('./pages/load-runs.page').then(m => m.LoadRunsPage)),\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: 'incidents', label: 'Incidents', icon: 'emergency', routerLink: link('incidents') },\n { id: 'rules', label: 'Rules', icon: 'rule', routerLink: link('rules') },\n { id: 'silences', label: 'Silences', icon: 'notifications_paused', routerLink: link('silences') },\n { id: 'notifications', label: 'Notifications', icon: 'campaign', routerLink: link('notifications') },\n { id: 'timeline', label: 'Timeline', icon: 'history', routerLink: link('timeline') },\n { id: 'load-runs', label: 'Load runs', icon: 'speed', routerLink: link('load-runs') },\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.1.0-alpha.
|
|
3
|
+
"version": "1.1.0-alpha.22",
|
|
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",
|
|
@@ -166,6 +166,12 @@
|
|
|
166
166
|
.jc-capacity-result strong { font-size: 24px; font-weight: 500; line-height: 1.2; }
|
|
167
167
|
.jc-capacity-notes { margin: 12px 0 0; padding-left: 18px; font-size: 13px; }
|
|
168
168
|
.jc-capacity-measured th { width: 240px; }
|
|
169
|
+
.jc-timeline-change { font-weight: 500; }
|
|
170
|
+
.jc-form { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center; margin-bottom: 8px; @include mat.form-field-density(-1); }
|
|
171
|
+
.jc-form-actions { display: flex; align-items: center; gap: 8px; width: 100%; }
|
|
172
|
+
.jc-row-actions { white-space: nowrap; text-align: right; }
|
|
173
|
+
.jc-load-steps { margin-top: 12px; }
|
|
174
|
+
.jc-load-steps summary { cursor: pointer; font-size: 13px; }
|
|
169
175
|
|
|
170
176
|
.jc-chart { height: 180px; width: 100%; background: var(--jc-surface); border: 1px solid var(--jc-border); border-radius: 6px; }
|
|
171
177
|
.jc-chart-empty { display: flex; align-items: center; justify-content: center; color: var(--jc-muted); font-size: 13px; }
|