@oneuptime/common 11.7.3 → 11.7.4

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 (365) hide show
  1. package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
  2. package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
  3. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  4. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  5. package/Models/DatabaseModels/Index.ts +5 -0
  6. package/Models/DatabaseModels/LogDropFilter.ts +89 -0
  7. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
  8. package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
  9. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  10. package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
  11. package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
  12. package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
  13. package/Server/API/AlertAPI.ts +32 -17
  14. package/Server/API/BaseAPI.ts +31 -2
  15. package/Server/API/BaseAnalyticsAPI.ts +36 -2
  16. package/Server/API/CommonAPI.ts +57 -0
  17. package/Server/API/IncidentAPI.ts +60 -33
  18. package/Server/API/IncidentEpisodeAPI.ts +31 -16
  19. package/Server/API/MonitorAPI.ts +30 -0
  20. package/Server/API/MonitorTemplateAPI.ts +8 -0
  21. package/Server/API/ProbeAPI.ts +196 -15
  22. package/Server/API/ProjectAPI.ts +79 -1
  23. package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
  24. package/Server/API/StatusPageAPI.ts +74 -37
  25. package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  31. package/Server/Services/AlertEpisodeService.ts +47 -0
  32. package/Server/Services/AlertService.ts +125 -0
  33. package/Server/Services/BillingService.ts +98 -0
  34. package/Server/Services/DatabaseService.ts +184 -20
  35. package/Server/Services/IncidentEpisodeService.ts +47 -0
  36. package/Server/Services/IncidentService.ts +123 -0
  37. package/Server/Services/IncidentTemplateService.ts +123 -1
  38. package/Server/Services/Index.ts +2 -0
  39. package/Server/Services/LogDropFilterService.ts +133 -0
  40. package/Server/Services/MonitorProbeService.ts +94 -4
  41. package/Server/Services/MonitorService.ts +48 -43
  42. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
  43. package/Server/Services/ProbeService.ts +66 -0
  44. package/Server/Services/ProjectService.ts +131 -7
  45. package/Server/Services/RecommendationDismissalService.ts +52 -0
  46. package/Server/Services/RunbookAgentJobService.ts +68 -7
  47. package/Server/Services/ScheduledMaintenanceService.ts +103 -0
  48. package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
  49. package/Server/Services/StatusPageGroupService.ts +186 -0
  50. package/Server/Services/TraceDropFilterService.ts +133 -0
  51. package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
  52. package/Server/Types/Workflow/Components/API/Get.ts +12 -0
  53. package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
  54. package/Server/Types/Workflow/Components/API/Post.ts +12 -0
  55. package/Server/Types/Workflow/Components/API/Put.ts +13 -0
  56. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
  57. package/Server/Utils/DropFilterValidation.ts +127 -0
  58. package/Server/Utils/Errors.ts +5 -0
  59. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  60. package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
  61. package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
  62. package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
  63. package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
  64. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
  65. package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
  66. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
  67. package/Tests/Server/API/ProbeAPI.test.ts +422 -53
  68. package/Tests/Server/API/ProjectAPI.test.ts +180 -0
  69. package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
  70. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
  71. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
  72. package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
  73. package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
  74. package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
  75. package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
  76. package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
  77. package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
  78. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
  79. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
  80. package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
  81. package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
  82. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
  83. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
  84. package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
  85. package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
  86. package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
  87. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
  88. package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
  89. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
  90. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
  91. package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
  92. package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
  93. package/Tests/Types/DateUserTimezone.test.ts +52 -0
  94. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
  95. package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
  96. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
  97. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
  98. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
  99. package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
  100. package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
  101. package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
  102. package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
  103. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
  104. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
  105. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
  106. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
  107. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
  108. package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
  109. package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
  110. package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
  111. package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
  112. package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
  113. package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
  114. package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
  115. package/Tests/UI/Components/InfoCard.test.tsx +358 -0
  116. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
  117. package/Tests/UI/Components/Modal.test.tsx +43 -5
  118. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
  119. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +427 -0
  120. package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
  121. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
  122. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
  123. package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
  124. package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
  125. package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
  126. package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
  127. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
  128. package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
  129. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
  130. package/Types/Date.ts +15 -6
  131. package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
  132. package/Types/Monitor/MonitorStep.ts +10 -1
  133. package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
  134. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
  135. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
  136. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
  137. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
  138. package/Types/Permission.ts +45 -0
  139. package/Types/Recommendation/RecommendationType.ts +20 -0
  140. package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
  141. package/Types/Runbook/RunbookStepTimeout.ts +202 -0
  142. package/Types/Telemetry/DropFilterSampling.ts +92 -0
  143. package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
  144. package/UI/Components/Accordion/Accordion.tsx +10 -2
  145. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
  146. package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
  147. package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
  148. package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
  149. package/UI/Components/Error/PageError.tsx +0 -4
  150. package/UI/Components/ErrorBoundary.tsx +252 -15
  151. package/UI/Components/InfoCard/InfoCard.tsx +58 -1
  152. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
  153. package/UI/Components/Modal/Modal.tsx +16 -60
  154. package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
  155. package/UI/Components/ModelTable/BaseModelTable.tsx +116 -67
  156. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +150 -47
  157. package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
  158. package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
  159. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
  160. package/UI/Utils/DropdownAlignment.ts +162 -0
  161. package/UI/Utils/TableFilterUrlState.ts +59 -20
  162. package/Utils/API.ts +106 -2
  163. package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
  164. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
  165. package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
  166. package/Utils/StatusPage/GroupTree.ts +307 -0
  167. package/Utils/StatusPage/ResourceUptime.ts +90 -17
  168. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
  169. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
  170. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
  171. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  172. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  173. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  174. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  175. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  176. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  177. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  178. package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
  179. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  180. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
  181. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  182. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
  183. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
  184. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  185. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
  187. package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
  189. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
  191. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  192. package/build/dist/Server/API/AlertAPI.js +22 -13
  193. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  194. package/build/dist/Server/API/BaseAPI.js +22 -1
  195. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  196. package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
  197. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  198. package/build/dist/Server/API/CommonAPI.js +42 -0
  199. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  200. package/build/dist/Server/API/IncidentAPI.js +43 -26
  201. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  202. package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
  203. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  204. package/build/dist/Server/API/MonitorAPI.js +23 -0
  205. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  206. package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
  207. package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
  208. package/build/dist/Server/API/ProbeAPI.js +131 -15
  209. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  210. package/build/dist/Server/API/ProjectAPI.js +62 -3
  211. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  212. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
  213. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  214. package/build/dist/Server/API/StatusPageAPI.js +61 -33
  215. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  216. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
  217. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
  218. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
  219. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
  220. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
  221. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  228. package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
  229. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  230. package/build/dist/Server/Services/AlertService.js +95 -0
  231. package/build/dist/Server/Services/AlertService.js.map +1 -1
  232. package/build/dist/Server/Services/BillingService.js +71 -0
  233. package/build/dist/Server/Services/BillingService.js.map +1 -1
  234. package/build/dist/Server/Services/DatabaseService.js +140 -17
  235. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  236. package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
  237. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  238. package/build/dist/Server/Services/IncidentService.js +100 -0
  239. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  240. package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
  241. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  242. package/build/dist/Server/Services/Index.js +2 -0
  243. package/build/dist/Server/Services/Index.js.map +1 -1
  244. package/build/dist/Server/Services/LogDropFilterService.js +93 -0
  245. package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
  246. package/build/dist/Server/Services/MonitorProbeService.js +73 -5
  247. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  248. package/build/dist/Server/Services/MonitorService.js +34 -39
  249. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  250. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
  251. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  252. package/build/dist/Server/Services/ProbeService.js +64 -0
  253. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  254. package/build/dist/Server/Services/ProjectService.js +103 -7
  255. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  256. package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
  257. package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
  258. package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
  259. package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
  260. package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
  261. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  262. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
  263. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  264. package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
  265. package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
  266. package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
  267. package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
  268. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
  269. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  270. package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
  271. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  272. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
  273. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  274. package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
  275. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  276. package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
  277. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  278. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
  279. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
  280. package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
  281. package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
  282. package/build/dist/Server/Utils/Errors.js +3 -0
  283. package/build/dist/Server/Utils/Errors.js.map +1 -1
  284. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
  285. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  286. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
  287. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  288. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
  289. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  290. package/build/dist/Types/Date.js +14 -4
  291. package/build/dist/Types/Date.js.map +1 -1
  292. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
  293. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  294. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  295. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  296. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
  297. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  298. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
  299. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
  300. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
  301. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
  302. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
  303. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
  304. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
  305. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
  306. package/build/dist/Types/Permission.js +40 -0
  307. package/build/dist/Types/Permission.js.map +1 -1
  308. package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
  309. package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
  310. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
  311. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
  312. package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
  313. package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
  314. package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
  315. package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
  316. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
  317. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  318. package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
  319. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  320. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
  321. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  322. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
  323. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  324. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
  325. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
  326. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
  327. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  328. package/build/dist/UI/Components/Error/PageError.js +1 -3
  329. package/build/dist/UI/Components/Error/PageError.js.map +1 -1
  330. package/build/dist/UI/Components/ErrorBoundary.js +145 -11
  331. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  332. package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
  333. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  334. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
  335. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  336. package/build/dist/UI/Components/Modal/Modal.js +4 -32
  337. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  338. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  339. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +69 -33
  340. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  341. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +73 -32
  342. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  343. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
  344. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
  345. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
  346. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
  347. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
  348. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  349. package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
  350. package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
  351. package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
  352. package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
  353. package/build/dist/Utils/API.js +39 -2
  354. package/build/dist/Utils/API.js.map +1 -1
  355. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
  356. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
  357. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
  358. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
  359. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
  360. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
  361. package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
  362. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
  363. package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
  364. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  365. package/package.json +1 -1
