@oneuptime/common 10.4.11 → 10.4.13
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/DatabaseModels/Alert.ts +332 -0
- package/Models/DatabaseModels/Incident.ts +332 -0
- package/Models/DatabaseModels/RunbookAgent.ts +16 -2
- package/Server/EnvironmentConfig.ts +16 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +14 -0
- package/Server/Infrastructure/ClickhouseDatabase.ts +20 -3
- package/Server/Infrastructure/InMemoryTTLCache.ts +61 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779302536475-AttachKubernetesAndDockerToIncidentAndAlert.ts +253 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779303924241-AttachServiceToIncidentAndAlert.ts +60 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/TelemetryIngest.ts +6 -38
- package/Server/Middleware/UserAuthorization.ts +1 -11
- package/Server/Services/AnalyticsDatabaseService.ts +33 -1
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +6 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +1 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +17 -0
- package/Server/Services/TelemetryIngestionKeyService.ts +90 -1
- package/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.ts +108 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +25 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +0 -7
- package/Types/Dashboard/DashboardComponentType.ts +0 -1
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +2 -0
- package/Types/Dashboard/DashboardComponents/DashboardTableComponent.ts +74 -1
- package/Types/Dashboard/DashboardTemplates.ts +164 -51
- package/Types/Monitor/CriteriaFilter.ts +13 -0
- package/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.ts +69 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
- package/Types/Monitor/MonitorStep.ts +39 -0
- package/Types/Monitor/MonitorStepDnssecMonitor.ts +59 -0
- package/Types/Monitor/MonitorType.ts +18 -2
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +36 -9
- package/Types/Probe/ProbeMonitorResponse.ts +2 -0
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +51 -0
- package/Utils/Dashboard/Components/DashboardTableComponent.ts +80 -17
- package/Utils/Dashboard/Components/Index.ts +0 -7
- package/Utils/Monitor/MonitorMetricType.ts +1 -0
- package/build/dist/Models/DatabaseModels/Alert.js +324 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +324 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunbookAgent.js +16 -2
- package/build/dist/Models/DatabaseModels/RunbookAgent.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +8 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +9 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +12 -3
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
- package/build/dist/Server/Infrastructure/InMemoryTTLCache.js +49 -0
- package/build/dist/Server/Infrastructure/InMemoryTTLCache.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779302536475-AttachKubernetesAndDockerToIncidentAndAlert.js +100 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779302536475-AttachKubernetesAndDockerToIncidentAndAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779303924241-AttachServiceToIncidentAndAlert.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779303924241-AttachServiceToIncidentAndAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +2 -26
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +1 -7
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +23 -2
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +28 -24
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +18 -2
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js +83 -0
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.js +94 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -3
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +0 -7
- package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +0 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardTableComponent.js +13 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardTableComponent.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +142 -42
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +12 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +26 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js +42 -0
- package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +16 -2
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +27 -7
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +47 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardTableComponent.js +68 -16
- package/build/dist/Utils/Dashboard/Components/DashboardTableComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/Index.js +0 -4
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +1 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -2
- package/Types/Dashboard/DashboardComponents/DashboardHostMetricChartComponent.ts +0 -27
- package/Typings/elkjs.d.ts +0 -30
- package/Utils/Dashboard/Components/DashboardHostMetricChartComponent.ts +0 -132
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardHostMetricChartComponent.js +0 -11
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardHostMetricChartComponent.js.map +0 -1
- package/build/dist/Utils/Dashboard/Components/DashboardHostMetricChartComponent.js +0 -113
- package/build/dist/Utils/Dashboard/Components/DashboardHostMetricChartComponent.js.map +0 -1
|
@@ -2,6 +2,7 @@ import { WorkflowHostname } from "../EnvironmentConfig";
|
|
|
2
2
|
import ClickhouseDatabase, {
|
|
3
3
|
ClickhouseAppInstance,
|
|
4
4
|
ClickhouseClient,
|
|
5
|
+
ClickhouseIngestInstance,
|
|
5
6
|
} from "../Infrastructure/ClickhouseDatabase";
|
|
6
7
|
import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization";
|
|
7
8
|
import CountBy from "../Types/AnalyticsDatabase/CountBy";
|
|
@@ -67,13 +68,16 @@ export default class AnalyticsDatabaseService<
|
|
|
67
68
|
> extends BaseService {
|
|
68
69
|
public modelType!: { new (): TBaseModel };
|
|
69
70
|
public database!: ClickhouseDatabase;
|
|
71
|
+
public ingestDatabase!: ClickhouseDatabase;
|
|
70
72
|
public model!: TBaseModel;
|
|
71
73
|
public databaseClient!: ClickhouseClient | null;
|
|
74
|
+
public ingestDatabaseClient!: ClickhouseClient | null;
|
|
72
75
|
public statementGenerator!: StatementGenerator<TBaseModel>;
|
|
73
76
|
|
|
74
77
|
public constructor(data: {
|
|
75
78
|
modelType: { new (): TBaseModel };
|
|
76
79
|
database?: ClickhouseDatabase | undefined;
|
|
80
|
+
ingestDatabase?: ClickhouseDatabase | undefined;
|
|
77
81
|
}) {
|
|
78
82
|
super();
|
|
79
83
|
this.modelType = data.modelType;
|
|
@@ -84,7 +88,14 @@ export default class AnalyticsDatabaseService<
|
|
|
84
88
|
this.database = ClickhouseAppInstance; // default database
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
if (data.ingestDatabase) {
|
|
92
|
+
this.ingestDatabase = data.ingestDatabase;
|
|
93
|
+
} else {
|
|
94
|
+
this.ingestDatabase = ClickhouseIngestInstance;
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
this.databaseClient = this.database.getDataSource();
|
|
98
|
+
this.ingestDatabaseClient = this.ingestDatabase.getDataSource();
|
|
88
99
|
|
|
89
100
|
this.statementGenerator = new StatementGenerator<TBaseModel>({
|
|
90
101
|
modelType: this.modelType,
|
|
@@ -98,7 +109,7 @@ export default class AnalyticsDatabaseService<
|
|
|
98
109
|
return;
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
const client: ClickhouseClient = this.
|
|
112
|
+
const client: ClickhouseClient = this.getIngestClient();
|
|
102
113
|
|
|
103
114
|
const tableName: string = this.model.tableName;
|
|
104
115
|
|
|
@@ -1040,6 +1051,8 @@ export default class AnalyticsDatabaseService<
|
|
|
1040
1051
|
public useDefaultDatabase(): void {
|
|
1041
1052
|
this.database = ClickhouseAppInstance;
|
|
1042
1053
|
this.databaseClient = this.database.getDataSource();
|
|
1054
|
+
this.ingestDatabase = ClickhouseIngestInstance;
|
|
1055
|
+
this.ingestDatabaseClient = this.ingestDatabase.getDataSource();
|
|
1043
1056
|
}
|
|
1044
1057
|
|
|
1045
1058
|
@CaptureSpan()
|
|
@@ -1100,6 +1113,25 @@ export default class AnalyticsDatabaseService<
|
|
|
1100
1113
|
return this.databaseClient;
|
|
1101
1114
|
}
|
|
1102
1115
|
|
|
1116
|
+
private getIngestClient(): ClickhouseClient {
|
|
1117
|
+
if (!this.ingestDatabase) {
|
|
1118
|
+
this.useDefaultDatabase();
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (!this.ingestDatabaseClient && this.ingestDatabase) {
|
|
1122
|
+
this.ingestDatabaseClient = this.ingestDatabase.getDataSource();
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (!this.ingestDatabaseClient) {
|
|
1126
|
+
throw new Exception(
|
|
1127
|
+
ExceptionCode.DatabaseNotConnectedException,
|
|
1128
|
+
"ClickHouse ingest client is not connected",
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
return this.ingestDatabaseClient;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1103
1135
|
protected async onUpdateSuccess(
|
|
1104
1136
|
onUpdate: OnUpdate<TBaseModel>,
|
|
1105
1137
|
_updatedItemIds: Array<ObjectID>
|
|
@@ -77,6 +77,9 @@ export class Service extends DatabaseService<Model> {
|
|
|
77
77
|
const userOnSchedule: ObjectID | null =
|
|
78
78
|
await OnCallDutyPolicyScheduleService.getCurrentUserIdInSchedule(
|
|
79
79
|
createdModel.onCallDutyPolicyScheduleId,
|
|
80
|
+
{
|
|
81
|
+
onCallDutyPolicyId: createdModel.onCallDutyPolicy?.id || undefined,
|
|
82
|
+
},
|
|
80
83
|
);
|
|
81
84
|
|
|
82
85
|
if (!userOnSchedule) {
|
|
@@ -277,6 +280,9 @@ export class Service extends DatabaseService<Model> {
|
|
|
277
280
|
const userOnSchedule: ObjectID | null =
|
|
278
281
|
await OnCallDutyPolicyScheduleService.getCurrentUserIdInSchedule(
|
|
279
282
|
deletedItem.onCallDutyPolicyScheduleId!,
|
|
283
|
+
{
|
|
284
|
+
onCallDutyPolicyId: deletedItem.onCallDutyPolicy?.id || undefined,
|
|
285
|
+
},
|
|
280
286
|
);
|
|
281
287
|
|
|
282
288
|
if (!userOnSchedule) {
|
|
@@ -460,6 +460,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
460
460
|
const userIdInSchedule: ObjectID | null =
|
|
461
461
|
await OnCallDutyPolicyScheduleService.getCurrentUserIdInSchedule(
|
|
462
462
|
scheduleRule.onCallDutyPolicyScheduleId!,
|
|
463
|
+
{ onCallDutyPolicyId: options.onCallPolicyId },
|
|
463
464
|
);
|
|
464
465
|
|
|
465
466
|
if (!userIdInSchedule) {
|
|
@@ -1052,17 +1052,28 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1052
1052
|
scheduleUserIds: Array<ObjectID>;
|
|
1053
1053
|
windowStart: Date;
|
|
1054
1054
|
windowEnd: Date;
|
|
1055
|
+
currentOnCallDutyPolicyId?: ObjectID | undefined;
|
|
1055
1056
|
}): Promise<Array<UserOverrideRecord>> {
|
|
1056
1057
|
if (data.scheduleUserIds.length === 0) {
|
|
1057
1058
|
return [];
|
|
1058
1059
|
}
|
|
1059
1060
|
|
|
1061
|
+
/*
|
|
1062
|
+
* When a policy context is provided, include overrides scoped to that
|
|
1063
|
+
* policy plus global overrides. Without a policy context (e.g. schedule
|
|
1064
|
+
* roster refresh, dashboard preview, incoming-call routing), only global
|
|
1065
|
+
* overrides apply — a policy-specific override from one policy must not
|
|
1066
|
+
* affect schedule resolution for a different policy.
|
|
1067
|
+
*/
|
|
1060
1068
|
const overrides: Array<OnCallDutyPolicyUserOverride> =
|
|
1061
1069
|
await OnCallDutyPolicyUserOverrideService.findBy({
|
|
1062
1070
|
query: {
|
|
1063
1071
|
projectId: data.projectId,
|
|
1064
1072
|
startsAt: QueryHelper.lessThanEqualTo(data.windowEnd),
|
|
1065
1073
|
endsAt: QueryHelper.greaterThanEqualTo(data.windowStart),
|
|
1074
|
+
onCallDutyPolicyId: data.currentOnCallDutyPolicyId
|
|
1075
|
+
? QueryHelper.equalToOrNull(data.currentOnCallDutyPolicyId)
|
|
1076
|
+
: QueryHelper.isNull(),
|
|
1066
1077
|
},
|
|
1067
1078
|
select: {
|
|
1068
1079
|
startsAt: true,
|
|
@@ -1106,6 +1117,7 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1106
1117
|
public async getEventByIndexInSchedule(data: {
|
|
1107
1118
|
scheduleId: ObjectID;
|
|
1108
1119
|
getNumberOfEvents: number; // which event would you like to get. First event, second event, etc.
|
|
1120
|
+
onCallDutyPolicyId?: ObjectID | undefined;
|
|
1109
1121
|
}): Promise<Array<CalendarEvent>> {
|
|
1110
1122
|
logger.debug(
|
|
1111
1123
|
"getEventByIndexInSchedule called with data: " + JSON.stringify(data),
|
|
@@ -1169,12 +1181,14 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1169
1181
|
scheduleUserIds,
|
|
1170
1182
|
windowStart: currentStartTime,
|
|
1171
1183
|
windowEnd: currentEndTime,
|
|
1184
|
+
currentOnCallDutyPolicyId: data.onCallDutyPolicyId,
|
|
1172
1185
|
});
|
|
1173
1186
|
|
|
1174
1187
|
if (overrides.length > 0) {
|
|
1175
1188
|
events = UserOverrideUtil.applyOverridesToEvents({
|
|
1176
1189
|
events,
|
|
1177
1190
|
overrides,
|
|
1191
|
+
currentOnCallDutyPolicyId: data.onCallDutyPolicyId?.toString(),
|
|
1178
1192
|
});
|
|
1179
1193
|
}
|
|
1180
1194
|
}
|
|
@@ -1189,6 +1203,7 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1189
1203
|
@CaptureSpan()
|
|
1190
1204
|
public async getCurrentUserIdInSchedule(
|
|
1191
1205
|
scheduleId: ObjectID,
|
|
1206
|
+
options?: { onCallDutyPolicyId?: ObjectID | undefined } | undefined,
|
|
1192
1207
|
): Promise<ObjectID | null> {
|
|
1193
1208
|
const { layerProps, projectId, scheduleUserIds } =
|
|
1194
1209
|
await this.getScheduleLayerProps({
|
|
@@ -1223,12 +1238,14 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1223
1238
|
scheduleUserIds,
|
|
1224
1239
|
windowStart: currentStartTime,
|
|
1225
1240
|
windowEnd: currentEndTime,
|
|
1241
|
+
currentOnCallDutyPolicyId: options?.onCallDutyPolicyId,
|
|
1226
1242
|
});
|
|
1227
1243
|
|
|
1228
1244
|
if (overrides.length > 0) {
|
|
1229
1245
|
events = UserOverrideUtil.applyOverridesToEvents({
|
|
1230
1246
|
events,
|
|
1231
1247
|
overrides,
|
|
1248
|
+
currentOnCallDutyPolicyId: options?.onCallDutyPolicyId?.toString(),
|
|
1232
1249
|
});
|
|
1233
1250
|
}
|
|
1234
1251
|
}
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import CreateBy from "../Types/Database/CreateBy";
|
|
2
|
-
import
|
|
2
|
+
import DeleteBy from "../Types/Database/DeleteBy";
|
|
3
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
4
|
+
import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
|
|
3
5
|
import DatabaseService from "./DatabaseService";
|
|
4
6
|
import ObjectID from "../../Types/ObjectID";
|
|
5
7
|
import Model from "../../Models/DatabaseModels/TelemetryIngestionKey";
|
|
6
8
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
9
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
10
|
+
import { createHash } from "crypto";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* 60s is the worst-case staleness on any single API node after a key is
|
|
14
|
+
* revoked from the dashboard. We invalidate in-process immediately on
|
|
15
|
+
* delete/update; this TTL is the upper bound for *other* processes.
|
|
16
|
+
*/
|
|
17
|
+
const POSITIVE_TTL_MS: number = 60 * 1000;
|
|
18
|
+
/*
|
|
19
|
+
* Short TTL on misses so an invalid-token flood can't pin entries in the
|
|
20
|
+
* bounded cache for long while still absorbing repeat hits.
|
|
21
|
+
*/
|
|
22
|
+
const NEGATIVE_TTL_MS: number = 10 * 1000;
|
|
7
23
|
|
|
8
24
|
export class Service extends DatabaseService<Model> {
|
|
25
|
+
private projectIdCache: InMemoryTTLCache<string | null> =
|
|
26
|
+
new InMemoryTTLCache(10_000);
|
|
27
|
+
|
|
9
28
|
public constructor() {
|
|
10
29
|
super(Model);
|
|
11
30
|
}
|
|
@@ -20,6 +39,76 @@ export class Service extends DatabaseService<Model> {
|
|
|
20
39
|
|
|
21
40
|
return { createBy, carryForward: null };
|
|
22
41
|
}
|
|
42
|
+
|
|
43
|
+
@CaptureSpan()
|
|
44
|
+
protected override async onBeforeDelete(
|
|
45
|
+
deleteBy: DeleteBy<Model>,
|
|
46
|
+
): Promise<OnDelete<Model>> {
|
|
47
|
+
/*
|
|
48
|
+
* We don't know which secretKey(s) are being deleted without an extra
|
|
49
|
+
* query; clear the whole cache. Key deletes are rare so this is cheap.
|
|
50
|
+
*/
|
|
51
|
+
this.projectIdCache.clear();
|
|
52
|
+
return { deleteBy, carryForward: null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@CaptureSpan()
|
|
56
|
+
protected override async onBeforeUpdate(
|
|
57
|
+
updateBy: UpdateBy<Model>,
|
|
58
|
+
): Promise<OnUpdate<Model>> {
|
|
59
|
+
/*
|
|
60
|
+
* Same reasoning as onBeforeDelete. secretKey is not user-editable today
|
|
61
|
+
* but projectId could change, so be conservative.
|
|
62
|
+
*/
|
|
63
|
+
this.projectIdCache.clear();
|
|
64
|
+
return { updateBy, carryForward: null };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve an ingestion token to its projectId, with a short-lived
|
|
69
|
+
* in-process cache to keep the hot ingest path off Postgres. Returns null
|
|
70
|
+
* for unknown, malformed, or revoked tokens (also cached, for a shorter
|
|
71
|
+
* TTL).
|
|
72
|
+
*/
|
|
73
|
+
@CaptureSpan()
|
|
74
|
+
public async getProjectIdFromSecretKey(
|
|
75
|
+
secretKey: string,
|
|
76
|
+
): Promise<ObjectID | null> {
|
|
77
|
+
const cacheKey: string = createHash("sha256")
|
|
78
|
+
.update(secretKey)
|
|
79
|
+
.digest("hex");
|
|
80
|
+
|
|
81
|
+
const cached: string | null | undefined = this.projectIdCache.get(cacheKey);
|
|
82
|
+
if (cached !== undefined) {
|
|
83
|
+
return cached === null ? null : new ObjectID(cached);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let secretKeyObjectId: ObjectID;
|
|
87
|
+
try {
|
|
88
|
+
secretKeyObjectId = new ObjectID(secretKey);
|
|
89
|
+
} catch {
|
|
90
|
+
this.projectIdCache.set(cacheKey, null, NEGATIVE_TTL_MS);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const token: Model | null = await this.findOneBy({
|
|
95
|
+
query: { secretKey: secretKeyObjectId },
|
|
96
|
+
select: { projectId: true },
|
|
97
|
+
props: { isRoot: true },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const projectId: ObjectID | undefined = token?.projectId as
|
|
101
|
+
| ObjectID
|
|
102
|
+
| undefined;
|
|
103
|
+
|
|
104
|
+
if (!projectId) {
|
|
105
|
+
this.projectIdCache.set(cacheKey, null, NEGATIVE_TTL_MS);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.projectIdCache.set(cacheKey, projectId.toString(), POSITIVE_TTL_MS);
|
|
110
|
+
return projectId;
|
|
111
|
+
}
|
|
23
112
|
}
|
|
24
113
|
|
|
25
114
|
export default new Service();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import DataToProcess from "../DataToProcess";
|
|
2
|
+
import CompareCriteria from "./CompareCriteria";
|
|
3
|
+
import {
|
|
4
|
+
CheckOn,
|
|
5
|
+
CriteriaFilter,
|
|
6
|
+
FilterType,
|
|
7
|
+
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
8
|
+
import DnssecMonitorResponse from "../../../../Types/Monitor/DnssecMonitor/DnssecMonitorResponse";
|
|
9
|
+
import ProbeMonitorResponse from "../../../../Types/Probe/ProbeMonitorResponse";
|
|
10
|
+
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
11
|
+
|
|
12
|
+
export default class DnssecMonitorCriteria {
|
|
13
|
+
@CaptureSpan()
|
|
14
|
+
public static async isMonitorInstanceCriteriaFilterMet(input: {
|
|
15
|
+
dataToProcess: DataToProcess;
|
|
16
|
+
criteriaFilter: CriteriaFilter;
|
|
17
|
+
}): Promise<string | null> {
|
|
18
|
+
const dataToProcess: ProbeMonitorResponse =
|
|
19
|
+
input.dataToProcess as ProbeMonitorResponse;
|
|
20
|
+
|
|
21
|
+
const dnssecResponse: DnssecMonitorResponse | undefined =
|
|
22
|
+
dataToProcess.dnssecResponse;
|
|
23
|
+
|
|
24
|
+
if (!dnssecResponse) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isTrue: boolean = input.criteriaFilter.filterType === FilterType.True;
|
|
29
|
+
const isFalse: boolean =
|
|
30
|
+
input.criteriaFilter.filterType === FilterType.False;
|
|
31
|
+
|
|
32
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecChainValid) {
|
|
33
|
+
if (dnssecResponse.isChainValid && isTrue) {
|
|
34
|
+
return `DNSSEC chain is valid for ${dnssecResponse.domainName}.`;
|
|
35
|
+
}
|
|
36
|
+
if (!dnssecResponse.isChainValid && isFalse) {
|
|
37
|
+
return `DNSSEC chain validation failed for ${dnssecResponse.domainName}.`;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecDnskeyExists) {
|
|
43
|
+
const exists: boolean = dnssecResponse.dnskeys.length > 0;
|
|
44
|
+
if (exists && isTrue) {
|
|
45
|
+
return `DNSKEY records present for ${dnssecResponse.domainName}.`;
|
|
46
|
+
}
|
|
47
|
+
if (!exists && isFalse) {
|
|
48
|
+
return `No DNSKEY records found for ${dnssecResponse.domainName}.`;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecDsExists) {
|
|
54
|
+
const exists: boolean = dnssecResponse.isParentDsPresent;
|
|
55
|
+
if (exists && isTrue) {
|
|
56
|
+
return `DS records present at parent zone for ${dnssecResponse.domainName}.`;
|
|
57
|
+
}
|
|
58
|
+
if (!exists && isFalse) {
|
|
59
|
+
return `No DS records found at the parent zone for ${dnssecResponse.domainName}.`;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecResolverConsensus) {
|
|
65
|
+
const consensus: boolean = dnssecResponse.resolverConsensusAd;
|
|
66
|
+
if (consensus && isTrue) {
|
|
67
|
+
return `All resolvers report DNSSEC-valid (AD flag) for ${dnssecResponse.domainName}.`;
|
|
68
|
+
}
|
|
69
|
+
if (!consensus && isFalse) {
|
|
70
|
+
return `Resolvers do not agree on DNSSEC validity for ${dnssecResponse.domainName}.`;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecNameserverConsistent) {
|
|
76
|
+
const consistent: boolean = dnssecResponse.isNameserverConsistent;
|
|
77
|
+
if (consistent && isTrue) {
|
|
78
|
+
return `Authoritative nameservers are consistent for ${dnssecResponse.domainName}.`;
|
|
79
|
+
}
|
|
80
|
+
if (!consistent && isFalse) {
|
|
81
|
+
return `Authoritative nameservers are inconsistent for ${dnssecResponse.domainName}.`;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecSignatureExpiresInDays) {
|
|
87
|
+
const threshold: number | null = CompareCriteria.convertToNumber(
|
|
88
|
+
input.criteriaFilter.value,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (threshold === null || threshold === undefined) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (dnssecResponse.daysUntilSignatureExpiry === undefined) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return CompareCriteria.compareCriteriaNumbers({
|
|
100
|
+
value: dnssecResponse.daysUntilSignatureExpiry,
|
|
101
|
+
threshold: threshold,
|
|
102
|
+
criteriaFilter: input.criteriaFilter,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -18,6 +18,7 @@ import ProfileMonitorCriteria from "./Criteria/ProfileMonitorCriteria";
|
|
|
18
18
|
import SnmpMonitorCriteria from "./Criteria/SnmpMonitorCriteria";
|
|
19
19
|
import DnsMonitorCriteria from "./Criteria/DnsMonitorCriteria";
|
|
20
20
|
import DomainMonitorCriteria from "./Criteria/DomainMonitorCriteria";
|
|
21
|
+
import DnssecMonitorCriteria from "./Criteria/DnssecMonitorCriteria";
|
|
21
22
|
import ExternalStatusPageMonitorCriteria from "./Criteria/ExternalStatusPageMonitorCriteria";
|
|
22
23
|
import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
|
|
23
24
|
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
|
|
@@ -761,6 +762,18 @@ ${contextBlock}
|
|
|
761
762
|
}
|
|
762
763
|
}
|
|
763
764
|
|
|
765
|
+
if (input.monitor.monitorType === MonitorType.DNSSEC) {
|
|
766
|
+
const dnssecMonitorResult: string | null =
|
|
767
|
+
await DnssecMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
|
|
768
|
+
dataToProcess: input.dataToProcess,
|
|
769
|
+
criteriaFilter: input.criteriaFilter,
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
if (dnssecMonitorResult) {
|
|
773
|
+
return dnssecMonitorResult;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
764
777
|
if (input.monitor.monitorType === MonitorType.ExternalStatusPage) {
|
|
765
778
|
const externalStatusPageResult: string | null =
|
|
766
779
|
await ExternalStatusPageMonitorCriteria.isMonitorInstanceCriteriaFilterMet(
|
|
@@ -19,6 +19,7 @@ import DnsMonitorResponse, {
|
|
|
19
19
|
DnsRecordResponse,
|
|
20
20
|
} from "../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
|
|
21
21
|
import DomainMonitorResponse from "../../../Types/Monitor/DomainMonitor/DomainMonitorResponse";
|
|
22
|
+
import DnssecMonitorResponse from "../../../Types/Monitor/DnssecMonitor/DnssecMonitorResponse";
|
|
22
23
|
import ExternalStatusPageMonitorResponse, {
|
|
23
24
|
ExternalStatusPageComponentStatus,
|
|
24
25
|
} from "../../../Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse";
|
|
@@ -332,6 +333,30 @@ export default class MonitorTemplateUtil {
|
|
|
332
333
|
} as JSONObject;
|
|
333
334
|
}
|
|
334
335
|
|
|
336
|
+
if (data.monitorType === MonitorType.DNSSEC) {
|
|
337
|
+
const dnssecResponse: DnssecMonitorResponse | undefined = (
|
|
338
|
+
data.dataToProcess as ProbeMonitorResponse
|
|
339
|
+
).dnssecResponse;
|
|
340
|
+
|
|
341
|
+
storageMap = {
|
|
342
|
+
isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
|
|
343
|
+
responseTimeInMs: dnssecResponse?.responseTimeInMs,
|
|
344
|
+
failureCause: dnssecResponse?.failureCause,
|
|
345
|
+
domainName: dnssecResponse?.domainName,
|
|
346
|
+
isZoneSigned: dnssecResponse?.isZoneSigned,
|
|
347
|
+
isParentDsPresent: dnssecResponse?.isParentDsPresent,
|
|
348
|
+
isChainValid: dnssecResponse?.isChainValid,
|
|
349
|
+
resolverConsensusAd: dnssecResponse?.resolverConsensusAd,
|
|
350
|
+
isNameserverConsistent: dnssecResponse?.isNameserverConsistent,
|
|
351
|
+
earliestSignatureExpiration:
|
|
352
|
+
dnssecResponse?.earliestSignatureExpiration,
|
|
353
|
+
daysUntilSignatureExpiry: dnssecResponse?.daysUntilSignatureExpiry,
|
|
354
|
+
dnskeyCount: dnssecResponse?.dnskeys?.length,
|
|
355
|
+
dsRecordCount: dnssecResponse?.parentDsRecords?.length,
|
|
356
|
+
rrsigCount: dnssecResponse?.rrsigs?.length,
|
|
357
|
+
} as JSONObject;
|
|
358
|
+
}
|
|
359
|
+
|
|
335
360
|
if (
|
|
336
361
|
data.monitorType === MonitorType.Metrics ||
|
|
337
362
|
data.monitorType === MonitorType.Kubernetes ||
|
|
@@ -423,7 +423,6 @@ describe("UserMiddleware", () => {
|
|
|
423
423
|
req,
|
|
424
424
|
tenantId: projectId,
|
|
425
425
|
userId,
|
|
426
|
-
isGlobalLogin: true,
|
|
427
426
|
},
|
|
428
427
|
);
|
|
429
428
|
expect(next).not.toBeCalled();
|
|
@@ -454,7 +453,6 @@ describe("UserMiddleware", () => {
|
|
|
454
453
|
req,
|
|
455
454
|
tenantId: projectId,
|
|
456
455
|
userId,
|
|
457
|
-
isGlobalLogin: true,
|
|
458
456
|
},
|
|
459
457
|
);
|
|
460
458
|
});
|
|
@@ -579,7 +577,6 @@ describe("UserMiddleware", () => {
|
|
|
579
577
|
req: mockedRequest,
|
|
580
578
|
tenantId: projectId,
|
|
581
579
|
userId,
|
|
582
|
-
isGlobalLogin: true,
|
|
583
580
|
},
|
|
584
581
|
);
|
|
585
582
|
});
|
|
@@ -609,7 +606,6 @@ describe("UserMiddleware", () => {
|
|
|
609
606
|
req,
|
|
610
607
|
tenantId: projectId,
|
|
611
608
|
userId,
|
|
612
|
-
isGlobalLogin: true,
|
|
613
609
|
}),
|
|
614
610
|
).rejects.toThrowError(new BadDataException("Invalid tenantId"));
|
|
615
611
|
expect(spyFindOneById).toHaveBeenCalledWith({
|
|
@@ -636,7 +632,6 @@ describe("UserMiddleware", () => {
|
|
|
636
632
|
req,
|
|
637
633
|
tenantId: projectId,
|
|
638
634
|
userId,
|
|
639
|
-
isGlobalLogin: true,
|
|
640
635
|
}),
|
|
641
636
|
).rejects.toThrowError(new SsoAuthorizationException());
|
|
642
637
|
expect(spyDoesSsoTokenForProjectExist).toHaveBeenCalledWith(
|
|
@@ -659,7 +654,6 @@ describe("UserMiddleware", () => {
|
|
|
659
654
|
req,
|
|
660
655
|
tenantId: projectId,
|
|
661
656
|
userId,
|
|
662
|
-
isGlobalLogin: true,
|
|
663
657
|
});
|
|
664
658
|
|
|
665
659
|
expect(result).toBeNull();
|
|
@@ -686,7 +680,6 @@ describe("UserMiddleware", () => {
|
|
|
686
680
|
req,
|
|
687
681
|
tenantId: projectId,
|
|
688
682
|
userId,
|
|
689
|
-
isGlobalLogin: true,
|
|
690
683
|
});
|
|
691
684
|
|
|
692
685
|
expect(result).toEqual(mockedUserTenantAccessPermission);
|
|
@@ -11,6 +11,8 @@ export enum ComponentInputType {
|
|
|
11
11
|
MetricsQueryConfig = "MetricsQueryConfig",
|
|
12
12
|
MetricsQueryConfigs = "MetricsQueryConfigs",
|
|
13
13
|
MetricsFormulaConfigs = "MetricsFormulaConfigs",
|
|
14
|
+
TableColumns = "TableColumns",
|
|
15
|
+
TableGroupBy = "TableGroupBy",
|
|
14
16
|
LongText = "Long Text",
|
|
15
17
|
Dropdown = "Dropdown",
|
|
16
18
|
MultiSelectDropdown = "MultiSelectDropdown",
|
|
@@ -1,14 +1,87 @@
|
|
|
1
|
+
import MetricFormulaConfigData from "../../Metrics/MetricFormulaConfigData";
|
|
1
2
|
import MetricQueryConfigData from "../../Metrics/MetricQueryConfigData";
|
|
3
|
+
import MetricsAggregationType from "../../Metrics/MetricsAggregationType";
|
|
2
4
|
import ObjectID from "../../ObjectID";
|
|
3
5
|
import DashboardComponentType from "../DashboardComponentType";
|
|
4
6
|
import BaseComponent from "./DashboardBaseComponent";
|
|
5
7
|
|
|
8
|
+
export enum TableReduce {
|
|
9
|
+
Last = "Last",
|
|
10
|
+
Avg = "Avg",
|
|
11
|
+
Sum = "Sum",
|
|
12
|
+
Min = "Min",
|
|
13
|
+
Max = "Max",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export enum TableColumnKind {
|
|
17
|
+
Metric = "Metric",
|
|
18
|
+
Formula = "Formula",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TableColumn {
|
|
22
|
+
/*
|
|
23
|
+
* Stable identifier so React keys survive reorder and the form can
|
|
24
|
+
* match inputs to the column being edited.
|
|
25
|
+
*/
|
|
26
|
+
id: string;
|
|
27
|
+
// Single-letter variable (a, b, c, ...) that formulas can reference.
|
|
28
|
+
variable: string;
|
|
29
|
+
// User-facing column header (used only when showAsColumn !== false).
|
|
30
|
+
header: string;
|
|
31
|
+
kind: TableColumnKind;
|
|
32
|
+
/*
|
|
33
|
+
* When false, the metric/formula is still fetched (so formulas can
|
|
34
|
+
* reference it by variable) but it does not render a visible column.
|
|
35
|
+
* Default: true.
|
|
36
|
+
*/
|
|
37
|
+
showAsColumn?: boolean | undefined;
|
|
38
|
+
// kind === Metric:
|
|
39
|
+
metricName?: string | undefined;
|
|
40
|
+
aggregation?: MetricsAggregationType | undefined;
|
|
41
|
+
// kind === Formula: expression referencing other variables, e.g. "(a / b) * 100".
|
|
42
|
+
formula?: string | undefined;
|
|
43
|
+
// Per-cell formatting (both kinds).
|
|
44
|
+
decimals?: number | undefined;
|
|
45
|
+
/*
|
|
46
|
+
* Unit to render after each cell value (e.g. "%", "MB", "ms").
|
|
47
|
+
* Empty/undefined = Auto: for metric columns, falls back to the metric
|
|
48
|
+
* type's native unit; for formula columns, no unit is rendered.
|
|
49
|
+
*/
|
|
50
|
+
unit?: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TableGroupByAttribute {
|
|
54
|
+
// OTel attribute key, e.g. "host.name" or "resource.k8s.pod.name".
|
|
55
|
+
key: string;
|
|
56
|
+
// Optional user-friendly header. When unset, the key is shown as-is.
|
|
57
|
+
header?: string | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
6
60
|
export default interface DashboardTableComponent extends BaseComponent {
|
|
7
61
|
componentType: DashboardComponentType.Table;
|
|
8
62
|
componentId: ObjectID;
|
|
9
63
|
arguments: {
|
|
10
|
-
|
|
64
|
+
// New shape: columns + widget-level group-by.
|
|
65
|
+
columns?: Array<TableColumn> | undefined;
|
|
66
|
+
// New shape — attribute + optional custom header. Source of truth.
|
|
67
|
+
groupByAttributes?: Array<TableGroupByAttribute> | undefined;
|
|
68
|
+
/*
|
|
69
|
+
* Legacy shape from before per-attribute headers. Read at render
|
|
70
|
+
* time if `groupByAttributes` is absent. Never written by the editor.
|
|
71
|
+
*/
|
|
72
|
+
groupByAttributeKeys?: Array<string> | undefined;
|
|
73
|
+
// Common display options.
|
|
11
74
|
tableTitle?: string | undefined;
|
|
75
|
+
tableDescription?: string | undefined;
|
|
12
76
|
maxRows?: number | undefined;
|
|
77
|
+
reduce?: TableReduce | undefined;
|
|
78
|
+
decimals?: number | undefined;
|
|
79
|
+
/*
|
|
80
|
+
* Legacy widget shape from before the columns/groupByAttributes
|
|
81
|
+
* redesign — read-only at render time, never written by the editor.
|
|
82
|
+
*/
|
|
83
|
+
metricQueryConfig?: MetricQueryConfigData | undefined;
|
|
84
|
+
metricQueryConfigs?: Array<MetricQueryConfigData> | undefined;
|
|
85
|
+
metricFormulaConfigs?: Array<MetricFormulaConfigData> | undefined;
|
|
13
86
|
};
|
|
14
87
|
}
|