@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.
Files changed (117) hide show
  1. package/Models/DatabaseModels/Alert.ts +332 -0
  2. package/Models/DatabaseModels/Incident.ts +332 -0
  3. package/Models/DatabaseModels/RunbookAgent.ts +16 -2
  4. package/Server/EnvironmentConfig.ts +16 -0
  5. package/Server/Infrastructure/ClickhouseConfig.ts +14 -0
  6. package/Server/Infrastructure/ClickhouseDatabase.ts +20 -3
  7. package/Server/Infrastructure/InMemoryTTLCache.ts +61 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1779302536475-AttachKubernetesAndDockerToIncidentAndAlert.ts +253 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1779303924241-AttachServiceToIncidentAndAlert.ts +60 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  11. package/Server/Middleware/TelemetryIngest.ts +6 -38
  12. package/Server/Middleware/UserAuthorization.ts +1 -11
  13. package/Server/Services/AnalyticsDatabaseService.ts +33 -1
  14. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +6 -0
  15. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +1 -0
  16. package/Server/Services/OnCallDutyPolicyScheduleService.ts +17 -0
  17. package/Server/Services/TelemetryIngestionKeyService.ts +90 -1
  18. package/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.ts +108 -0
  19. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
  20. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +25 -0
  21. package/Tests/Server/Middleware/UserAuthorization.test.ts +0 -7
  22. package/Types/Dashboard/DashboardComponentType.ts +0 -1
  23. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +2 -0
  24. package/Types/Dashboard/DashboardComponents/DashboardTableComponent.ts +74 -1
  25. package/Types/Dashboard/DashboardTemplates.ts +164 -51
  26. package/Types/Monitor/CriteriaFilter.ts +13 -0
  27. package/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.ts +69 -0
  28. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  29. package/Types/Monitor/MonitorStep.ts +39 -0
  30. package/Types/Monitor/MonitorStepDnssecMonitor.ts +59 -0
  31. package/Types/Monitor/MonitorType.ts +18 -2
  32. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +36 -9
  33. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  34. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +51 -0
  35. package/Utils/Dashboard/Components/DashboardTableComponent.ts +80 -17
  36. package/Utils/Dashboard/Components/Index.ts +0 -7
  37. package/Utils/Monitor/MonitorMetricType.ts +1 -0
  38. package/build/dist/Models/DatabaseModels/Alert.js +324 -0
  39. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  40. package/build/dist/Models/DatabaseModels/Incident.js +324 -0
  41. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  42. package/build/dist/Models/DatabaseModels/RunbookAgent.js +16 -2
  43. package/build/dist/Models/DatabaseModels/RunbookAgent.js.map +1 -1
  44. package/build/dist/Server/EnvironmentConfig.js +8 -0
  45. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  46. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +9 -1
  47. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  48. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +12 -3
  49. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  50. package/build/dist/Server/Infrastructure/InMemoryTTLCache.js +49 -0
  51. package/build/dist/Server/Infrastructure/InMemoryTTLCache.js.map +1 -0
  52. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779302536475-AttachKubernetesAndDockerToIncidentAndAlert.js +100 -0
  53. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779302536475-AttachKubernetesAndDockerToIncidentAndAlert.js.map +1 -0
  54. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779303924241-AttachServiceToIncidentAndAlert.js +28 -0
  55. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779303924241-AttachServiceToIncidentAndAlert.js.map +1 -0
  56. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  58. package/build/dist/Server/Middleware/TelemetryIngest.js +2 -26
  59. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  60. package/build/dist/Server/Middleware/UserAuthorization.js +1 -7
  61. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  62. package/build/dist/Server/Services/AnalyticsDatabaseService.js +23 -2
  63. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  64. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +28 -24
  65. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  66. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +1 -1
  67. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  68. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +18 -2
  69. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  70. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +83 -0
  71. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  72. package/build/dist/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.js +94 -0
  73. package/build/dist/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.js.map +1 -0
  74. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  75. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  76. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -3
  77. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  78. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +0 -7
  79. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
  80. package/build/dist/Types/Dashboard/DashboardComponentType.js +0 -1
  81. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  82. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +2 -0
  83. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  84. package/build/dist/Types/Dashboard/DashboardComponents/DashboardTableComponent.js +13 -1
  85. package/build/dist/Types/Dashboard/DashboardComponents/DashboardTableComponent.js.map +1 -1
  86. package/build/dist/Types/Dashboard/DashboardTemplates.js +142 -42
  87. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  88. package/build/dist/Types/Monitor/CriteriaFilter.js +12 -0
  89. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  90. package/build/dist/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.js +2 -0
  91. package/build/dist/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.js.map +1 -0
  92. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  93. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  94. package/build/dist/Types/Monitor/MonitorStep.js +26 -0
  95. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  96. package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js +42 -0
  97. package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js.map +1 -0
  98. package/build/dist/Types/Monitor/MonitorType.js +16 -2
  99. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  100. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +27 -7
  101. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
  102. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +47 -0
  103. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  104. package/build/dist/Utils/Dashboard/Components/DashboardTableComponent.js +68 -16
  105. package/build/dist/Utils/Dashboard/Components/DashboardTableComponent.js.map +1 -1
  106. package/build/dist/Utils/Dashboard/Components/Index.js +0 -4
  107. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  108. package/build/dist/Utils/Monitor/MonitorMetricType.js +1 -0
  109. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  110. package/package.json +1 -2
  111. package/Types/Dashboard/DashboardComponents/DashboardHostMetricChartComponent.ts +0 -27
  112. package/Typings/elkjs.d.ts +0 -30
  113. package/Utils/Dashboard/Components/DashboardHostMetricChartComponent.ts +0 -132
  114. package/build/dist/Types/Dashboard/DashboardComponents/DashboardHostMetricChartComponent.js +0 -11
  115. package/build/dist/Types/Dashboard/DashboardComponents/DashboardHostMetricChartComponent.js.map +0 -1
  116. package/build/dist/Utils/Dashboard/Components/DashboardHostMetricChartComponent.js +0 -113
  117. 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.getDatabaseClient();
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 { OnCreate } from "../Types/Database/Hooks";
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);
@@ -23,7 +23,6 @@ enum DashboardComponentType {
23
23
  DockerNetworkList = `DockerNetworkList`,
24
24
  DockerVolumeList = `DockerVolumeList`,
25
25
  HostList = `HostList`,
26
- HostMetricChart = `HostMetricChart`,
27
26
  }
28
27
 
29
28
  export default DashboardComponentType;
@@ -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
- metricQueryConfig?: MetricQueryConfigData | undefined;
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
  }