@@ -0,0 +1,743 @@
1
+ import ObjectID from "../../ObjectID";
2
+ import MetricsViewConfig from "../../Metrics/MetricsViewConfig";
3
+ import MetricQueryConfigData from "../../Metrics/MetricQueryConfigData";
4
+ import MetricFormulaConfigData from "../../Metrics/MetricFormulaConfigData";
5
+ import MonitorStep from "../MonitorStep";
6
+ import MonitorSteps from "../MonitorSteps";
7
+ import MonitorCriteriaInstance from "../MonitorCriteriaInstance";
8
+ import { CriteriaIncident } from "../CriteriaIncident";
9
+ import { CriteriaAlert } from "../CriteriaAlert";
10
+ import MonitorRecommendationSeverityMapper from "./MonitorRecommendationSeverityMapper";
11
+ import {
12
+ MonitorRecommendation,
13
+ MonitorRecommendationArgs,
14
+ MonitorRecommendationNotificationMode,
15
+ MonitorRecommendationNotificationSettings,
16
+ MonitorRecommendationSeverity,
17
+ } from "./MonitorRecommendationTypes";
18
+
19
+ /*
20
+ * The structural slice of every `MonitorStep<X>Monitor` this module reads.
21
+ *
22
+ * All eight infrastructure step configs carry a `metricViewConfig` plus
23
+ * exactly one identifier field, whose name varies by resource type
24
+ * (see `MonitorRecommendationTypes.MonitorRecommendationArgs`). Reading all
25
+ * three optional names keeps the fingerprint logic resource-type-agnostic
26
+ * without needing a per-type switch.
27
+ */
28
+ interface InfrastructureMonitorStepConfig {
29
+ clusterIdentifier?: string | undefined;
30
+ hostIdentifier?: string | undefined;
31
+ fleetIdentifier?: string | undefined;
32
+ metricViewConfig?: MetricsViewConfig | undefined;
33
+ }
34
+
35
+ /*
36
+ * What uniquely identifies "this alert is already being watched", derived
37
+ * from a monitor step rather than from the monitor's name.
38
+ *
39
+ * Name matching was the obvious alternative and is wrong: renaming a monitor
40
+ * would make an already-created recommendation reappear as un-created, and
41
+ * two different templates on the same resource can be renamed into collision.
42
+ * What a step actually WATCHES is the right identity, and every component
43
+ * below is deterministic per template.
44
+ *
45
+ * The first version of this used only (resourceIdentifier, metricNames,
46
+ * formulas), which turned out to be far too coarse: thirteen groups of
47
+ * genuinely different templates fingerprinted identically. Eight of the Ceph
48
+ * templates all query the single metric `ceph_health_detail` and differ only
49
+ * by the health-check name they filter on, so creating "Ceph Daemon Crash"
50
+ * marked four unrelated recommendations — clock skew, OSD nearfull,
51
+ * backfillfull, full — as "already created" and silently hid four real
52
+ * monitoring gaps. "Cluster Near Full" hid "Cluster Full" the same way. The
53
+ * failure is invisible by construction: the cards render as handled.
54
+ *
55
+ * So the fingerprint now carries everything that distinguishes one shipped
56
+ * template from another:
57
+ *
58
+ * configKind which infrastructure config the step carries. Docker,
59
+ * Docker Swarm and Podman ship byte-identical metric sets
60
+ * ("container.cpu.utilization"), and while the page never
61
+ * mixes resource types today, nothing in this function
62
+ * enforced that.
63
+ * metricAliases the per-query variable name. Author-chosen per template
64
+ * ("recent_crash", "mon_clock_skew"), and the thing the
65
+ * criteria filters actually reference.
66
+ * queryAttributes the attribute filter on each query, e.g.
67
+ * { name: "PG_DAMAGED" } — the only difference between
68
+ * several Ceph health-detail templates.
69
+ * criteriaFilters the thresholds. The last resort, and the only thing that
70
+ * separates "near full" (85%) from "full" (95%).
71
+ *
72
+ * Including thresholds has a cost worth stating: retuning a created monitor
73
+ * from 90% to 85% makes its recommendation resurface as not-yet-created. That
74
+ * is the correct reading — the monitor no longer watches what the
75
+ * recommendation describes — it is visible, and it is dismissable. Silently
76
+ * hiding a Critical recommendation is neither.
77
+ *
78
+ * Existing monitors keep matching: none of these components is touched by the
79
+ * notification settings the create flow writes, so a monitor created before
80
+ * this change still fingerprints to its own template.
81
+ */
82
+ export interface MonitorRecommendationFingerprint {
83
+ resourceIdentifier: string;
84
+ configKind: string;
85
+ metricNames: Array<string>;
86
+ formulas: Array<string>;
87
+ metricAliases: Array<string>;
88
+ queryAttributes: Array<string>;
89
+ criteriaFilters: Array<string>;
90
+ }
91
+
92
+ export default class MonitorRecommendationUtil {
93
+ /*
94
+ * Deterministic monitor name for a recommendation applied to a resource.
95
+ *
96
+ * Used both as the created monitor's name and as the `monitorName` arg the
97
+ * template modules interpolate into their incident/alert titles.
98
+ */
99
+ public static getMonitorName(data: {
100
+ recommendation: MonitorRecommendation;
101
+ resourceDisplayName: string;
102
+ }): string {
103
+ const resourceName: string = data.resourceDisplayName.trim();
104
+
105
+ if (!resourceName) {
106
+ return data.recommendation.name;
107
+ }
108
+
109
+ return `${resourceName} - ${data.recommendation.name}`;
110
+ }
111
+
112
+ /*
113
+ * Push the user's chosen on-call policies, owners, labels, severities and
114
+ * incident-vs-alert choice into every criteria instance in a step.
115
+ *
116
+ * This is the whole reason the create form asks for on-call policies: every
117
+ * shipped template hardcodes `onCallPolicyIds: []`, so a template-created
118
+ * monitor would open incidents that page nobody. Templates are the source
119
+ * of the thresholds; this is the source of "who hears about it, how loudly,
120
+ * and through which record".
121
+ *
122
+ * `severity` is the RECOMMENDATION's severity (`Critical` / `Warning`), not
123
+ * a project severity. It selects which project severity to write, via the
124
+ * caller's mapping — see `MonitorRecommendationSeverityMapper`.
125
+ *
126
+ * Mutates and returns the step (the step was just built by the template and
127
+ * is not shared with anything else).
128
+ */
129
+ public static applyNotificationSettingsToMonitorStep(data: {
130
+ monitorStep: MonitorStep;
131
+ notificationSettings: MonitorRecommendationNotificationSettings;
132
+ severity: MonitorRecommendationSeverity;
133
+ }): MonitorStep {
134
+ const settings: MonitorRecommendationNotificationSettings =
135
+ data.notificationSettings;
136
+
137
+ const incidentSeverityId: ObjectID | undefined =
138
+ MonitorRecommendationSeverityMapper.resolveSeverityId({
139
+ severity: data.severity,
140
+ severityMap: settings.incidentSeverityIdBySeverity,
141
+ });
142
+
143
+ const alertSeverityId: ObjectID | undefined =
144
+ MonitorRecommendationSeverityMapper.resolveSeverityId({
145
+ severity: data.severity,
146
+ severityMap: settings.alertSeverityIdBySeverity,
147
+ });
148
+
149
+ const criteriaInstances: Array<MonitorCriteriaInstance> =
150
+ data.monitorStep.data?.monitorCriteria?.data
151
+ ?.monitorCriteriaInstanceArray || [];
152
+
153
+ for (const criteriaInstance of criteriaInstances) {
154
+ if (!criteriaInstance.data) {
155
+ continue;
156
+ }
157
+
158
+ for (const incident of criteriaInstance.data.incidents || []) {
159
+ this.applyToCriteriaIncident(incident, settings, incidentSeverityId);
160
+ }
161
+
162
+ for (const alert of criteriaInstance.data.alerts || []) {
163
+ this.applyToCriteriaAlert(alert, settings, alertSeverityId);
164
+ }
165
+
166
+ this.applyNotificationModeToCriteriaInstance(
167
+ criteriaInstance,
168
+ settings.notificationMode,
169
+ );
170
+ }
171
+
172
+ return data.monitorStep;
173
+ }
174
+
175
+ /*
176
+ * Turn the user's Alert / Incident / Both choice into the two flags the
177
+ * monitor evaluator actually reads (`MonitorIncident.ts` and
178
+ * `MonitorAlert.ts` both early-return unless the flag is true).
179
+ *
180
+ * Two things this deliberately does NOT do:
181
+ *
182
+ * 1. It never flips a flag on a criteria instance whose corresponding
183
+ * array is empty. Every template ships a "Healthy" recovery criteria
184
+ * with `incidents: []` and `createIncidents: false`; setting the flag
185
+ * there would mark it as incident-creating, which the auto-resolve path
186
+ * reads as "this criteria contributes breaches" and would stop
187
+ * recovered monitors from resolving their own incidents.
188
+ *
189
+ * 2. It never empties the arrays of the mode that was NOT chosen. The
190
+ * config stays on the monitor, inert, exactly like the monitor criteria
191
+ * form's own toggles behave — so a user who later decides they do want
192
+ * incidents flips one switch on the monitor instead of re-authoring the
193
+ * incident title, description and severity.
194
+ */
195
+ private static applyNotificationModeToCriteriaInstance(
196
+ criteriaInstance: MonitorCriteriaInstance,
197
+ notificationMode: MonitorRecommendationNotificationMode | undefined,
198
+ ): void {
199
+ if (!notificationMode || !criteriaInstance.data) {
200
+ return;
201
+ }
202
+
203
+ const shouldCreateIncidents: boolean =
204
+ notificationMode === MonitorRecommendationNotificationMode.Incident ||
205
+ notificationMode === MonitorRecommendationNotificationMode.Both;
206
+
207
+ const shouldCreateAlerts: boolean =
208
+ notificationMode === MonitorRecommendationNotificationMode.Alert ||
209
+ notificationMode === MonitorRecommendationNotificationMode.Both;
210
+
211
+ if ((criteriaInstance.data.incidents || []).length > 0) {
212
+ criteriaInstance.data.createIncidents = shouldCreateIncidents;
213
+ }
214
+
215
+ if ((criteriaInstance.data.alerts || []).length > 0) {
216
+ criteriaInstance.data.createAlerts = shouldCreateAlerts;
217
+ }
218
+ }
219
+
220
+ private static applyToCriteriaIncident(
221
+ incident: CriteriaIncident,
222
+ settings: MonitorRecommendationNotificationSettings,
223
+ incidentSeverityId: ObjectID | undefined,
224
+ ): void {
225
+ if (settings.onCallPolicyIds && settings.onCallPolicyIds.length > 0) {
226
+ incident.onCallPolicyIds = [...settings.onCallPolicyIds];
227
+ }
228
+
229
+ if (settings.labelIds && settings.labelIds.length > 0) {
230
+ incident.labelIds = [...settings.labelIds];
231
+ }
232
+
233
+ if (settings.ownerTeamIds && settings.ownerTeamIds.length > 0) {
234
+ incident.ownerTeamIds = [...settings.ownerTeamIds];
235
+ }
236
+
237
+ if (settings.ownerUserIds && settings.ownerUserIds.length > 0) {
238
+ incident.ownerUserIds = [...settings.ownerUserIds];
239
+ }
240
+
241
+ if (incidentSeverityId) {
242
+ incident.incidentSeverityId = incidentSeverityId;
243
+ }
244
+ }
245
+
246
+ private static applyToCriteriaAlert(
247
+ alert: CriteriaAlert,
248
+ settings: MonitorRecommendationNotificationSettings,
249
+ alertSeverityId: ObjectID | undefined,
250
+ ): void {
251
+ if (settings.onCallPolicyIds && settings.onCallPolicyIds.length > 0) {
252
+ alert.onCallPolicyIds = [...settings.onCallPolicyIds];
253
+ }
254
+
255
+ if (settings.labelIds && settings.labelIds.length > 0) {
256
+ alert.labelIds = [...settings.labelIds];
257
+ }
258
+
259
+ if (settings.ownerTeamIds && settings.ownerTeamIds.length > 0) {
260
+ alert.ownerTeamIds = [...settings.ownerTeamIds];
261
+ }
262
+
263
+ if (settings.ownerUserIds && settings.ownerUserIds.length > 0) {
264
+ alert.ownerUserIds = [...settings.ownerUserIds];
265
+ }
266
+
267
+ if (alertSeverityId) {
268
+ alert.alertSeverityId = alertSeverityId;
269
+ }
270
+ }
271
+
272
+ /*
273
+ * Build the complete `MonitorSteps` for one recommendation, ready to hang
274
+ * off a `Monitor` and POST.
275
+ *
276
+ * `defaultMonitorStatusId` is separate from the online/offline ids in
277
+ * `args` because `MonitorSteps.getValidationError` requires it and the
278
+ * template modules never set it — they only produce the inner step.
279
+ */
280
+ public static buildMonitorSteps(data: {
281
+ recommendation: MonitorRecommendation;
282
+ args: MonitorRecommendationArgs;
283
+ defaultMonitorStatusId: ObjectID;
284
+ notificationSettings?:
285
+ | MonitorRecommendationNotificationSettings
286
+ | undefined;
287
+ }): MonitorSteps {
288
+ const monitorStep: MonitorStep = data.recommendation.getMonitorStep(
289
+ data.args,
290
+ );
291
+
292
+ if (data.notificationSettings) {
293
+ this.applyNotificationSettingsToMonitorStep({
294
+ monitorStep: monitorStep,
295
+ notificationSettings: data.notificationSettings,
296
+ severity: data.recommendation.severity,
297
+ });
298
+ }
299
+
300
+ const monitorSteps: MonitorSteps = new MonitorSteps();
301
+
302
+ monitorSteps.data = {
303
+ monitorStepsInstanceArray: [monitorStep],
304
+ defaultMonitorStatusId: data.defaultMonitorStatusId,
305
+ };
306
+
307
+ return monitorSteps;
308
+ }
309
+
310
+ /*
311
+ * Reduce a monitor step to what it watches. Returns undefined for steps that
312
+ * carry no infrastructure config (an HTTP monitor step, say) — those can
313
+ * never match a recommendation.
314
+ *
315
+ * See `MonitorRecommendationFingerprint` for why each component is here.
316
+ */
317
+ public static getFingerprintFromMonitorStep(
318
+ monitorStep: MonitorStep,
319
+ ): MonitorRecommendationFingerprint | undefined {
320
+ const configKind: string | undefined =
321
+ this.getInfrastructureConfigKind(monitorStep);
322
+
323
+ if (!configKind) {
324
+ return undefined;
325
+ }
326
+
327
+ const config: InfrastructureMonitorStepConfig = (
328
+ monitorStep.data as unknown as Record<
329
+ string,
330
+ InfrastructureMonitorStepConfig
331
+ >
332
+ )[configKind]!;
333
+
334
+ const resourceIdentifier: string =
335
+ config.clusterIdentifier ||
336
+ config.hostIdentifier ||
337
+ config.fleetIdentifier ||
338
+ "";
339
+
340
+ const metricNames: Array<string> = [];
341
+ const metricAliases: Array<string> = [];
342
+ const queryAttributes: Array<string> = [];
343
+ const formulas: Array<string> = [];
344
+
345
+ for (const queryConfig of config.metricViewConfig?.queryConfigs || []) {
346
+ const metricName: string | undefined =
347
+ this.getMetricNameFromQueryConfig(queryConfig);
348
+
349
+ if (metricName) {
350
+ metricNames.push(metricName);
351
+ }
352
+
353
+ const metricAlias: string | undefined =
354
+ queryConfig.metricAliasData?.metricVariable;
355
+
356
+ if (metricAlias) {
357
+ metricAliases.push(metricAlias);
358
+ }
359
+
360
+ queryAttributes.push(this.getQueryAttributeKey(queryConfig));
361
+ }
362
+
363
+ for (const formulaConfig of config.metricViewConfig?.formulaConfigs || []) {
364
+ const formula: string | undefined =
365
+ this.getFormulaFromFormulaConfig(formulaConfig);
366
+
367
+ if (formula) {
368
+ formulas.push(formula);
369
+ }
370
+ }
371
+
372
+ return {
373
+ resourceIdentifier: resourceIdentifier,
374
+ configKind: configKind,
375
+ /*
376
+ * Every list is sorted so that declaration order inside a template is
377
+ * not part of the identity — a template author reordering two queries
378
+ * must not orphan every monitor already created from it.
379
+ */
380
+ metricNames: metricNames.sort(),
381
+ formulas: formulas.sort(),
382
+ metricAliases: metricAliases.sort(),
383
+ queryAttributes: queryAttributes.sort(),
384
+ criteriaFilters: this.getCriteriaFilterKeys(monitorStep).sort(),
385
+ };
386
+ }
387
+
388
+ /*
389
+ * Which infrastructure config the step carries, as the property name.
390
+ *
391
+ * Returned instead of the config object itself because the NAME is part of
392
+ * the fingerprint: Docker, Docker Swarm and Podman ship structurally
393
+ * identical metric configs, so a Docker monitor and a Podman monitor
394
+ * watching container CPU are indistinguishable by contents alone.
395
+ */
396
+ private static getInfrastructureConfigKind(
397
+ monitorStep: MonitorStep,
398
+ ): string | undefined {
399
+ if (!monitorStep.data) {
400
+ return undefined;
401
+ }
402
+
403
+ const kinds: Array<string> = [
404
+ "kubernetesMonitor",
405
+ "hostMonitor",
406
+ "dockerMonitor",
407
+ "dockerSwarmMonitor",
408
+ "podmanMonitor",
409
+ "proxmoxMonitor",
410
+ "cephMonitor",
411
+ "iotMonitor",
412
+ ];
413
+
414
+ const data: Record<string, unknown> = monitorStep.data as unknown as Record<
415
+ string,
416
+ unknown
417
+ >;
418
+
419
+ return kinds.find((kind: string) => {
420
+ return Boolean(data[kind]);
421
+ });
422
+ }
423
+
424
+ /*
425
+ * The attribute filter on one query, as a stable string.
426
+ *
427
+ * Keys are sorted before serializing: `{ name: "X", severity: "Y" }` and
428
+ * `{ severity: "Y", name: "X" }` describe the same query, and JSON.stringify
429
+ * alone would call them different.
430
+ */
431
+ private static getQueryAttributeKey(
432
+ queryConfig: MetricQueryConfigData,
433
+ ): string {
434
+ const attributes: unknown =
435
+ queryConfig.metricQueryData?.filterData?.["attributes"];
436
+
437
+ if (!attributes || typeof attributes !== "object") {
438
+ return "";
439
+ }
440
+
441
+ const record: Record<string, unknown> = attributes as Record<
442
+ string,
443
+ unknown
444
+ >;
445
+
446
+ return Object.keys(record)
447
+ .sort()
448
+ .map((key: string) => {
449
+ return `${key}=${String(record[key])}`;
450
+ })
451
+ .join(",");
452
+ }
453
+
454
+ /*
455
+ * Every threshold the step evaluates, as stable strings.
456
+ *
457
+ * Covers all criteria instances, healthy and unhealthy alike: the recovery
458
+ * criteria's own threshold is equally part of what the step watches, and two
459
+ * templates that differ only in their recovery bound are still two different
460
+ * monitors.
461
+ *
462
+ * `metricAlias` is included because a multi-query step's filters are only
463
+ * meaningful against the series they name.
464
+ */
465
+ private static getCriteriaFilterKeys(
466
+ monitorStep: MonitorStep,
467
+ ): Array<string> {
468
+ const keys: Array<string> = [];
469
+
470
+ const criteriaInstances: Array<MonitorCriteriaInstance> =
471
+ monitorStep.data?.monitorCriteria?.data?.monitorCriteriaInstanceArray ||
472
+ [];
473
+
474
+ for (const criteriaInstance of criteriaInstances) {
475
+ for (const filter of criteriaInstance.data?.filters || []) {
476
+ keys.push(
477
+ [
478
+ String(filter.checkOn ?? ""),
479
+ String(filter.filterType ?? ""),
480
+ String(filter.value ?? ""),
481
+ String(filter.metricMonitorOptions?.metricAlias ?? ""),
482
+ ].join("|"),
483
+ );
484
+ }
485
+ }
486
+
487
+ return keys;
488
+ }
489
+
490
+ private static getMetricNameFromQueryConfig(
491
+ queryConfig: MetricQueryConfigData,
492
+ ): string | undefined {
493
+ const metricName: unknown = queryConfig.metricQueryData?.filterData?.[
494
+ "metricName"
495
+ ] as unknown;
496
+
497
+ if (typeof metricName === "string" && metricName) {
498
+ return metricName;
499
+ }
500
+
501
+ return undefined;
502
+ }
503
+
504
+ private static getFormulaFromFormulaConfig(
505
+ formulaConfig: MetricFormulaConfigData,
506
+ ): string | undefined {
507
+ const formula: string | undefined =
508
+ formulaConfig.metricFormulaData?.metricFormula;
509
+
510
+ if (typeof formula === "string" && formula) {
511
+ return formula;
512
+ }
513
+
514
+ return undefined;
515
+ }
516
+
517
+ /*
518
+ * A positional array rather than the object, so the serialization is stable
519
+ * regardless of the order the fields were written in. Every component of
520
+ * `MonitorRecommendationFingerprint` must appear here — a field added to the
521
+ * interface but forgotten below is invisible: the type checker is satisfied
522
+ * and the fingerprint silently keeps ignoring it, which is exactly the class
523
+ * of bug the interface's own comment describes.
524
+ */
525
+ public static serializeFingerprint(
526
+ fingerprint: MonitorRecommendationFingerprint,
527
+ ): string {
528
+ return JSON.stringify([
529
+ fingerprint.resourceIdentifier,
530
+ fingerprint.configKind,
531
+ fingerprint.metricNames,
532
+ fingerprint.formulas,
533
+ fingerprint.metricAliases,
534
+ fingerprint.queryAttributes,
535
+ fingerprint.criteriaFilters,
536
+ ]);
537
+ }
538
+
539
+ public static areFingerprintsEqual(
540
+ a: MonitorRecommendationFingerprint | undefined,
541
+ b: MonitorRecommendationFingerprint | undefined,
542
+ ): boolean {
543
+ if (!a || !b) {
544
+ return false;
545
+ }
546
+
547
+ return this.serializeFingerprint(a) === this.serializeFingerprint(b);
548
+ }
549
+
550
+ /*
551
+ * Which recommendations are already covered by monitors that exist.
552
+ *
553
+ * `existingMonitorSteps` is every step of every monitor already attached to
554
+ * this resource. A recommendation counts as covered when its freshly-built
555
+ * step fingerprints identically to one of them — i.e. same resource, same
556
+ * metrics, same formulas.
557
+ *
558
+ * `args` must use the same resource identifier the existing monitors were
559
+ * created with, otherwise nothing will ever match.
560
+ */
561
+ public static getCoveredRecommendationIds(data: {
562
+ recommendations: Array<MonitorRecommendation>;
563
+ existingMonitorSteps: Array<MonitorStep>;
564
+ args: MonitorRecommendationArgs;
565
+ }): Set<string> {
566
+ const existingFingerprints: Set<string> = new Set<string>();
567
+
568
+ for (const monitorStep of data.existingMonitorSteps) {
569
+ const fingerprint: MonitorRecommendationFingerprint | undefined =
570
+ this.getFingerprintFromMonitorStep(monitorStep);
571
+
572
+ if (fingerprint) {
573
+ existingFingerprints.add(this.serializeFingerprint(fingerprint));
574
+ }
575
+ }
576
+
577
+ const covered: Set<string> = new Set<string>();
578
+
579
+ if (existingFingerprints.size === 0) {
580
+ return covered;
581
+ }
582
+
583
+ for (const recommendation of data.recommendations) {
584
+ const fingerprint: string | undefined =
585
+ this.getSerializedFingerprintForRecommendation({
586
+ recommendation: recommendation,
587
+ args: data.args,
588
+ });
589
+
590
+ if (fingerprint && existingFingerprints.has(fingerprint)) {
591
+ covered.add(recommendation.recommendationId);
592
+ }
593
+ }
594
+
595
+ return covered;
596
+ }
597
+
598
+ /*
599
+ * Same diff as `getCoveredRecommendationIds`, but keeps WHICH monitor
600
+ * covers each recommendation instead of only that one does.
601
+ *
602
+ * The set-only version is enough to grey a card out, and that is all the
603
+ * page could do with it: a user looking at "Already created" had no way to
604
+ * get from the card to the monitor to check its thresholds, its on-call
605
+ * policy, or whether it is even enabled. Returning the id lets the card link
606
+ * there.
607
+ *
608
+ * When several monitors fingerprint identically — which happens when someone
609
+ * creates the same recommendation twice by hand — the FIRST is kept, matching
610
+ * the order the caller passed them in (the API sorts monitors, so this is
611
+ * stable across loads rather than whichever row Postgres returned first).
612
+ */
613
+ public static getCoveredRecommendationMonitorIds(data: {
614
+ recommendations: Array<MonitorRecommendation>;
615
+ existingMonitors: Array<{
616
+ monitorId: ObjectID;
617
+ monitorSteps: Array<MonitorStep>;
618
+ }>;
619
+ args: MonitorRecommendationArgs;
620
+ }): Map<string, ObjectID> {
621
+ const monitorIdByFingerprint: Map<string, ObjectID> = new Map<
622
+ string,
623
+ ObjectID
624
+ >();
625
+
626
+ for (const existingMonitor of data.existingMonitors) {
627
+ for (const monitorStep of existingMonitor.monitorSteps) {
628
+ const fingerprint: MonitorRecommendationFingerprint | undefined =
629
+ this.getFingerprintFromMonitorStep(monitorStep);
630
+
631
+ if (!fingerprint) {
632
+ continue;
633
+ }
634
+
635
+ const serialized: string = this.serializeFingerprint(fingerprint);
636
+
637
+ if (!monitorIdByFingerprint.has(serialized)) {
638
+ monitorIdByFingerprint.set(serialized, existingMonitor.monitorId);
639
+ }
640
+ }
641
+ }
642
+
643
+ const covered: Map<string, ObjectID> = new Map<string, ObjectID>();
644
+
645
+ if (monitorIdByFingerprint.size === 0) {
646
+ return covered;
647
+ }
648
+
649
+ for (const recommendation of data.recommendations) {
650
+ const fingerprint: string | undefined =
651
+ this.getSerializedFingerprintForRecommendation({
652
+ recommendation: recommendation,
653
+ args: data.args,
654
+ });
655
+
656
+ if (!fingerprint) {
657
+ continue;
658
+ }
659
+
660
+ const monitorId: ObjectID | undefined =
661
+ monitorIdByFingerprint.get(fingerprint);
662
+
663
+ if (monitorId) {
664
+ covered.set(recommendation.recommendationId, monitorId);
665
+ }
666
+ }
667
+
668
+ return covered;
669
+ }
670
+
671
+ /*
672
+ * Build a recommendation's step exactly as the create flow would, and reduce
673
+ * it to its serialized fingerprint.
674
+ *
675
+ * The `monitorName` recomputation matters: templates interpolate the monitor
676
+ * name into incident titles, and while the name is not part of the
677
+ * fingerprint, building the step with a different name than the create flow
678
+ * uses would be an easy way for the two paths to drift apart later.
679
+ */
680
+ private static getSerializedFingerprintForRecommendation(data: {
681
+ recommendation: MonitorRecommendation;
682
+ args: MonitorRecommendationArgs;
683
+ }): string | undefined {
684
+ const fingerprint: MonitorRecommendationFingerprint | undefined =
685
+ this.getFingerprintFromMonitorStep(
686
+ data.recommendation.getMonitorStep({
687
+ ...data.args,
688
+ monitorName: this.getMonitorName({
689
+ recommendation: data.recommendation,
690
+ resourceDisplayName: data.args.monitorName,
691
+ }),
692
+ }),
693
+ );
694
+
695
+ if (!fingerprint) {
696
+ return undefined;
697
+ }
698
+
699
+ return this.serializeFingerprint(fingerprint);
700
+ }
701
+
702
+ /*
703
+ * The recommendations worth showing first: not yet covered, Critical before
704
+ * Warning, otherwise the module's own declaration order (which puts the
705
+ * most important templates first).
706
+ */
707
+ public static sortRecommendations(data: {
708
+ recommendations: Array<MonitorRecommendation>;
709
+ coveredRecommendationIds: Set<string>;
710
+ }): Array<MonitorRecommendation> {
711
+ const indexOf: Map<string, number> = new Map<string, number>();
712
+
713
+ data.recommendations.forEach(
714
+ (recommendation: MonitorRecommendation, index: number) => {
715
+ indexOf.set(recommendation.recommendationId, index);
716
+ },
717
+ );
718
+
719
+ return [...data.recommendations].sort(
720
+ (a: MonitorRecommendation, b: MonitorRecommendation) => {
721
+ const aCovered: boolean = data.coveredRecommendationIds.has(
722
+ a.recommendationId,
723
+ );
724
+ const bCovered: boolean = data.coveredRecommendationIds.has(
725
+ b.recommendationId,
726
+ );
727
+
728
+ if (aCovered !== bCovered) {
729
+ return aCovered ? 1 : -1;
730
+ }
731
+
732
+ if (a.severity !== b.severity) {
733
+ return a.severity === "Critical" ? -1 : 1;
734
+ }
735
+
736
+ return (
737
+ (indexOf.get(a.recommendationId) ?? 0) -
738
+ (indexOf.get(b.recommendationId) ?? 0)
739
+ );
740
+ },
741
+ );
742
+ }
743
+ }