@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.
Files changed (28) hide show
  1. package/README.md +1 -0
  2. package/fesm2022/{juice-js-observability-alert.page-nEy5Gc6j.mjs → juice-js-observability-alert.page-DOAX9WWa.mjs} +154 -2
  3. package/fesm2022/juice-js-observability-alert.page-DOAX9WWa.mjs.map +1 -0
  4. package/fesm2022/juice-js-observability-incident.page-CCHEWXou.mjs +400 -0
  5. package/fesm2022/juice-js-observability-incident.page-CCHEWXou.mjs.map +1 -0
  6. package/fesm2022/juice-js-observability-incidents.page-CpNqYp2W.mjs +438 -0
  7. package/fesm2022/juice-js-observability-incidents.page-CpNqYp2W.mjs.map +1 -0
  8. package/fesm2022/juice-js-observability-load-runs.page-C2rk2bz9.mjs +349 -0
  9. package/fesm2022/juice-js-observability-load-runs.page-C2rk2bz9.mjs.map +1 -0
  10. package/fesm2022/juice-js-observability-notifications.page-BDqb10C6.mjs +725 -0
  11. package/fesm2022/juice-js-observability-notifications.page-BDqb10C6.mjs.map +1 -0
  12. package/fesm2022/juice-js-observability-rules.page-Df2uDVwz.mjs +580 -0
  13. package/fesm2022/juice-js-observability-rules.page-Df2uDVwz.mjs.map +1 -0
  14. package/fesm2022/{juice-js-observability-service.page-rGAvUK_k.mjs → juice-js-observability-service.page-BFXUe4mX.mjs} +38 -4
  15. package/fesm2022/juice-js-observability-service.page-BFXUe4mX.mjs.map +1 -0
  16. package/fesm2022/juice-js-observability-silences.page-Cg53dJAy.mjs +523 -0
  17. package/fesm2022/juice-js-observability-silences.page-Cg53dJAy.mjs.map +1 -0
  18. package/fesm2022/juice-js-observability-timeline.page-CtU2EoDN.mjs +213 -0
  19. package/fesm2022/juice-js-observability-timeline.page-CtU2EoDN.mjs.map +1 -0
  20. package/fesm2022/juice-js-observability.mjs +141 -3
  21. package/fesm2022/juice-js-observability.mjs.map +1 -1
  22. package/package.json +1 -1
  23. package/styles/_observability.scss +6 -0
  24. package/types/juice-js-observability.d.ts +242 -0
  25. package/fesm2022/juice-js-observability-alert.page-nEy5Gc6j.mjs.map +0 -1
  26. package/fesm2022/juice-js-observability-rules.page-Crb07uWm.mjs +0 -175
  27. package/fesm2022/juice-js-observability-rules.page-Crb07uWm.mjs.map +0 -1
  28. package/fesm2022/juice-js-observability-service.page-rGAvUK_k.mjs.map +0 -1
