@oneuptime/common 11.3.9 → 11.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/MutableMetric.ts +333 -0
- package/Server/Services/AlertService.ts +167 -206
- package/Server/Services/AlertStateTimelineService.ts +121 -1
- package/Server/Services/AnalyticsDatabaseService.ts +19 -23
- package/Server/Services/IncidentService.ts +179 -252
- package/Server/Services/IncidentStateTimelineService.ts +120 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MetricAggregationService.ts +73 -3
- package/Server/Services/MetricService.ts +228 -0
- package/Server/Services/MutableMetricService.ts +275 -0
- package/Server/Services/TelemetryAttributeService.ts +183 -1
- package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +2 -0
- package/Server/Utils/AnalyticsDatabase/Statement.ts +2 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +145 -0
- package/Tests/Types/Monitor/MonitorStepEntityScope.test.ts +0 -56
- package/Types/AnalyticsDatabase/AnalyticsTableEngine.ts +1 -0
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +1 -0
- package/Types/Dashboard/DashboardTemplates.ts +267 -7
- package/Types/Monitor/MonitorStepMetricMonitor.ts +0 -27
- package/build/dist/Models/AnalyticsModels/MutableMetric.js +275 -0
- package/build/dist/Models/AnalyticsModels/MutableMetric.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +133 -170
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +97 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +16 -15
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +142 -207
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +97 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +49 -3
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +155 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MutableMetricService.js +182 -0
- package/build/dist/Server/Services/MutableMetricService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js +131 -6
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +2 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +2 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableEngine.js +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableEngine.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +252 -6
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +0 -13
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,8 @@ import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
|
|
|
13
13
|
import AggregationType from "../../../Types/BaseDatabase/AggregationType";
|
|
14
14
|
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
15
15
|
import GenericObject from "../../../Types/GenericObject";
|
|
16
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
17
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
16
18
|
import {
|
|
17
19
|
describe,
|
|
18
20
|
expect,
|
|
@@ -379,6 +381,149 @@ describe("AnalyticsDatabaseService", () => {
|
|
|
379
381
|
* ids per call. Must be strictly additive: option-less calls keep the
|
|
380
382
|
* exact pre-existing exec/query payload.
|
|
381
383
|
*/
|
|
384
|
+
/*
|
|
385
|
+
* Required-field validation. A required column is satisfied by any present
|
|
386
|
+
* value (including the boolean `false`); only null/undefined triggers a
|
|
387
|
+
* default-fill or a "<field> is required" error. This is regression coverage
|
|
388
|
+
* for the boolean branch that previously rejected every required Boolean
|
|
389
|
+
* column (both true and false), which made models like MutableMetric — whose
|
|
390
|
+
* `isDeleted` tombstone flag is a required Boolean — impossible to insert.
|
|
391
|
+
*/
|
|
392
|
+
describe("checkRequiredFields", () => {
|
|
393
|
+
class RequiredFieldsModel extends AnalyticsBaseModel {
|
|
394
|
+
public constructor() {
|
|
395
|
+
super({
|
|
396
|
+
tableName: "<required-fields-table>",
|
|
397
|
+
singularName: "<singular-name>",
|
|
398
|
+
pluralName: "<plural-name>",
|
|
399
|
+
tableColumns: [
|
|
400
|
+
new AnalyticsTableColumn({
|
|
401
|
+
key: "requiredText",
|
|
402
|
+
title: "<title>",
|
|
403
|
+
description: "<description>",
|
|
404
|
+
required: true,
|
|
405
|
+
type: TableColumnType.Text,
|
|
406
|
+
}),
|
|
407
|
+
new AnalyticsTableColumn({
|
|
408
|
+
key: "requiredBoolWithDefault",
|
|
409
|
+
title: "<title>",
|
|
410
|
+
description: "<description>",
|
|
411
|
+
required: true,
|
|
412
|
+
type: TableColumnType.Boolean,
|
|
413
|
+
defaultValue: false,
|
|
414
|
+
}),
|
|
415
|
+
],
|
|
416
|
+
crudApiPath: new Route("route"),
|
|
417
|
+
primaryKeys: ["requiredText"],
|
|
418
|
+
sortKeys: ["requiredText"],
|
|
419
|
+
partitionKey: "requiredText",
|
|
420
|
+
tableEngine: AnalyticsTableEngine.MergeTree,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/*
|
|
425
|
+
* checkRequiredFields reads columns via property accessors, so the model
|
|
426
|
+
* must expose getters/setters that delegate to get/setColumnValue (real
|
|
427
|
+
* analytics models do the same for every column).
|
|
428
|
+
*/
|
|
429
|
+
public get requiredText(): string | undefined {
|
|
430
|
+
return this.getColumnValue("requiredText") as string | undefined;
|
|
431
|
+
}
|
|
432
|
+
public set requiredText(v: string | undefined) {
|
|
433
|
+
this.setColumnValue("requiredText", v);
|
|
434
|
+
}
|
|
435
|
+
public get requiredBoolWithDefault(): boolean | undefined {
|
|
436
|
+
return this.getColumnValue("requiredBoolWithDefault") as
|
|
437
|
+
| boolean
|
|
438
|
+
| undefined;
|
|
439
|
+
}
|
|
440
|
+
public set requiredBoolWithDefault(v: boolean | undefined) {
|
|
441
|
+
this.setColumnValue("requiredBoolWithDefault", v);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let requiredFieldsService: AnalyticsDatabaseService<RequiredFieldsModel>;
|
|
446
|
+
|
|
447
|
+
beforeEach(() => {
|
|
448
|
+
requiredFieldsService = new AnalyticsDatabaseService({
|
|
449
|
+
modelType: RequiredFieldsModel,
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Build a model with every required column satisfied except as overridden.
|
|
454
|
+
const buildModel: (
|
|
455
|
+
overrides?: (model: RequiredFieldsModel) => void,
|
|
456
|
+
) => RequiredFieldsModel = (
|
|
457
|
+
overrides?: (model: RequiredFieldsModel) => void,
|
|
458
|
+
): RequiredFieldsModel => {
|
|
459
|
+
const model: RequiredFieldsModel = new RequiredFieldsModel();
|
|
460
|
+
// _id and createdAt are required on the base model and carry no default.
|
|
461
|
+
model.setColumnValue("_id", ObjectID.generate());
|
|
462
|
+
model.setColumnValue("createdAt", OneUptimeDate.getCurrentDate());
|
|
463
|
+
model.setColumnValue("requiredText", "value");
|
|
464
|
+
if (overrides) {
|
|
465
|
+
overrides(model);
|
|
466
|
+
}
|
|
467
|
+
return model;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const check: (model: RequiredFieldsModel) => RequiredFieldsModel = (
|
|
471
|
+
model: RequiredFieldsModel,
|
|
472
|
+
): RequiredFieldsModel => {
|
|
473
|
+
return (requiredFieldsService as any).checkRequiredFields(model);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
test("accepts a required boolean set to false (regression)", () => {
|
|
477
|
+
const model: RequiredFieldsModel = buildModel(
|
|
478
|
+
(m: RequiredFieldsModel): void => {
|
|
479
|
+
m.setColumnValue("requiredBoolWithDefault", false);
|
|
480
|
+
},
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
expect(() => {
|
|
484
|
+
return check(model);
|
|
485
|
+
}).not.toThrow();
|
|
486
|
+
expect(model.getColumnValue("requiredBoolWithDefault")).toBe(false);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("accepts a required boolean set to true", () => {
|
|
490
|
+
const model: RequiredFieldsModel = buildModel(
|
|
491
|
+
(m: RequiredFieldsModel): void => {
|
|
492
|
+
m.setColumnValue("requiredBoolWithDefault", true);
|
|
493
|
+
},
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
expect(() => {
|
|
497
|
+
return check(model);
|
|
498
|
+
}).not.toThrow();
|
|
499
|
+
expect(model.getColumnValue("requiredBoolWithDefault")).toBe(true);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("fills the default when a required boolean is unset", () => {
|
|
503
|
+
const model: RequiredFieldsModel = buildModel();
|
|
504
|
+
|
|
505
|
+
expect(() => {
|
|
506
|
+
return check(model);
|
|
507
|
+
}).not.toThrow();
|
|
508
|
+
expect(model.getColumnValue("requiredBoolWithDefault")).toBe(false);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test("throws when a required column without a default is missing", () => {
|
|
512
|
+
const model: RequiredFieldsModel = new RequiredFieldsModel();
|
|
513
|
+
model.setColumnValue("_id", ObjectID.generate());
|
|
514
|
+
model.setColumnValue("createdAt", OneUptimeDate.getCurrentDate());
|
|
515
|
+
model.setColumnValue("requiredBoolWithDefault", false);
|
|
516
|
+
// requiredText (required, no default) intentionally left unset.
|
|
517
|
+
|
|
518
|
+
expect(() => {
|
|
519
|
+
return check(model);
|
|
520
|
+
}).toThrow(BadDataException);
|
|
521
|
+
expect(() => {
|
|
522
|
+
return check(model);
|
|
523
|
+
}).toThrow("requiredText is required");
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
382
527
|
describe("execute / executeQuery per-call options", () => {
|
|
383
528
|
let exec: ReturnType<typeof jest.fn>;
|
|
384
529
|
let query: ReturnType<typeof jest.fn>;
|
|
@@ -4,7 +4,6 @@ import Log from "../../../Models/AnalyticsModels/Log";
|
|
|
4
4
|
import Span from "../../../Models/AnalyticsModels/Span";
|
|
5
5
|
import ExceptionInstance from "../../../Models/AnalyticsModels/ExceptionInstance";
|
|
6
6
|
import Profile from "../../../Models/AnalyticsModels/Profile";
|
|
7
|
-
import Metric from "../../../Models/AnalyticsModels/Metric";
|
|
8
7
|
import MonitorStepLogMonitor, {
|
|
9
8
|
MonitorStepLogMonitorUtil,
|
|
10
9
|
} from "../../../Types/Monitor/MonitorStepLogMonitor";
|
|
@@ -17,9 +16,6 @@ import MonitorStepExceptionMonitor, {
|
|
|
17
16
|
import MonitorStepProfileMonitor, {
|
|
18
17
|
MonitorStepProfileMonitorUtil,
|
|
19
18
|
} from "../../../Types/Monitor/MonitorStepProfileMonitor";
|
|
20
|
-
import MonitorStepMetricMonitor, {
|
|
21
|
-
MonitorStepMetricMonitorUtil,
|
|
22
|
-
} from "../../../Types/Monitor/MonitorStepMetricMonitor";
|
|
23
19
|
import { JSONObject } from "../../../Types/JSON";
|
|
24
20
|
|
|
25
21
|
/*
|
|
@@ -221,55 +217,3 @@ describe("MonitorStepProfileMonitorUtil.toQuery entity scoping", () => {
|
|
|
221
217
|
expectIncludes(query.entityKeys, TWO_KEYS);
|
|
222
218
|
});
|
|
223
219
|
});
|
|
224
|
-
|
|
225
|
-
describe("MonitorStepMetricMonitorUtil.applyEntityScopeToQuery", () => {
|
|
226
|
-
test("is a no-op when entityKeys is undefined (pre-existing saved monitors)", () => {
|
|
227
|
-
const step: MonitorStepMetricMonitor =
|
|
228
|
-
MonitorStepMetricMonitorUtil.getDefault();
|
|
229
|
-
delete step.entityKeys;
|
|
230
|
-
|
|
231
|
-
const query: Query<Metric> = {};
|
|
232
|
-
MonitorStepMetricMonitorUtil.applyEntityScopeToQuery(query, step);
|
|
233
|
-
|
|
234
|
-
expect(query.entityKeys).toBeUndefined();
|
|
235
|
-
expect(Object.keys(query)).toHaveLength(0);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("is a no-op when entityKeys is an empty array", () => {
|
|
239
|
-
const step: MonitorStepMetricMonitor =
|
|
240
|
-
MonitorStepMetricMonitorUtil.getDefault();
|
|
241
|
-
step.entityKeys = [];
|
|
242
|
-
|
|
243
|
-
const query: Query<Metric> = {};
|
|
244
|
-
MonitorStepMetricMonitorUtil.applyEntityScopeToQuery(query, step);
|
|
245
|
-
|
|
246
|
-
expect(query.entityKeys).toBeUndefined();
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test("stamps an Includes on entityKeys for two keys and preserves other predicates", () => {
|
|
250
|
-
const step: MonitorStepMetricMonitor =
|
|
251
|
-
MonitorStepMetricMonitorUtil.getDefault();
|
|
252
|
-
step.entityKeys = TWO_KEYS;
|
|
253
|
-
|
|
254
|
-
const query: Query<Metric> = { name: "cpu.usage" };
|
|
255
|
-
const returned: Query<Metric> =
|
|
256
|
-
MonitorStepMetricMonitorUtil.applyEntityScopeToQuery(query, step);
|
|
257
|
-
|
|
258
|
-
expect(returned).toBe(query);
|
|
259
|
-
expect(query.name).toBe("cpu.usage");
|
|
260
|
-
expectIncludes(query.entityKeys, TWO_KEYS);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test("fromJSON preserves entityKeys (pass-through serialization)", () => {
|
|
264
|
-
const step: MonitorStepMetricMonitor =
|
|
265
|
-
MonitorStepMetricMonitorUtil.getDefault();
|
|
266
|
-
step.entityKeys = TWO_KEYS;
|
|
267
|
-
|
|
268
|
-
const roundTripped: MonitorStepMetricMonitor =
|
|
269
|
-
MonitorStepMetricMonitorUtil.fromJSON(
|
|
270
|
-
MonitorStepMetricMonitorUtil.toJSON(step),
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
expect(roundTripped.entityKeys).toEqual(TWO_KEYS);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
@@ -9,6 +9,7 @@ import DashboardVariable, { DashboardVariableType } from "./DashboardVariable";
|
|
|
9
9
|
import IconProp from "../Icon/IconProp";
|
|
10
10
|
import MetricsAggregationType from "../Metrics/MetricsAggregationType";
|
|
11
11
|
import IncidentMetricType from "../Incident/IncidentMetricType";
|
|
12
|
+
import AlertMetricType from "../Alerts/AlertMetricType";
|
|
12
13
|
import MonitorMetricType from "../Monitor/MonitorMetricType";
|
|
13
14
|
import MetricDashboardMetricType from "../Metrics/MetricDashboardMetricType";
|
|
14
15
|
import { DashboardValueTrendDirection } from "./DashboardComponents/DashboardValueComponent";
|
|
@@ -24,6 +25,7 @@ export enum DashboardTemplateType {
|
|
|
24
25
|
Blank = "Blank",
|
|
25
26
|
Monitor = "Monitor",
|
|
26
27
|
Incident = "Incident",
|
|
28
|
+
Alert = "Alert",
|
|
27
29
|
Kubernetes = "Kubernetes",
|
|
28
30
|
Host = "Host",
|
|
29
31
|
Proxmox = "Proxmox",
|
|
@@ -32,11 +34,32 @@ export enum DashboardTemplateType {
|
|
|
32
34
|
Metrics = "Metrics",
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/*
|
|
38
|
+
* Templates are grouped into categories so the "Create from Template"
|
|
39
|
+
* modal can render them under labelled sections instead of one flat
|
|
40
|
+
* grid. The enum values double as the section headings, and
|
|
41
|
+
* DashboardTemplateCategories below fixes the display order.
|
|
42
|
+
*/
|
|
43
|
+
export enum DashboardTemplateCategory {
|
|
44
|
+
GetStarted = "Get Started",
|
|
45
|
+
Monitoring = "Monitoring & APM",
|
|
46
|
+
IncidentResponse = "Incident Response",
|
|
47
|
+
Infrastructure = "Infrastructure",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const DashboardTemplateCategories: Array<DashboardTemplateCategory> = [
|
|
51
|
+
DashboardTemplateCategory.GetStarted,
|
|
52
|
+
DashboardTemplateCategory.Monitoring,
|
|
53
|
+
DashboardTemplateCategory.IncidentResponse,
|
|
54
|
+
DashboardTemplateCategory.Infrastructure,
|
|
55
|
+
];
|
|
56
|
+
|
|
35
57
|
export interface DashboardTemplate {
|
|
36
58
|
type: DashboardTemplateType;
|
|
37
59
|
name: string;
|
|
38
60
|
description: string;
|
|
39
61
|
icon: IconProp;
|
|
62
|
+
category: DashboardTemplateCategory;
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
@@ -45,6 +68,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
45
68
|
name: "Blank Dashboard",
|
|
46
69
|
description: "Start from scratch with an empty dashboard.",
|
|
47
70
|
icon: IconProp.Add,
|
|
71
|
+
category: DashboardTemplateCategory.GetStarted,
|
|
48
72
|
},
|
|
49
73
|
{
|
|
50
74
|
type: DashboardTemplateType.Monitor,
|
|
@@ -52,6 +76,15 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
52
76
|
description:
|
|
53
77
|
"Response time, uptime, status codes, CPU/memory health gauges, and breakdown table for synthetic and server monitors.",
|
|
54
78
|
icon: IconProp.Heartbeat,
|
|
79
|
+
category: DashboardTemplateCategory.Monitoring,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: DashboardTemplateType.Metrics,
|
|
83
|
+
name: "Metrics Dashboard",
|
|
84
|
+
description:
|
|
85
|
+
"HTTP request rate, latency, error rate, CPU utilization gauge, memory usage, disk and network I/O, and runtime metrics.",
|
|
86
|
+
icon: IconProp.ChartBar,
|
|
87
|
+
category: DashboardTemplateCategory.Monitoring,
|
|
55
88
|
},
|
|
56
89
|
{
|
|
57
90
|
type: DashboardTemplateType.Incident,
|
|
@@ -59,6 +92,15 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
59
92
|
description:
|
|
60
93
|
"Incident count, MTTR/MTTA gauges, duration trends, severity breakdown, time-in-state, and longest-incident tables.",
|
|
61
94
|
icon: IconProp.Alert,
|
|
95
|
+
category: DashboardTemplateCategory.IncidentResponse,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: DashboardTemplateType.Alert,
|
|
99
|
+
name: "Alert Dashboard",
|
|
100
|
+
description:
|
|
101
|
+
"Alert count, MTTR/MTTA gauges, duration trends, alert volume over time, and longest-alert / acknowledgement tables.",
|
|
102
|
+
icon: IconProp.Bell,
|
|
103
|
+
category: DashboardTemplateCategory.IncidentResponse,
|
|
62
104
|
},
|
|
63
105
|
{
|
|
64
106
|
type: DashboardTemplateType.Kubernetes,
|
|
@@ -66,6 +108,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
66
108
|
description:
|
|
67
109
|
"Pod/node CPU and memory averages, utilization gauges, live pod and node lists, network I/O, restarts, and cluster logs.",
|
|
68
110
|
icon: IconProp.Kubernetes,
|
|
111
|
+
category: DashboardTemplateCategory.Infrastructure,
|
|
69
112
|
},
|
|
70
113
|
{
|
|
71
114
|
type: DashboardTemplateType.Host,
|
|
@@ -73,6 +116,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
73
116
|
description:
|
|
74
117
|
"Per-host CPU, memory, disk and network charts, a live host inventory, CPU utilization gauge, process counts, and recent logs.",
|
|
75
118
|
icon: IconProp.Server,
|
|
119
|
+
category: DashboardTemplateCategory.Infrastructure,
|
|
76
120
|
},
|
|
77
121
|
{
|
|
78
122
|
type: DashboardTemplateType.Proxmox,
|
|
@@ -80,6 +124,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
80
124
|
description:
|
|
81
125
|
"Live node and guest inventories with status, CPU/memory trends, network throughput, and cluster logs.",
|
|
82
126
|
icon: IconProp.ServerStack,
|
|
127
|
+
category: DashboardTemplateCategory.Infrastructure,
|
|
83
128
|
},
|
|
84
129
|
{
|
|
85
130
|
type: DashboardTemplateType.Ceph,
|
|
@@ -87,6 +132,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
87
132
|
description:
|
|
88
133
|
"OSD wall and pool capacity lists, health and capacity stats, degraded-PG trends, client throughput, and cluster logs.",
|
|
89
134
|
icon: IconProp.Database,
|
|
135
|
+
category: DashboardTemplateCategory.Infrastructure,
|
|
90
136
|
},
|
|
91
137
|
{
|
|
92
138
|
type: DashboardTemplateType.DockerSwarm,
|
|
@@ -94,16 +140,22 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
|
|
|
94
140
|
description:
|
|
95
141
|
"Live node and service inventories with role/status, container CPU and memory trends, PID counts, and cluster logs.",
|
|
96
142
|
icon: IconProp.Cube,
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
type: DashboardTemplateType.Metrics,
|
|
100
|
-
name: "Metrics Dashboard",
|
|
101
|
-
description:
|
|
102
|
-
"HTTP request rate, latency, error rate, CPU utilization gauge, memory usage, disk and network I/O, and runtime metrics.",
|
|
103
|
-
icon: IconProp.ChartBar,
|
|
143
|
+
category: DashboardTemplateCategory.Infrastructure,
|
|
104
144
|
},
|
|
105
145
|
];
|
|
106
146
|
|
|
147
|
+
/*
|
|
148
|
+
* Templates for a category in their declared order. The modal uses this
|
|
149
|
+
* to render one labelled section per category.
|
|
150
|
+
*/
|
|
151
|
+
export function getDashboardTemplatesByCategory(
|
|
152
|
+
category: DashboardTemplateCategory,
|
|
153
|
+
): Array<DashboardTemplate> {
|
|
154
|
+
return DashboardTemplates.filter((template: DashboardTemplate): boolean => {
|
|
155
|
+
return template.category === category;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
107
159
|
// -- Metric query config helpers --
|
|
108
160
|
|
|
109
161
|
interface MetricConfig {
|
|
@@ -1105,6 +1157,212 @@ function createIncidentDashboardConfig(): DashboardViewConfig {
|
|
|
1105
1157
|
};
|
|
1106
1158
|
}
|
|
1107
1159
|
|
|
1160
|
+
function createAlertDashboardConfig(): DashboardViewConfig {
|
|
1161
|
+
/*
|
|
1162
|
+
* Alert metrics (TimeToResolve, TimeToAcknowledge, AlertDuration) are
|
|
1163
|
+
* emitted with unit "seconds" by AlertService, and AlertCount is a
|
|
1164
|
+
* unitless "1 per alert" counter — the same shape as the Incident
|
|
1165
|
+
* metrics. As with the Incident template we drop legendUnit overrides
|
|
1166
|
+
* (ValueFormatter scales `seconds` to sec/min/hr based on magnitude)
|
|
1167
|
+
* and author the MTTR/MTTA gauge ranges in seconds so the 0-100% sweep
|
|
1168
|
+
* is meaningful.
|
|
1169
|
+
*
|
|
1170
|
+
* Alerts have a leaner metric catalog than incidents: there are no
|
|
1171
|
+
* severity-change, time-in-state, or postmortem metrics, so this
|
|
1172
|
+
* template trades those widgets for the four alert metrics that are
|
|
1173
|
+
* actually emitted.
|
|
1174
|
+
*/
|
|
1175
|
+
const components: Array<DashboardBaseComponent> = [
|
|
1176
|
+
// Row 0: Title
|
|
1177
|
+
createTextComponent({
|
|
1178
|
+
text: "Alert Dashboard",
|
|
1179
|
+
top: 0,
|
|
1180
|
+
left: 0,
|
|
1181
|
+
width: 12,
|
|
1182
|
+
height: 1,
|
|
1183
|
+
isBold: true,
|
|
1184
|
+
}),
|
|
1185
|
+
|
|
1186
|
+
// Row 1: Key alert metrics — every one is "higher = worse".
|
|
1187
|
+
createValueComponent({
|
|
1188
|
+
title: "Alert Count",
|
|
1189
|
+
top: 1,
|
|
1190
|
+
left: 0,
|
|
1191
|
+
width: 3,
|
|
1192
|
+
metricConfig: {
|
|
1193
|
+
metricName: AlertMetricType.AlertCount,
|
|
1194
|
+
aggregationType: MetricsAggregationType.Sum,
|
|
1195
|
+
},
|
|
1196
|
+
trendDirection: DashboardValueTrendDirection.HigherIsWorse,
|
|
1197
|
+
}),
|
|
1198
|
+
createValueComponent({
|
|
1199
|
+
title: "MTTR",
|
|
1200
|
+
top: 1,
|
|
1201
|
+
left: 3,
|
|
1202
|
+
width: 3,
|
|
1203
|
+
metricConfig: {
|
|
1204
|
+
metricName: AlertMetricType.TimeToResolve,
|
|
1205
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1206
|
+
},
|
|
1207
|
+
trendDirection: DashboardValueTrendDirection.HigherIsWorse,
|
|
1208
|
+
}),
|
|
1209
|
+
createValueComponent({
|
|
1210
|
+
title: "MTTA",
|
|
1211
|
+
top: 1,
|
|
1212
|
+
left: 6,
|
|
1213
|
+
width: 3,
|
|
1214
|
+
metricConfig: {
|
|
1215
|
+
metricName: AlertMetricType.TimeToAcknowledge,
|
|
1216
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1217
|
+
},
|
|
1218
|
+
trendDirection: DashboardValueTrendDirection.HigherIsWorse,
|
|
1219
|
+
}),
|
|
1220
|
+
createValueComponent({
|
|
1221
|
+
title: "Avg Duration",
|
|
1222
|
+
top: 1,
|
|
1223
|
+
left: 9,
|
|
1224
|
+
width: 3,
|
|
1225
|
+
metricConfig: {
|
|
1226
|
+
metricName: AlertMetricType.AlertDuration,
|
|
1227
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1228
|
+
},
|
|
1229
|
+
trendDirection: DashboardValueTrendDirection.HigherIsWorse,
|
|
1230
|
+
}),
|
|
1231
|
+
|
|
1232
|
+
// Row 2-4: Alert trends
|
|
1233
|
+
createChartComponent({
|
|
1234
|
+
title: "Alerts Over Time",
|
|
1235
|
+
chartType: DashboardChartType.Bar,
|
|
1236
|
+
top: 2,
|
|
1237
|
+
left: 0,
|
|
1238
|
+
width: 6,
|
|
1239
|
+
height: 3,
|
|
1240
|
+
metricConfig: {
|
|
1241
|
+
metricName: AlertMetricType.AlertCount,
|
|
1242
|
+
aggregationType: MetricsAggregationType.Sum,
|
|
1243
|
+
legend: "Alerts",
|
|
1244
|
+
},
|
|
1245
|
+
}),
|
|
1246
|
+
createChartComponent({
|
|
1247
|
+
title: "Alert Duration Over Time",
|
|
1248
|
+
chartType: DashboardChartType.Line,
|
|
1249
|
+
top: 2,
|
|
1250
|
+
left: 6,
|
|
1251
|
+
width: 6,
|
|
1252
|
+
height: 3,
|
|
1253
|
+
metricConfig: {
|
|
1254
|
+
metricName: AlertMetricType.AlertDuration,
|
|
1255
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1256
|
+
legend: "Avg Duration",
|
|
1257
|
+
},
|
|
1258
|
+
}),
|
|
1259
|
+
|
|
1260
|
+
// Row 5: Section header
|
|
1261
|
+
createTextComponent({
|
|
1262
|
+
text: "Response Performance",
|
|
1263
|
+
top: 5,
|
|
1264
|
+
left: 0,
|
|
1265
|
+
width: 12,
|
|
1266
|
+
height: 1,
|
|
1267
|
+
isBold: true,
|
|
1268
|
+
}),
|
|
1269
|
+
|
|
1270
|
+
/*
|
|
1271
|
+
* Row 6-8: MTTR/MTTA gauges. Ranges and thresholds are in seconds
|
|
1272
|
+
* (matching the stored metric unit) and mirror the Incident
|
|
1273
|
+
* template: MTTR full scale 2 hours (warn 1 hour, critical 1.5
|
|
1274
|
+
* hours); MTTA full scale 1 hour (warn 15 min, critical 30 min).
|
|
1275
|
+
*/
|
|
1276
|
+
createGaugeComponent({
|
|
1277
|
+
title: "MTTR",
|
|
1278
|
+
top: 6,
|
|
1279
|
+
left: 0,
|
|
1280
|
+
width: 3,
|
|
1281
|
+
height: 3,
|
|
1282
|
+
minValue: 0,
|
|
1283
|
+
maxValue: 7200,
|
|
1284
|
+
warningThreshold: 3600,
|
|
1285
|
+
criticalThreshold: 5400,
|
|
1286
|
+
metricConfig: {
|
|
1287
|
+
metricName: AlertMetricType.TimeToResolve,
|
|
1288
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1289
|
+
},
|
|
1290
|
+
}),
|
|
1291
|
+
createGaugeComponent({
|
|
1292
|
+
title: "MTTA",
|
|
1293
|
+
top: 6,
|
|
1294
|
+
left: 3,
|
|
1295
|
+
width: 3,
|
|
1296
|
+
height: 3,
|
|
1297
|
+
minValue: 0,
|
|
1298
|
+
maxValue: 3600,
|
|
1299
|
+
warningThreshold: 900,
|
|
1300
|
+
criticalThreshold: 1800,
|
|
1301
|
+
metricConfig: {
|
|
1302
|
+
metricName: AlertMetricType.TimeToAcknowledge,
|
|
1303
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1304
|
+
},
|
|
1305
|
+
}),
|
|
1306
|
+
createChartComponent({
|
|
1307
|
+
title: "MTTR and MTTA Over Time",
|
|
1308
|
+
chartType: DashboardChartType.Area,
|
|
1309
|
+
top: 6,
|
|
1310
|
+
left: 6,
|
|
1311
|
+
width: 6,
|
|
1312
|
+
height: 3,
|
|
1313
|
+
metricConfig: {
|
|
1314
|
+
metricName: AlertMetricType.TimeToResolve,
|
|
1315
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1316
|
+
legend: "MTTR",
|
|
1317
|
+
},
|
|
1318
|
+
}),
|
|
1319
|
+
|
|
1320
|
+
// Row 9: Section header
|
|
1321
|
+
createTextComponent({
|
|
1322
|
+
text: "Alert Details",
|
|
1323
|
+
top: 9,
|
|
1324
|
+
left: 0,
|
|
1325
|
+
width: 12,
|
|
1326
|
+
height: 1,
|
|
1327
|
+
isBold: true,
|
|
1328
|
+
}),
|
|
1329
|
+
|
|
1330
|
+
/*
|
|
1331
|
+
* Row 10-12: Operational tables. Like the Incident template, logs /
|
|
1332
|
+
* traces are intentionally absent — alert records are rows in
|
|
1333
|
+
* Postgres, not log/trace sources.
|
|
1334
|
+
*/
|
|
1335
|
+
createTableComponent({
|
|
1336
|
+
title: "Alerts by Duration",
|
|
1337
|
+
top: 10,
|
|
1338
|
+
left: 0,
|
|
1339
|
+
width: 6,
|
|
1340
|
+
height: 3,
|
|
1341
|
+
metricConfig: {
|
|
1342
|
+
metricName: AlertMetricType.AlertDuration,
|
|
1343
|
+
aggregationType: MetricsAggregationType.Max,
|
|
1344
|
+
},
|
|
1345
|
+
}),
|
|
1346
|
+
createTableComponent({
|
|
1347
|
+
title: "Time to Acknowledge",
|
|
1348
|
+
top: 10,
|
|
1349
|
+
left: 6,
|
|
1350
|
+
width: 6,
|
|
1351
|
+
height: 3,
|
|
1352
|
+
metricConfig: {
|
|
1353
|
+
metricName: AlertMetricType.TimeToAcknowledge,
|
|
1354
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1355
|
+
},
|
|
1356
|
+
}),
|
|
1357
|
+
];
|
|
1358
|
+
|
|
1359
|
+
return {
|
|
1360
|
+
_type: ObjectType.DashboardViewConfig,
|
|
1361
|
+
components,
|
|
1362
|
+
heightInDashboardUnits: Math.max(DashboardSize.heightInDashboardUnits, 13),
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1108
1366
|
function createKubernetesDashboardConfig(): DashboardViewConfig {
|
|
1109
1367
|
/*
|
|
1110
1368
|
* Layout notes:
|
|
@@ -2427,6 +2685,8 @@ export function getTemplateConfig(
|
|
|
2427
2685
|
return createMonitorDashboardConfig();
|
|
2428
2686
|
case DashboardTemplateType.Incident:
|
|
2429
2687
|
return createIncidentDashboardConfig();
|
|
2688
|
+
case DashboardTemplateType.Alert:
|
|
2689
|
+
return createAlertDashboardConfig();
|
|
2430
2690
|
case DashboardTemplateType.Kubernetes:
|
|
2431
2691
|
return createKubernetesDashboardConfig();
|
|
2432
2692
|
case DashboardTemplateType.Host:
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import Metric from "../../Models/AnalyticsModels/Metric";
|
|
2
|
-
import Includes from "../BaseDatabase/Includes";
|
|
3
|
-
import Query from "../BaseDatabase/Query";
|
|
4
1
|
import { JSONObject } from "../JSON";
|
|
5
2
|
import MetricsViewConfig from "../Metrics/MetricsViewConfig";
|
|
6
3
|
import RollingTime from "../RollingTime/RollingTime";
|
|
@@ -8,13 +5,6 @@ import RollingTime from "../RollingTime/RollingTime";
|
|
|
8
5
|
export default interface MonitorStepMetricMonitor {
|
|
9
6
|
metricViewConfig: MetricsViewConfig;
|
|
10
7
|
rollingTime: RollingTime;
|
|
11
|
-
/*
|
|
12
|
-
* Stable telemetry entity keys (host / pod / container / ...) — scopes
|
|
13
|
-
* every metric query in this step to rows carrying any of these in their
|
|
14
|
-
* entityKeys column. Optional: monitors saved before this field existed
|
|
15
|
-
* have it undefined.
|
|
16
|
-
*/
|
|
17
|
-
entityKeys?: Array<string> | undefined;
|
|
18
8
|
}
|
|
19
9
|
|
|
20
10
|
export class MonitorStepMetricMonitorUtil {
|
|
@@ -25,26 +15,9 @@ export class MonitorStepMetricMonitorUtil {
|
|
|
25
15
|
formulaConfigs: [],
|
|
26
16
|
},
|
|
27
17
|
rollingTime: RollingTime.Past1Minute,
|
|
28
|
-
entityKeys: [],
|
|
29
18
|
};
|
|
30
19
|
}
|
|
31
20
|
|
|
32
|
-
/*
|
|
33
|
-
* Stamps the step's entity scope onto a per-queryConfig Query<Metric>.
|
|
34
|
-
* Compiles to hasAny(entityKeys, [...]) server-side. Undefined/empty is
|
|
35
|
-
* a no-op so monitors saved before the field existed are unaffected.
|
|
36
|
-
*/
|
|
37
|
-
public static applyEntityScopeToQuery(
|
|
38
|
-
query: Query<Metric>,
|
|
39
|
-
monitor: MonitorStepMetricMonitor,
|
|
40
|
-
): Query<Metric> {
|
|
41
|
-
if (monitor.entityKeys && monitor.entityKeys.length > 0) {
|
|
42
|
-
query.entityKeys = new Includes(monitor.entityKeys);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return query;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
21
|
public static fromJSON(json: JSONObject): MonitorStepMetricMonitor {
|
|
49
22
|
return json as any as MonitorStepMetricMonitor;
|
|
50
23
|
}
|