@juice-js/observability 1.1.0-alpha.21 → 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/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-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-BrcID0Bs.mjs → juice-js-observability-service.page-BFXUe4mX.mjs} +3 -1
- 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 +128 -3
- package/fesm2022/juice-js-observability.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/_observability.scss +4 -0
- package/types/juice-js-observability.d.ts +197 -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-BrcID0Bs.mjs.map +0 -1
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import { ApiService } from "./juice-js-observability.mjs";
|
|
2
|
+
import { S as apiResource, 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, signal } from "@angular/core";
|
|
5
|
+
import { DatePipe } from "@angular/common";
|
|
6
|
+
import * as i1 from "@angular/forms";
|
|
7
|
+
import { FormsModule } from "@angular/forms";
|
|
8
|
+
import * as i2 from "@angular/material/button";
|
|
9
|
+
import { MatButtonModule } from "@angular/material/button";
|
|
10
|
+
import { MatFormFieldModule } from "@angular/material/form-field";
|
|
11
|
+
import * as i5 from "@angular/material/input";
|
|
12
|
+
import { MatInputModule } from "@angular/material/input";
|
|
13
|
+
import * as i6 from "@angular/material/progress-bar";
|
|
14
|
+
import { MatProgressBarModule } from "@angular/material/progress-bar";
|
|
15
|
+
import * as i4 from "@angular/material/select";
|
|
16
|
+
import { MatSelectModule } from "@angular/material/select";
|
|
17
|
+
import * as i3 from "@angular/material/checkbox";
|
|
18
|
+
import { MatCheckboxModule } from "@angular/material/checkbox";
|
|
19
|
+
var SilencesPage = class SilencesPage {
|
|
20
|
+
api = inject(ApiService);
|
|
21
|
+
service = input(...ngDevMode ? [void 0, { debugName: "service" }] : /* istanbul ignore next */ []);
|
|
22
|
+
environment = input(...ngDevMode ? [void 0, { debugName: "environment" }] : /* istanbul ignore next */ []);
|
|
23
|
+
host = input(...ngDevMode ? [void 0, { debugName: "host" }] : /* istanbul ignore next */ []);
|
|
24
|
+
showAll = signal(false, ...ngDevMode ? [{ debugName: "showAll" }] : /* istanbul ignore next */ []);
|
|
25
|
+
saving = signal(false, ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
|
|
26
|
+
saveError = signal("", ...ngDevMode ? [{ debugName: "saveError" }] : /* istanbul ignore next */ []);
|
|
27
|
+
kind = "Silence";
|
|
28
|
+
comment = "";
|
|
29
|
+
hours = 1;
|
|
30
|
+
starts = "";
|
|
31
|
+
weekly = false;
|
|
32
|
+
serviceField = "";
|
|
33
|
+
environmentField = "";
|
|
34
|
+
hostField = "";
|
|
35
|
+
rules = "";
|
|
36
|
+
maximum = "";
|
|
37
|
+
all = false;
|
|
38
|
+
silences = apiResource({
|
|
39
|
+
params: () => ({ all: this.showAll() }),
|
|
40
|
+
stream: ({ params }) => this.api.silences({ all: params.all || null })
|
|
41
|
+
});
|
|
42
|
+
error = computed(() => problemText(this.silences.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
|
|
43
|
+
ngOnInit() {
|
|
44
|
+
this.serviceField = this.service() ?? "";
|
|
45
|
+
this.environmentField = this.environment() ?? "";
|
|
46
|
+
this.hostField = this.host() ?? "";
|
|
47
|
+
}
|
|
48
|
+
scope(s) {
|
|
49
|
+
if (s.allAlerts) return "Everything";
|
|
50
|
+
return [
|
|
51
|
+
s.service,
|
|
52
|
+
s.environment,
|
|
53
|
+
s.host,
|
|
54
|
+
s.ruleKeys.join(", "),
|
|
55
|
+
s.maximumSeverity ? `up to ${s.maximumSeverity}` : ""
|
|
56
|
+
].filter(Boolean).join(" · ");
|
|
57
|
+
}
|
|
58
|
+
over(s) {
|
|
59
|
+
return !s.repeatWeekly && new Date(s.endsAt).getTime() <= Date.now();
|
|
60
|
+
}
|
|
61
|
+
create() {
|
|
62
|
+
const start = this.kind === "Maintenance" && this.starts ? new Date(this.starts) : /* @__PURE__ */ new Date();
|
|
63
|
+
const end = new Date(start.getTime() + (this.hours || 0) * 36e5);
|
|
64
|
+
this.saving.set(true);
|
|
65
|
+
this.saveError.set("");
|
|
66
|
+
this.api.createSilence({
|
|
67
|
+
kind: this.kind,
|
|
68
|
+
comment: this.comment,
|
|
69
|
+
startsAt: this.kind === "Maintenance" ? start.toISOString() : void 0,
|
|
70
|
+
endsAt: end.toISOString(),
|
|
71
|
+
repeatWeekly: this.kind === "Maintenance" && this.weekly,
|
|
72
|
+
allAlerts: this.all,
|
|
73
|
+
service: this.all ? void 0 : this.serviceField,
|
|
74
|
+
environment: this.all ? void 0 : this.environmentField,
|
|
75
|
+
host: this.all ? void 0 : this.hostField,
|
|
76
|
+
ruleKeys: this.all ? [] : this.rules.split(",").map((r) => r.trim()).filter(Boolean),
|
|
77
|
+
maximumSeverity: this.all ? void 0 : this.maximum || void 0
|
|
78
|
+
}).subscribe({
|
|
79
|
+
next: () => {
|
|
80
|
+
this.saving.set(false);
|
|
81
|
+
this.comment = "";
|
|
82
|
+
this.silences.reload();
|
|
83
|
+
},
|
|
84
|
+
error: (e) => {
|
|
85
|
+
this.saving.set(false);
|
|
86
|
+
this.saveError.set(e.status === 403 ? "Silencing needs the operator or admin role." : problemText(e));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
cancel(s) {
|
|
91
|
+
this.api.cancelSilence(s.id).subscribe({
|
|
92
|
+
next: () => this.silences.reload(),
|
|
93
|
+
error: (e) => this.saveError.set(e.status === 403 ? "Ending a silence needs the operator or admin role." : problemText(e))
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
97
|
+
minVersion: "12.0.0",
|
|
98
|
+
version: "22.2.4",
|
|
99
|
+
ngImport: i0,
|
|
100
|
+
type: SilencesPage,
|
|
101
|
+
deps: [],
|
|
102
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
103
|
+
});
|
|
104
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
105
|
+
minVersion: "17.0.0",
|
|
106
|
+
version: "22.2.4",
|
|
107
|
+
type: SilencesPage,
|
|
108
|
+
isStandalone: true,
|
|
109
|
+
selector: "ng-component",
|
|
110
|
+
inputs: {
|
|
111
|
+
service: {
|
|
112
|
+
classPropertyName: "service",
|
|
113
|
+
publicName: "service",
|
|
114
|
+
isSignal: true,
|
|
115
|
+
isRequired: false,
|
|
116
|
+
transformFunction: null
|
|
117
|
+
},
|
|
118
|
+
environment: {
|
|
119
|
+
classPropertyName: "environment",
|
|
120
|
+
publicName: "environment",
|
|
121
|
+
isSignal: true,
|
|
122
|
+
isRequired: false,
|
|
123
|
+
transformFunction: null
|
|
124
|
+
},
|
|
125
|
+
host: {
|
|
126
|
+
classPropertyName: "host",
|
|
127
|
+
publicName: "host",
|
|
128
|
+
isSignal: true,
|
|
129
|
+
isRequired: false,
|
|
130
|
+
transformFunction: null
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
ngImport: i0,
|
|
134
|
+
template: `
|
|
135
|
+
<jc-context-bar>
|
|
136
|
+
<h1>Silences</h1>
|
|
137
|
+
<span class="jc-spacer"></span>
|
|
138
|
+
<mat-checkbox [ngModel]="showAll()" (ngModelChange)="showAll.set($event)" data-testid="silences-all">Include ended</mat-checkbox>
|
|
139
|
+
</jc-context-bar>
|
|
140
|
+
@if (silences.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
|
|
141
|
+
@if (silences.error()) { <p class="jc-error">{{ error() }}</p> }
|
|
142
|
+
|
|
143
|
+
<section class="jc-card">
|
|
144
|
+
<h2>New</h2>
|
|
145
|
+
<form class="jc-form" data-testid="silence-form" (ngSubmit)="create()">
|
|
146
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>Kind</mat-label>
|
|
147
|
+
<mat-select name="kind" [(ngModel)]="kind"><mat-option value="Silence">Silence, from now</mat-option><mat-option value="Maintenance">Maintenance window</mat-option></mat-select>
|
|
148
|
+
</mat-form-field>
|
|
149
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-wide"><mat-label>Why</mat-label><input matInput name="comment" data-testid="silence-comment" [(ngModel)]="comment" maxlength="500"></mat-form-field>
|
|
150
|
+
@if (kind === 'Maintenance') {
|
|
151
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Starts</mat-label><input matInput type="datetime-local" name="starts" [(ngModel)]="starts"></mat-form-field>
|
|
152
|
+
<mat-checkbox name="weekly" [(ngModel)]="weekly">Every week</mat-checkbox>
|
|
153
|
+
}
|
|
154
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>For (hours)</mat-label><input matInput type="number" min="0.1" step="0.5" name="hours" data-testid="silence-hours" [(ngModel)]="hours"></mat-form-field>
|
|
155
|
+
<p class="jc-muted jc-small jc-form-actions">What to hold. Everything filled in must match.</p>
|
|
156
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Service</mat-label><input matInput name="service" data-testid="silence-service" [(ngModel)]="serviceField" [disabled]="all"></mat-form-field>
|
|
157
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Environment</mat-label><input matInput name="environment" [(ngModel)]="environmentField" [disabled]="all"></mat-form-field>
|
|
158
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Host</mat-label><input matInput name="host" [(ngModel)]="hostField" [disabled]="all"></mat-form-field>
|
|
159
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Rules, comma separated</mat-label><input matInput name="rules" [(ngModel)]="rules" [disabled]="all"></mat-form-field>
|
|
160
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>Severity up to</mat-label>
|
|
161
|
+
<mat-select name="maximum" [(ngModel)]="maximum" [disabled]="all"><mat-option value="">Any</mat-option><mat-option value="Info">Info</mat-option><mat-option value="Warning">Warning</mat-option></mat-select>
|
|
162
|
+
</mat-form-field>
|
|
163
|
+
<mat-checkbox name="all" [(ngModel)]="all">Everything</mat-checkbox>
|
|
164
|
+
<div class="jc-form-actions">
|
|
165
|
+
<button mat-flat-button type="submit" data-testid="silence-save" [disabled]="saving()">{{ kind === 'Silence' ? 'Silence' : 'Schedule' }}</button>
|
|
166
|
+
@if (saveError()) { <span class="jc-error" data-testid="silence-error">{{ saveError() }}</span> }
|
|
167
|
+
</div>
|
|
168
|
+
</form>
|
|
169
|
+
</section>
|
|
170
|
+
|
|
171
|
+
<table class="jc-table" data-testid="silences">
|
|
172
|
+
<thead><tr><th>Why</th><th>Holds</th><th>When</th><th>Holding</th><th>By</th><th></th></tr></thead>
|
|
173
|
+
<tbody>
|
|
174
|
+
@for (s of silences.value()?.items ?? []; track s.id) {
|
|
175
|
+
<tr [class.jc-muted]="s.cancelledAt">
|
|
176
|
+
<td><strong>{{ s.comment }}</strong><div class="jc-small">@if (s.active) { <span class="jc-badge jc-health-degraded">Active</span> } @else if (s.cancelledAt) { Cancelled by {{ s.cancelledBy }} } @else { {{ s.kind === 'Maintenance' ? 'Maintenance' : 'Silence' }} }</div></td>
|
|
177
|
+
<td class="jc-small">{{ scope(s) }}</td>
|
|
178
|
+
<td class="jc-nowrap jc-small">{{ s.startsAt | date: 'MMM d, HH:mm' }} to {{ s.endsAt | date: 'MMM d, HH:mm' }}@if (s.repeatWeekly) { <div>every week</div> }</td>
|
|
179
|
+
<td>{{ s.held }}</td>
|
|
180
|
+
<td>{{ s.createdBy }}</td>
|
|
181
|
+
<td class="jc-row-actions">@if (!s.cancelledAt && !over(s)) { <button mat-button type="button" [attr.data-testid]="'silence-cancel-' + s.id" (click)="cancel(s)">End now</button> }</td>
|
|
182
|
+
</tr>
|
|
183
|
+
} @empty { <tr><td colspan="6" class="jc-muted">No silences: every matching alert notifies.</td></tr> }
|
|
184
|
+
</tbody>
|
|
185
|
+
</table>`,
|
|
186
|
+
isInline: true,
|
|
187
|
+
dependencies: [
|
|
188
|
+
{
|
|
189
|
+
kind: "ngmodule",
|
|
190
|
+
type: FormsModule
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
kind: "directive",
|
|
194
|
+
type: i1.ɵNgNoValidate,
|
|
195
|
+
selector: "form:not([ngNoForm]):not([ngNativeValidate])"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
kind: "directive",
|
|
199
|
+
type: i1.DefaultValueAccessor,
|
|
200
|
+
selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
kind: "directive",
|
|
204
|
+
type: i1.NumberValueAccessor,
|
|
205
|
+
selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
kind: "directive",
|
|
209
|
+
type: i1.NgControlStatus,
|
|
210
|
+
selector: "[formControlName],[ngModel],[formControl]"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
kind: "directive",
|
|
214
|
+
type: i1.NgControlStatusGroup,
|
|
215
|
+
selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
kind: "directive",
|
|
219
|
+
type: i1.MaxLengthValidator,
|
|
220
|
+
selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]",
|
|
221
|
+
inputs: ["maxlength"]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
kind: "directive",
|
|
225
|
+
type: i1.MinValidator,
|
|
226
|
+
selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",
|
|
227
|
+
inputs: ["min"]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
kind: "directive",
|
|
231
|
+
type: i1.NgModel,
|
|
232
|
+
selector: "[ngModel]:not([formControlName]):not([formControl])",
|
|
233
|
+
inputs: [
|
|
234
|
+
"name",
|
|
235
|
+
"disabled",
|
|
236
|
+
"ngModel",
|
|
237
|
+
"ngModelOptions"
|
|
238
|
+
],
|
|
239
|
+
outputs: ["ngModelChange"],
|
|
240
|
+
exportAs: ["ngModel"]
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
kind: "directive",
|
|
244
|
+
type: i1.NgForm,
|
|
245
|
+
selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]",
|
|
246
|
+
inputs: ["ngFormOptions"],
|
|
247
|
+
outputs: ["ngSubmit"],
|
|
248
|
+
exportAs: ["ngForm"]
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
kind: "ngmodule",
|
|
252
|
+
type: MatButtonModule
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
kind: "component",
|
|
256
|
+
type: i2.MatButton,
|
|
257
|
+
selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ",
|
|
258
|
+
inputs: ["matButton"],
|
|
259
|
+
exportAs: ["matButton", "matAnchor"]
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
kind: "ngmodule",
|
|
263
|
+
type: MatCheckboxModule
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
kind: "component",
|
|
267
|
+
type: i3.MatCheckbox,
|
|
268
|
+
selector: "mat-checkbox",
|
|
269
|
+
inputs: [
|
|
270
|
+
"aria-label",
|
|
271
|
+
"aria-labelledby",
|
|
272
|
+
"aria-describedby",
|
|
273
|
+
"aria-expanded",
|
|
274
|
+
"aria-controls",
|
|
275
|
+
"aria-owns",
|
|
276
|
+
"id",
|
|
277
|
+
"required",
|
|
278
|
+
"labelPosition",
|
|
279
|
+
"name",
|
|
280
|
+
"value",
|
|
281
|
+
"disableRipple",
|
|
282
|
+
"tabIndex",
|
|
283
|
+
"color",
|
|
284
|
+
"disabledInteractive",
|
|
285
|
+
"checked",
|
|
286
|
+
"disabled",
|
|
287
|
+
"indeterminate"
|
|
288
|
+
],
|
|
289
|
+
outputs: ["change", "indeterminateChange"],
|
|
290
|
+
exportAs: ["matCheckbox"]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
kind: "ngmodule",
|
|
294
|
+
type: MatFormFieldModule
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
kind: "component",
|
|
298
|
+
type: i4.MatFormField,
|
|
299
|
+
selector: "mat-form-field",
|
|
300
|
+
inputs: [
|
|
301
|
+
"hideRequiredMarker",
|
|
302
|
+
"color",
|
|
303
|
+
"floatLabel",
|
|
304
|
+
"appearance",
|
|
305
|
+
"subscriptSizing",
|
|
306
|
+
"hintLabel"
|
|
307
|
+
],
|
|
308
|
+
exportAs: ["matFormField"]
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
kind: "directive",
|
|
312
|
+
type: i4.MatLabel,
|
|
313
|
+
selector: "mat-label"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
kind: "ngmodule",
|
|
317
|
+
type: MatInputModule
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
kind: "directive",
|
|
321
|
+
type: i5.MatInput,
|
|
322
|
+
selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",
|
|
323
|
+
inputs: [
|
|
324
|
+
"disabled",
|
|
325
|
+
"id",
|
|
326
|
+
"placeholder",
|
|
327
|
+
"name",
|
|
328
|
+
"required",
|
|
329
|
+
"type",
|
|
330
|
+
"errorStateMatcher",
|
|
331
|
+
"aria-describedby",
|
|
332
|
+
"value",
|
|
333
|
+
"readonly",
|
|
334
|
+
"disabledInteractive"
|
|
335
|
+
],
|
|
336
|
+
exportAs: ["matInput"]
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
kind: "ngmodule",
|
|
340
|
+
type: MatProgressBarModule
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
kind: "component",
|
|
344
|
+
type: i6.MatProgressBar,
|
|
345
|
+
selector: "mat-progress-bar",
|
|
346
|
+
inputs: [
|
|
347
|
+
"color",
|
|
348
|
+
"value",
|
|
349
|
+
"bufferValue",
|
|
350
|
+
"mode"
|
|
351
|
+
],
|
|
352
|
+
outputs: ["animationEnd"],
|
|
353
|
+
exportAs: ["matProgressBar"]
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
kind: "ngmodule",
|
|
357
|
+
type: MatSelectModule
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
kind: "component",
|
|
361
|
+
type: i4.MatSelect,
|
|
362
|
+
selector: "mat-select",
|
|
363
|
+
inputs: [
|
|
364
|
+
"aria-describedby",
|
|
365
|
+
"panelClass",
|
|
366
|
+
"disabled",
|
|
367
|
+
"disableRipple",
|
|
368
|
+
"tabIndex",
|
|
369
|
+
"hideSingleSelectionIndicator",
|
|
370
|
+
"placeholder",
|
|
371
|
+
"required",
|
|
372
|
+
"multiple",
|
|
373
|
+
"disableOptionCentering",
|
|
374
|
+
"compareWith",
|
|
375
|
+
"value",
|
|
376
|
+
"aria-label",
|
|
377
|
+
"aria-labelledby",
|
|
378
|
+
"errorStateMatcher",
|
|
379
|
+
"typeaheadDebounceInterval",
|
|
380
|
+
"sortComparator",
|
|
381
|
+
"id",
|
|
382
|
+
"panelWidth",
|
|
383
|
+
"canSelectNullableOptions"
|
|
384
|
+
],
|
|
385
|
+
outputs: [
|
|
386
|
+
"openedChange",
|
|
387
|
+
"opened",
|
|
388
|
+
"closed",
|
|
389
|
+
"selectionChange",
|
|
390
|
+
"valueChange"
|
|
391
|
+
],
|
|
392
|
+
exportAs: ["matSelect"]
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
kind: "component",
|
|
396
|
+
type: i4.MatOption,
|
|
397
|
+
selector: "mat-option",
|
|
398
|
+
inputs: [
|
|
399
|
+
"value",
|
|
400
|
+
"id",
|
|
401
|
+
"disabled"
|
|
402
|
+
],
|
|
403
|
+
outputs: ["onSelectionChange"],
|
|
404
|
+
exportAs: ["matOption"]
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
kind: "component",
|
|
408
|
+
type: ContextBar,
|
|
409
|
+
selector: "jc-context-bar"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
kind: "pipe",
|
|
413
|
+
type: DatePipe,
|
|
414
|
+
name: "date"
|
|
415
|
+
}
|
|
416
|
+
],
|
|
417
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
421
|
+
minVersion: "12.0.0",
|
|
422
|
+
version: "22.2.4",
|
|
423
|
+
ngImport: i0,
|
|
424
|
+
type: SilencesPage,
|
|
425
|
+
decorators: [{
|
|
426
|
+
type: Component,
|
|
427
|
+
args: [{
|
|
428
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
429
|
+
imports: [
|
|
430
|
+
DatePipe,
|
|
431
|
+
FormsModule,
|
|
432
|
+
MatButtonModule,
|
|
433
|
+
MatCheckboxModule,
|
|
434
|
+
MatFormFieldModule,
|
|
435
|
+
MatInputModule,
|
|
436
|
+
MatProgressBarModule,
|
|
437
|
+
MatSelectModule,
|
|
438
|
+
ContextBar
|
|
439
|
+
],
|
|
440
|
+
template: `
|
|
441
|
+
<jc-context-bar>
|
|
442
|
+
<h1>Silences</h1>
|
|
443
|
+
<span class="jc-spacer"></span>
|
|
444
|
+
<mat-checkbox [ngModel]="showAll()" (ngModelChange)="showAll.set($event)" data-testid="silences-all">Include ended</mat-checkbox>
|
|
445
|
+
</jc-context-bar>
|
|
446
|
+
@if (silences.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
|
|
447
|
+
@if (silences.error()) { <p class="jc-error">{{ error() }}</p> }
|
|
448
|
+
|
|
449
|
+
<section class="jc-card">
|
|
450
|
+
<h2>New</h2>
|
|
451
|
+
<form class="jc-form" data-testid="silence-form" (ngSubmit)="create()">
|
|
452
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>Kind</mat-label>
|
|
453
|
+
<mat-select name="kind" [(ngModel)]="kind"><mat-option value="Silence">Silence, from now</mat-option><mat-option value="Maintenance">Maintenance window</mat-option></mat-select>
|
|
454
|
+
</mat-form-field>
|
|
455
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-wide"><mat-label>Why</mat-label><input matInput name="comment" data-testid="silence-comment" [(ngModel)]="comment" maxlength="500"></mat-form-field>
|
|
456
|
+
@if (kind === 'Maintenance') {
|
|
457
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Starts</mat-label><input matInput type="datetime-local" name="starts" [(ngModel)]="starts"></mat-form-field>
|
|
458
|
+
<mat-checkbox name="weekly" [(ngModel)]="weekly">Every week</mat-checkbox>
|
|
459
|
+
}
|
|
460
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>For (hours)</mat-label><input matInput type="number" min="0.1" step="0.5" name="hours" data-testid="silence-hours" [(ngModel)]="hours"></mat-form-field>
|
|
461
|
+
<p class="jc-muted jc-small jc-form-actions">What to hold. Everything filled in must match.</p>
|
|
462
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Service</mat-label><input matInput name="service" data-testid="silence-service" [(ngModel)]="serviceField" [disabled]="all"></mat-form-field>
|
|
463
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Environment</mat-label><input matInput name="environment" [(ngModel)]="environmentField" [disabled]="all"></mat-form-field>
|
|
464
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Host</mat-label><input matInput name="host" [(ngModel)]="hostField" [disabled]="all"></mat-form-field>
|
|
465
|
+
<mat-form-field subscriptSizing="dynamic"><mat-label>Rules, comma separated</mat-label><input matInput name="rules" [(ngModel)]="rules" [disabled]="all"></mat-form-field>
|
|
466
|
+
<mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>Severity up to</mat-label>
|
|
467
|
+
<mat-select name="maximum" [(ngModel)]="maximum" [disabled]="all"><mat-option value="">Any</mat-option><mat-option value="Info">Info</mat-option><mat-option value="Warning">Warning</mat-option></mat-select>
|
|
468
|
+
</mat-form-field>
|
|
469
|
+
<mat-checkbox name="all" [(ngModel)]="all">Everything</mat-checkbox>
|
|
470
|
+
<div class="jc-form-actions">
|
|
471
|
+
<button mat-flat-button type="submit" data-testid="silence-save" [disabled]="saving()">{{ kind === 'Silence' ? 'Silence' : 'Schedule' }}</button>
|
|
472
|
+
@if (saveError()) { <span class="jc-error" data-testid="silence-error">{{ saveError() }}</span> }
|
|
473
|
+
</div>
|
|
474
|
+
</form>
|
|
475
|
+
</section>
|
|
476
|
+
|
|
477
|
+
<table class="jc-table" data-testid="silences">
|
|
478
|
+
<thead><tr><th>Why</th><th>Holds</th><th>When</th><th>Holding</th><th>By</th><th></th></tr></thead>
|
|
479
|
+
<tbody>
|
|
480
|
+
@for (s of silences.value()?.items ?? []; track s.id) {
|
|
481
|
+
<tr [class.jc-muted]="s.cancelledAt">
|
|
482
|
+
<td><strong>{{ s.comment }}</strong><div class="jc-small">@if (s.active) { <span class="jc-badge jc-health-degraded">Active</span> } @else if (s.cancelledAt) { Cancelled by {{ s.cancelledBy }} } @else { {{ s.kind === 'Maintenance' ? 'Maintenance' : 'Silence' }} }</div></td>
|
|
483
|
+
<td class="jc-small">{{ scope(s) }}</td>
|
|
484
|
+
<td class="jc-nowrap jc-small">{{ s.startsAt | date: 'MMM d, HH:mm' }} to {{ s.endsAt | date: 'MMM d, HH:mm' }}@if (s.repeatWeekly) { <div>every week</div> }</td>
|
|
485
|
+
<td>{{ s.held }}</td>
|
|
486
|
+
<td>{{ s.createdBy }}</td>
|
|
487
|
+
<td class="jc-row-actions">@if (!s.cancelledAt && !over(s)) { <button mat-button type="button" [attr.data-testid]="'silence-cancel-' + s.id" (click)="cancel(s)">End now</button> }</td>
|
|
488
|
+
</tr>
|
|
489
|
+
} @empty { <tr><td colspan="6" class="jc-muted">No silences: every matching alert notifies.</td></tr> }
|
|
490
|
+
</tbody>
|
|
491
|
+
</table>`
|
|
492
|
+
}]
|
|
493
|
+
}],
|
|
494
|
+
propDecorators: {
|
|
495
|
+
service: [{
|
|
496
|
+
type: i0.Input,
|
|
497
|
+
args: [{
|
|
498
|
+
isSignal: true,
|
|
499
|
+
alias: "service",
|
|
500
|
+
required: false
|
|
501
|
+
}]
|
|
502
|
+
}],
|
|
503
|
+
environment: [{
|
|
504
|
+
type: i0.Input,
|
|
505
|
+
args: [{
|
|
506
|
+
isSignal: true,
|
|
507
|
+
alias: "environment",
|
|
508
|
+
required: false
|
|
509
|
+
}]
|
|
510
|
+
}],
|
|
511
|
+
host: [{
|
|
512
|
+
type: i0.Input,
|
|
513
|
+
args: [{
|
|
514
|
+
isSignal: true,
|
|
515
|
+
alias: "host",
|
|
516
|
+
required: false
|
|
517
|
+
}]
|
|
518
|
+
}]
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
export { SilencesPage };
|
|
522
|
+
|
|
523
|
+
//# sourceMappingURL=juice-js-observability-silences.page-Cg53dJAy.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juice-js-observability-silences.page-Cg53dJAy.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/silences.page.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, OnInit, computed, inject, input, signal } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { MatSelectModule } from '@angular/material/select';\nimport { ApiService } from '../core/api.service';\nimport { Silence } from '../core/models';\nimport { apiResource } from '../shared/resource';\nimport { problemText } from '../shared/telemetry';\nimport { ContextBar } from '../shared/ui';\n\n/**\n * Silences and maintenance windows. Alerts go on firing and resolving under one: only what is sent changes, and what was held is sent when it ends if it still matters. Every\n * silence has an end and a reason, and shows how much it's holding, so a forgotten one can't quietly hide an outage.\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [DatePipe, FormsModule, MatButtonModule, MatCheckboxModule, MatFormFieldModule, MatInputModule, MatProgressBarModule, MatSelectModule, ContextBar],\n template: `\n <jc-context-bar>\n <h1>Silences</h1>\n <span class=\"jc-spacer\"></span>\n <mat-checkbox [ngModel]=\"showAll()\" (ngModelChange)=\"showAll.set($event)\" data-testid=\"silences-all\">Include ended</mat-checkbox>\n </jc-context-bar>\n @if (silences.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (silences.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n\n <section class=\"jc-card\">\n <h2>New</h2>\n <form class=\"jc-form\" data-testid=\"silence-form\" (ngSubmit)=\"create()\">\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\"><mat-label>Kind</mat-label>\n <mat-select name=\"kind\" [(ngModel)]=\"kind\"><mat-option value=\"Silence\">Silence, from now</mat-option><mat-option value=\"Maintenance\">Maintenance window</mat-option></mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\"><mat-label>Why</mat-label><input matInput name=\"comment\" data-testid=\"silence-comment\" [(ngModel)]=\"comment\" maxlength=\"500\"></mat-form-field>\n @if (kind === 'Maintenance') {\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Starts</mat-label><input matInput type=\"datetime-local\" name=\"starts\" [(ngModel)]=\"starts\"></mat-form-field>\n <mat-checkbox name=\"weekly\" [(ngModel)]=\"weekly\">Every week</mat-checkbox>\n }\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\"><mat-label>For (hours)</mat-label><input matInput type=\"number\" min=\"0.1\" step=\"0.5\" name=\"hours\" data-testid=\"silence-hours\" [(ngModel)]=\"hours\"></mat-form-field>\n <p class=\"jc-muted jc-small jc-form-actions\">What to hold. Everything filled in must match.</p>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Service</mat-label><input matInput name=\"service\" data-testid=\"silence-service\" [(ngModel)]=\"serviceField\" [disabled]=\"all\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Environment</mat-label><input matInput name=\"environment\" [(ngModel)]=\"environmentField\" [disabled]=\"all\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Host</mat-label><input matInput name=\"host\" [(ngModel)]=\"hostField\" [disabled]=\"all\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\"><mat-label>Rules, comma separated</mat-label><input matInput name=\"rules\" [(ngModel)]=\"rules\" [disabled]=\"all\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\"><mat-label>Severity up to</mat-label>\n <mat-select name=\"maximum\" [(ngModel)]=\"maximum\" [disabled]=\"all\"><mat-option value=\"\">Any</mat-option><mat-option value=\"Info\">Info</mat-option><mat-option value=\"Warning\">Warning</mat-option></mat-select>\n </mat-form-field>\n <mat-checkbox name=\"all\" [(ngModel)]=\"all\">Everything</mat-checkbox>\n <div class=\"jc-form-actions\">\n <button mat-flat-button type=\"submit\" data-testid=\"silence-save\" [disabled]=\"saving()\">{{ kind === 'Silence' ? 'Silence' : 'Schedule' }}</button>\n @if (saveError()) { <span class=\"jc-error\" data-testid=\"silence-error\">{{ saveError() }}</span> }\n </div>\n </form>\n </section>\n\n <table class=\"jc-table\" data-testid=\"silences\">\n <thead><tr><th>Why</th><th>Holds</th><th>When</th><th>Holding</th><th>By</th><th></th></tr></thead>\n <tbody>\n @for (s of silences.value()?.items ?? []; track s.id) {\n <tr [class.jc-muted]=\"s.cancelledAt\">\n <td><strong>{{ s.comment }}</strong><div class=\"jc-small\">@if (s.active) { <span class=\"jc-badge jc-health-degraded\">Active</span> } @else if (s.cancelledAt) { Cancelled by {{ s.cancelledBy }} } @else { {{ s.kind === 'Maintenance' ? 'Maintenance' : 'Silence' }} }</div></td>\n <td class=\"jc-small\">{{ scope(s) }}</td>\n <td class=\"jc-nowrap jc-small\">{{ s.startsAt | date: 'MMM d, HH:mm' }} to {{ s.endsAt | date: 'MMM d, HH:mm' }}@if (s.repeatWeekly) { <div>every week</div> }</td>\n <td>{{ s.held }}</td>\n <td>{{ s.createdBy }}</td>\n <td class=\"jc-row-actions\">@if (!s.cancelledAt && !over(s)) { <button mat-button type=\"button\" [attr.data-testid]=\"'silence-cancel-' + s.id\" (click)=\"cancel(s)\">End now</button> }</td>\n </tr>\n } @empty { <tr><td colspan=\"6\" class=\"jc-muted\">No silences: every matching alert notifies.</td></tr> }\n </tbody>\n </table>`,\n})\nexport class SilencesPage implements OnInit {\n private readonly api = inject(ApiService);\n /** Filled in from an alert's \"Silence this\". */\n readonly service = input<string>();\n readonly environment = input<string>();\n readonly host = input<string>();\n\n protected readonly showAll = signal(false);\n protected readonly saving = signal(false);\n protected readonly saveError = signal('');\n protected kind = 'Silence';\n protected comment = '';\n protected hours = 1;\n protected starts = '';\n protected weekly = false;\n protected serviceField = '';\n protected environmentField = '';\n protected hostField = '';\n protected rules = '';\n protected maximum = '';\n protected all = false;\n\n protected readonly silences = apiResource({ params: () => ({ all: this.showAll() }), stream: ({ params }) => this.api.silences({ all: params.all || null }) });\n protected readonly error = computed(() => problemText(this.silences.error()));\n\n ngOnInit() {\n this.serviceField = this.service() ?? '';\n this.environmentField = this.environment() ?? '';\n this.hostField = this.host() ?? '';\n }\n\n protected scope(s: Silence): string {\n if (s.allAlerts) return 'Everything';\n return [s.service, s.environment, s.host, s.ruleKeys.join(', '), s.maximumSeverity ? `up to ${s.maximumSeverity}` : ''].filter(Boolean).join(' · ');\n }\n\n protected over(s: Silence): boolean { return !s.repeatWeekly && new Date(s.endsAt).getTime() <= Date.now(); }\n\n protected create() {\n const start = this.kind === 'Maintenance' && this.starts ? new Date(this.starts) : new Date();\n const end = new Date(start.getTime() + (this.hours || 0) * 3_600_000);\n this.saving.set(true);\n this.saveError.set('');\n this.api.createSilence({\n kind: this.kind, comment: this.comment, startsAt: this.kind === 'Maintenance' ? start.toISOString() : undefined, endsAt: end.toISOString(), repeatWeekly: this.kind === 'Maintenance' && this.weekly,\n allAlerts: this.all, service: this.all ? undefined : this.serviceField, environment: this.all ? undefined : this.environmentField, host: this.all ? undefined : this.hostField,\n ruleKeys: this.all ? [] : this.rules.split(',').map(r => r.trim()).filter(Boolean), maximumSeverity: this.all ? undefined : this.maximum || undefined,\n }).subscribe({\n next: () => { this.saving.set(false); this.comment = ''; this.silences.reload(); },\n error: e => { this.saving.set(false); this.saveError.set((e as { status?: number }).status === 403 ? 'Silencing needs the operator or admin role.' : problemText(e)); },\n });\n }\n\n protected cancel(s: Silence) {\n this.api.cancelSilence(s.id).subscribe({\n next: () => this.silences.reload(),\n error: e => this.saveError.set((e as { status?: number }).status === 403 ? 'Ending a silence needs the operator or admin role.' : problemText(e)),\n });\n }\n}\n"],"mappings":"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"}
|