@@ -0,0 +1,438 @@
1
+ import { ApiService, ContextService, LinkPipe, OBSERVABILITY_OPTIONS } 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 { Router, RouterLink } from "@angular/router";
6
+ import { DatePipe } from "@angular/common";
7
+ import * as i1 from "@angular/forms";
8
+ import { FormsModule } from "@angular/forms";
9
+ import * as i2 from "@angular/material/button";
10
+ import { MatButtonModule } from "@angular/material/button";
11
+ import { MatFormFieldModule } from "@angular/material/form-field";
12
+ import * as i4 from "@angular/material/input";
13
+ import { MatInputModule } from "@angular/material/input";
14
+ import * as i5 from "@angular/material/progress-bar";
15
+ import { MatProgressBarModule } from "@angular/material/progress-bar";
16
+ import * as i3 from "@angular/material/select";
17
+ import { MatSelectModule } from "@angular/material/select";
18
+ var IncidentsPage = class IncidentsPage {
19
+ ctx = inject(ContextService);
20
+ api = inject(ApiService);
21
+ router = inject(Router);
22
+ base = inject(OBSERVABILITY_OPTIONS).basePath;
23
+ alert = input(...ngDevMode ? [void 0, { debugName: "alert" }] : /* istanbul ignore next */ []);
24
+ title = input(...ngDevMode ? [void 0, { debugName: "title" }] : /* istanbul ignore next */ []);
25
+ state = signal("", ...ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []);
26
+ saving = signal(false, ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
27
+ saveError = signal("", ...ngDevMode ? [{ debugName: "saveError" }] : /* istanbul ignore next */ []);
28
+ titleField = "";
29
+ severity = "";
30
+ note = "";
31
+ incidents = apiResource({
32
+ params: () => this.state(),
33
+ stream: ({ params }) => this.api.incidents({ state: params })
34
+ });
35
+ error = computed(() => problemText(this.incidents.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
36
+ ngOnInit() {
37
+ this.titleField = this.title() ?? "";
38
+ }
39
+ create() {
40
+ this.saving.set(true);
41
+ this.saveError.set("");
42
+ const alert = this.alert();
43
+ this.api.createIncident({
44
+ title: this.titleField,
45
+ severity: this.severity || void 0,
46
+ note: this.note,
47
+ alertIds: alert ? [alert] : []
48
+ }).subscribe({
49
+ next: (incident) => {
50
+ this.saving.set(false);
51
+ const base = (this.base ?? "/").replace(/\/$/, "");
52
+ this.router.navigate([`${base}/incidents/${incident.id}`], { queryParams: this.ctx.keep({
53
+ alert: null,
54
+ title: null
55
+ }) });
56
+ },
57
+ error: (e) => {
58
+ this.saving.set(false);
59
+ this.saveError.set(e.status === 403 ? "Opening an incident needs the operator or admin role." : problemText(e));
60
+ }
61
+ });
62
+ }
63
+ static ɵfac = i0.ɵɵngDeclareFactory({
64
+ minVersion: "12.0.0",
65
+ version: "22.2.4",
66
+ ngImport: i0,
67
+ type: IncidentsPage,
68
+ deps: [],
69
+ target: i0.ɵɵFactoryTarget.Component
70
+ });
71
+ static ɵcmp = i0.ɵɵngDeclareComponent({
72
+ minVersion: "17.0.0",
73
+ version: "22.2.4",
74
+ type: IncidentsPage,
75
+ isStandalone: true,
76
+ selector: "ng-component",
77
+ inputs: {
78
+ alert: {
79
+ classPropertyName: "alert",
80
+ publicName: "alert",
81
+ isSignal: true,
82
+ isRequired: false,
83
+ transformFunction: null
84
+ },
85
+ title: {
86
+ classPropertyName: "title",
87
+ publicName: "title",
88
+ isSignal: true,
89
+ isRequired: false,
90
+ transformFunction: null
91
+ }
92
+ },
93
+ ngImport: i0,
94
+ template: `
95
+ <jc-context-bar>
96
+ <h1>Incidents</h1>
97
+ <span class="jc-spacer"></span>
98
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>State</mat-label>
99
+ <mat-select [ngModel]="state()" (ngModelChange)="state.set($event)" name="state">
100
+ <mat-option value="">Any</mat-option><mat-option value="open">Open</mat-option><mat-option value="investigating">Investigating</mat-option><mat-option value="mitigated">Mitigated</mat-option><mat-option value="resolved">Resolved</mat-option>
101
+ </mat-select>
102
+ </mat-form-field>
103
+ </jc-context-bar>
104
+ @if (incidents.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
105
+ @if (incidents.error()) { <p class="jc-error">{{ error() }}</p> }
106
+ <section class="jc-card">
107
+ <h2>New incident</h2>
108
+ <form class="jc-form" data-testid="incident-form" (ngSubmit)="create()">
109
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide"><mat-label>Title</mat-label><input matInput name="titleField" data-testid="incident-title" [(ngModel)]="titleField" maxlength="200"></mat-form-field>
110
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>Severity</mat-label>
111
+ <mat-select name="severity" [(ngModel)]="severity"><mat-option value="">As its worst alert</mat-option><mat-option value="Info">Info</mat-option><mat-option value="Warning">Warning</mat-option><mat-option value="Critical">Critical</mat-option></mat-select>
112
+ </mat-form-field>
113
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide"><mat-label>Note</mat-label><input matInput name="note" [(ngModel)]="note" maxlength="2000"></mat-form-field>
114
+ @if (alert()) { <span class="jc-muted jc-small" data-testid="incident-with-alert">With the alert you came from.</span> }
115
+ <div class="jc-form-actions">
116
+ <button mat-flat-button type="submit" data-testid="incident-save" [disabled]="saving()">Open incident</button>
117
+ @if (saveError()) { <span class="jc-error" data-testid="incident-error">{{ saveError() }}</span> }
118
+ </div>
119
+ </form>
120
+ </section>
121
+ <table class="jc-table" data-testid="incidents">
122
+ <thead><tr><th>Incident</th><th>Severity</th><th>State</th><th>Opened</th><th>By</th><th>Last change</th></tr></thead>
123
+ <tbody>
124
+ @for (i of incidents.value()?.items ?? []; track i.id) {
125
+ <tr>
126
+ <td><a [routerLink]="['incidents', i.id] | jcLink" [queryParams]="ctx.keep({ alert: null, title: null })">{{ i.title }}</a></td>
127
+ <td>{{ i.severity }}</td>
128
+ <td>{{ i.state }}</td>
129
+ <td class="jc-nowrap">{{ i.createdAt | date: 'MMM d, HH:mm' }}</td>
130
+ <td>{{ i.createdBy }}</td>
131
+ <td class="jc-nowrap">{{ i.updatedAt | date: 'MMM d, HH:mm' }}</td>
132
+ </tr>
133
+ } @empty { <tr><td colspan="6" class="jc-muted">No incidents.</td></tr> }
134
+ </tbody>
135
+ </table>`,
136
+ isInline: true,
137
+ dependencies: [
138
+ {
139
+ kind: "directive",
140
+ type: RouterLink,
141
+ selector: "[routerLink]",
142
+ inputs: [
143
+ "target",
144
+ "queryParams",
145
+ "fragment",
146
+ "queryParamsHandling",
147
+ "state",
148
+ "info",
149
+ "relativeTo",
150
+ "preserveFragment",
151
+ "skipLocationChange",
152
+ "replaceUrl",
153
+ "browserUrl",
154
+ "routerLink"
155
+ ]
156
+ },
157
+ {
158
+ kind: "ngmodule",
159
+ type: FormsModule
160
+ },
161
+ {
162
+ kind: "directive",
163
+ type: i1.ɵNgNoValidate,
164
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
165
+ },
166
+ {
167
+ kind: "directive",
168
+ type: i1.DefaultValueAccessor,
169
+ 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]"
170
+ },
171
+ {
172
+ kind: "directive",
173
+ type: i1.NgControlStatus,
174
+ selector: "[formControlName],[ngModel],[formControl]"
175
+ },
176
+ {
177
+ kind: "directive",
178
+ type: i1.NgControlStatusGroup,
179
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
180
+ },
181
+ {
182
+ kind: "directive",
183
+ type: i1.MaxLengthValidator,
184
+ selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]",
185
+ inputs: ["maxlength"]
186
+ },
187
+ {
188
+ kind: "directive",
189
+ type: i1.NgModel,
190
+ selector: "[ngModel]:not([formControlName]):not([formControl])",
191
+ inputs: [
192
+ "name",
193
+ "disabled",
194
+ "ngModel",
195
+ "ngModelOptions"
196
+ ],
197
+ outputs: ["ngModelChange"],
198
+ exportAs: ["ngModel"]
199
+ },
200
+ {
201
+ kind: "directive",
202
+ type: i1.NgForm,
203
+ selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]",
204
+ inputs: ["ngFormOptions"],
205
+ outputs: ["ngSubmit"],
206
+ exportAs: ["ngForm"]
207
+ },
208
+ {
209
+ kind: "ngmodule",
210
+ type: MatButtonModule
211
+ },
212
+ {
213
+ kind: "component",
214
+ type: i2.MatButton,
215
+ 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] ",
216
+ inputs: ["matButton"],
217
+ exportAs: ["matButton", "matAnchor"]
218
+ },
219
+ {
220
+ kind: "ngmodule",
221
+ type: MatFormFieldModule
222
+ },
223
+ {
224
+ kind: "component",
225
+ type: i3.MatFormField,
226
+ selector: "mat-form-field",
227
+ inputs: [
228
+ "hideRequiredMarker",
229
+ "color",
230
+ "floatLabel",
231
+ "appearance",
232
+ "subscriptSizing",
233
+ "hintLabel"
234
+ ],
235
+ exportAs: ["matFormField"]
236
+ },
237
+ {
238
+ kind: "directive",
239
+ type: i3.MatLabel,
240
+ selector: "mat-label"
241
+ },
242
+ {
243
+ kind: "ngmodule",
244
+ type: MatInputModule
245
+ },
246
+ {
247
+ kind: "directive",
248
+ type: i4.MatInput,
249
+ selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",
250
+ inputs: [
251
+ "disabled",
252
+ "id",
253
+ "placeholder",
254
+ "name",
255
+ "required",
256
+ "type",
257
+ "errorStateMatcher",
258
+ "aria-describedby",
259
+ "value",
260
+ "readonly",
261
+ "disabledInteractive"
262
+ ],
263
+ exportAs: ["matInput"]
264
+ },
265
+ {
266
+ kind: "ngmodule",
267
+ type: MatProgressBarModule
268
+ },
269
+ {
270
+ kind: "component",
271
+ type: i5.MatProgressBar,
272
+ selector: "mat-progress-bar",
273
+ inputs: [
274
+ "color",
275
+ "value",
276
+ "bufferValue",
277
+ "mode"
278
+ ],
279
+ outputs: ["animationEnd"],
280
+ exportAs: ["matProgressBar"]
281
+ },
282
+ {
283
+ kind: "ngmodule",
284
+ type: MatSelectModule
285
+ },
286
+ {
287
+ kind: "component",
288
+ type: i3.MatSelect,
289
+ selector: "mat-select",
290
+ inputs: [
291
+ "aria-describedby",
292
+ "panelClass",
293
+ "disabled",
294
+ "disableRipple",
295
+ "tabIndex",
296
+ "hideSingleSelectionIndicator",
297
+ "placeholder",
298
+ "required",
299
+ "multiple",
300
+ "disableOptionCentering",
301
+ "compareWith",
302
+ "value",
303
+ "aria-label",
304
+ "aria-labelledby",
305
+ "errorStateMatcher",
306
+ "typeaheadDebounceInterval",
307
+ "sortComparator",
308
+ "id",
309
+ "panelWidth",
310
+ "canSelectNullableOptions"
311
+ ],
312
+ outputs: [
313
+ "openedChange",
314
+ "opened",
315
+ "closed",
316
+ "selectionChange",
317
+ "valueChange"
318
+ ],
319
+ exportAs: ["matSelect"]
320
+ },
321
+ {
322
+ kind: "component",
323
+ type: i3.MatOption,
324
+ selector: "mat-option",
325
+ inputs: [
326
+ "value",
327
+ "id",
328
+ "disabled"
329
+ ],
330
+ outputs: ["onSelectionChange"],
331
+ exportAs: ["matOption"]
332
+ },
333
+ {
334
+ kind: "component",
335
+ type: ContextBar,
336
+ selector: "jc-context-bar"
337
+ },
338
+ {
339
+ kind: "pipe",
340
+ type: LinkPipe,
341
+ name: "jcLink"
342
+ },
343
+ {
344
+ kind: "pipe",
345
+ type: DatePipe,
346
+ name: "date"
347
+ }
348
+ ],
349
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
350
+ });
351
+ };
352
+ i0.ɵɵngDeclareClassMetadata({
353
+ minVersion: "12.0.0",
354
+ version: "22.2.4",
355
+ ngImport: i0,
356
+ type: IncidentsPage,
357
+ decorators: [{
358
+ type: Component,
359
+ args: [{
360
+ changeDetection: ChangeDetectionStrategy.OnPush,
361
+ imports: [
362
+ LinkPipe,
363
+ RouterLink,
364
+ DatePipe,
365
+ FormsModule,
366
+ MatButtonModule,
367
+ MatFormFieldModule,
368
+ MatInputModule,
369
+ MatProgressBarModule,
370
+ MatSelectModule,
371
+ ContextBar
372
+ ],
373
+ template: `
374
+ <jc-context-bar>
375
+ <h1>Incidents</h1>
376
+ <span class="jc-spacer"></span>
377
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>State</mat-label>
378
+ <mat-select [ngModel]="state()" (ngModelChange)="state.set($event)" name="state">
379
+ <mat-option value="">Any</mat-option><mat-option value="open">Open</mat-option><mat-option value="investigating">Investigating</mat-option><mat-option value="mitigated">Mitigated</mat-option><mat-option value="resolved">Resolved</mat-option>
380
+ </mat-select>
381
+ </mat-form-field>
382
+ </jc-context-bar>
383
+ @if (incidents.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
384
+ @if (incidents.error()) { <p class="jc-error">{{ error() }}</p> }
385
+ <section class="jc-card">
386
+ <h2>New incident</h2>
387
+ <form class="jc-form" data-testid="incident-form" (ngSubmit)="create()">
388
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide"><mat-label>Title</mat-label><input matInput name="titleField" data-testid="incident-title" [(ngModel)]="titleField" maxlength="200"></mat-form-field>
389
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-sm"><mat-label>Severity</mat-label>
390
+ <mat-select name="severity" [(ngModel)]="severity"><mat-option value="">As its worst alert</mat-option><mat-option value="Info">Info</mat-option><mat-option value="Warning">Warning</mat-option><mat-option value="Critical">Critical</mat-option></mat-select>
391
+ </mat-form-field>
392
+ <mat-form-field subscriptSizing="dynamic" class="jc-field-wide"><mat-label>Note</mat-label><input matInput name="note" [(ngModel)]="note" maxlength="2000"></mat-form-field>
393
+ @if (alert()) { <span class="jc-muted jc-small" data-testid="incident-with-alert">With the alert you came from.</span> }
394
+ <div class="jc-form-actions">
395
+ <button mat-flat-button type="submit" data-testid="incident-save" [disabled]="saving()">Open incident</button>
396
+ @if (saveError()) { <span class="jc-error" data-testid="incident-error">{{ saveError() }}</span> }
397
+ </div>
398
+ </form>
399
+ </section>
400
+ <table class="jc-table" data-testid="incidents">
401
+ <thead><tr><th>Incident</th><th>Severity</th><th>State</th><th>Opened</th><th>By</th><th>Last change</th></tr></thead>
402
+ <tbody>
403
+ @for (i of incidents.value()?.items ?? []; track i.id) {
404
+ <tr>
405
+ <td><a [routerLink]="['incidents', i.id] | jcLink" [queryParams]="ctx.keep({ alert: null, title: null })">{{ i.title }}</a></td>
406
+ <td>{{ i.severity }}</td>
407
+ <td>{{ i.state }}</td>
408
+ <td class="jc-nowrap">{{ i.createdAt | date: 'MMM d, HH:mm' }}</td>
409
+ <td>{{ i.createdBy }}</td>
410
+ <td class="jc-nowrap">{{ i.updatedAt | date: 'MMM d, HH:mm' }}</td>
411
+ </tr>
412
+ } @empty { <tr><td colspan="6" class="jc-muted">No incidents.</td></tr> }
413
+ </tbody>
414
+ </table>`
415
+ }]
416
+ }],
417
+ propDecorators: {
418
+ alert: [{
419
+ type: i0.Input,
420
+ args: [{
421
+ isSignal: true,
422
+ alias: "alert",
423
+ required: false
424
+ }]
425
+ }],
426
+ title: [{
427
+ type: i0.Input,
428
+ args: [{
429
+ isSignal: true,
430
+ alias: "title",
431
+ required: false
432
+ }]
433
+ }]
434
+ }
435
+ });
436
+ export { IncidentsPage };
437
+
438
+ //# sourceMappingURL=juice-js-observability-incidents.page-CpNqYp2W.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"juice-js-observability-incidents.page-CpNqYp2W.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/incidents.page.ts"],"sourcesContent":["import { LinkPipe } from '../core/options';\nimport { 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 { 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 { Router, RouterLink } from '@angular/router';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { OBSERVABILITY_OPTIONS } from '../core/options';\nimport { apiResource } from '../shared/resource';\nimport { problemText } from '../shared/telemetry';\nimport { ContextBar } from '../shared/ui';\n\n/** Operational work grouped across alerts: what's being handled, by state, and starting a new one, usually from an alert. */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LinkPipe, RouterLink, DatePipe, FormsModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatProgressBarModule, MatSelectModule, ContextBar],\n template: `\n <jc-context-bar>\n <h1>Incidents</h1>\n <span class=\"jc-spacer\"></span>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\"><mat-label>State</mat-label>\n <mat-select [ngModel]=\"state()\" (ngModelChange)=\"state.set($event)\" name=\"state\">\n <mat-option value=\"\">Any</mat-option><mat-option value=\"open\">Open</mat-option><mat-option value=\"investigating\">Investigating</mat-option><mat-option value=\"mitigated\">Mitigated</mat-option><mat-option value=\"resolved\">Resolved</mat-option>\n </mat-select>\n </mat-form-field>\n </jc-context-bar>\n @if (incidents.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (incidents.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n <section class=\"jc-card\">\n <h2>New incident</h2>\n <form class=\"jc-form\" data-testid=\"incident-form\" (ngSubmit)=\"create()\">\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\"><mat-label>Title</mat-label><input matInput name=\"titleField\" data-testid=\"incident-title\" [(ngModel)]=\"titleField\" maxlength=\"200\"></mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-sm\"><mat-label>Severity</mat-label>\n <mat-select name=\"severity\" [(ngModel)]=\"severity\"><mat-option value=\"\">As its worst alert</mat-option><mat-option value=\"Info\">Info</mat-option><mat-option value=\"Warning\">Warning</mat-option><mat-option value=\"Critical\">Critical</mat-option></mat-select>\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"jc-field-wide\"><mat-label>Note</mat-label><input matInput name=\"note\" [(ngModel)]=\"note\" maxlength=\"2000\"></mat-form-field>\n @if (alert()) { <span class=\"jc-muted jc-small\" data-testid=\"incident-with-alert\">With the alert you came from.</span> }\n <div class=\"jc-form-actions\">\n <button mat-flat-button type=\"submit\" data-testid=\"incident-save\" [disabled]=\"saving()\">Open incident</button>\n @if (saveError()) { <span class=\"jc-error\" data-testid=\"incident-error\">{{ saveError() }}</span> }\n </div>\n </form>\n </section>\n <table class=\"jc-table\" data-testid=\"incidents\">\n <thead><tr><th>Incident</th><th>Severity</th><th>State</th><th>Opened</th><th>By</th><th>Last change</th></tr></thead>\n <tbody>\n @for (i of incidents.value()?.items ?? []; track i.id) {\n <tr>\n <td><a [routerLink]=\"['incidents', i.id] | jcLink\" [queryParams]=\"ctx.keep({ alert: null, title: null })\">{{ i.title }}</a></td>\n <td>{{ i.severity }}</td>\n <td>{{ i.state }}</td>\n <td class=\"jc-nowrap\">{{ i.createdAt | date: 'MMM d, HH:mm' }}</td>\n <td>{{ i.createdBy }}</td>\n <td class=\"jc-nowrap\">{{ i.updatedAt | date: 'MMM d, HH:mm' }}</td>\n </tr>\n } @empty { <tr><td colspan=\"6\" class=\"jc-muted\">No incidents.</td></tr> }\n </tbody>\n </table>`,\n})\nexport class IncidentsPage implements OnInit {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n private readonly base = inject(OBSERVABILITY_OPTIONS).basePath;\n /** From an alert's \"Open an incident\". */\n readonly alert = input<string>();\n readonly title = input<string>();\n protected readonly state = signal('');\n protected readonly saving = signal(false);\n protected readonly saveError = signal('');\n protected titleField = '';\n protected severity = '';\n protected note = '';\n\n protected readonly incidents = apiResource({ params: () => this.state(), stream: ({ params }) => this.api.incidents({ state: params }) });\n protected readonly error = computed(() => problemText(this.incidents.error()));\n\n ngOnInit() { this.titleField = this.title() ?? ''; }\n\n protected create() {\n this.saving.set(true);\n this.saveError.set('');\n const alert = this.alert();\n this.api.createIncident({ title: this.titleField, severity: this.severity || undefined, note: this.note, alertIds: alert ? [alert] : [] }).subscribe({\n next: incident => {\n this.saving.set(false);\n const base = (this.base ?? '/').replace(/\\/$/, '');\n void this.router.navigate([`${base}/incidents/${incident.id}`], { queryParams: this.ctx.keep({ alert: null, title: null }) });\n },\n error: e => { this.saving.set(false); this.saveError.set((e as { status?: number }).status === 403 ? 'Opening an incident needs the operator or admin role.' : problemText(e)); },\n });\n }\n}\n"],"mappings":"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"}