@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
@@ -70,6 +70,9 @@ import WorkspaceNotificationRuleService, {
70
70
  MessageBlocksByWorkspaceType,
71
71
  } from "./WorkspaceNotificationRuleService";
72
72
  import MonitorStepsProjectValidator from "../Utils/Monitor/MonitorStepsProjectValidator";
73
+ import ProjectScopedReferenceValidator, {
74
+ resolveReferenceId,
75
+ } from "../Utils/Database/ProjectScopedReferenceValidator";
73
76
  import MonitorWorkspaceMessages from "../Utils/Workspace/WorkspaceMessages/Monitor";
74
77
  import MonitorFeedService from "./MonitorFeedService";
75
78
  import { MonitorFeedEventType } from "../../Models/DatabaseModels/MonitorFeed";
@@ -388,7 +391,19 @@ export class Service extends DatabaseService<Model> {
388
391
  protected override async onBeforeUpdate(
389
392
  updateBy: UpdateBy<Model>,
390
393
  ): Promise<OnUpdate<Model>> {
391
- if (updateBy.data.monitorSteps) {
394
+ /*
395
+ * currentMonitorStatusId is writable by any project member and its FK is
396
+ * ON DELETE NO ACTION, so an id from another project here leaves that
397
+ * project undeletable — the same shape monitorSteps had. The
398
+ * 1785240000000 migration repaired the rows that existed then; this stops
399
+ * new ones. It stays NO ACTION on purpose: deleting a status monitors are
400
+ * currently in should be blocked, not cascaded.
401
+ */
402
+ const currentMonitorStatusId: ObjectID | string | undefined =
403
+ resolveReferenceId(updateBy.data.currentMonitorStatusId) ||
404
+ resolveReferenceId(updateBy.data.currentMonitorStatus);
405
+
406
+ if (updateBy.data.monitorSteps || currentMonitorStatusId) {
392
407
  /*
393
408
  * Root/API updates do not always carry a tenantId, so fall back to the
394
409
  * project of each monitor the query actually matches.
@@ -398,10 +413,30 @@ export class Service extends DatabaseService<Model> {
398
413
  : await this.getProjectIdsForUpdateQuery(updateBy);
399
414
 
400
415
  for (const projectId of projectIds) {
401
- await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
402
- monitorSteps: updateBy.data.monitorSteps as MonitorSteps | JSONObject,
403
- projectId: projectId,
404
- });
416
+ if (updateBy.data.monitorSteps) {
417
+ await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject(
418
+ {
419
+ monitorSteps: updateBy.data.monitorSteps as
420
+ | MonitorSteps
421
+ | JSONObject,
422
+ projectId: projectId,
423
+ },
424
+ );
425
+ }
426
+
427
+ await ProjectScopedReferenceValidator.validateReferencesBelongToProject(
428
+ {
429
+ projectId: projectId,
430
+ subject: "monitor",
431
+ references: [
432
+ {
433
+ modelName: "Monitor Status",
434
+ id: currentMonitorStatusId,
435
+ service: MonitorStatusService,
436
+ },
437
+ ],
438
+ },
439
+ );
405
440
  }
406
441
  }
407
442
 
@@ -1207,7 +1242,8 @@ ${createdItem.description?.trim() || "No description provided."}
1207
1242
  /*
1208
1243
  * Only global probes and probes belonging to this project may be attached -
1209
1244
  * the id list comes from the browser, so it cannot be trusted to stay inside
1210
- * the tenant.
1245
+ * the tenant. ProbeService.getProbesAttachableToProject owns that rule and
1246
+ * MonitorProbeService enforces the same one on the CRUD path.
1211
1247
  */
1212
1248
  @CaptureSpan()
1213
1249
  public async addSelectedProbesToMonitor(
@@ -1215,47 +1251,16 @@ ${createdItem.description?.trim() || "No description provided."}
1215
1251
  monitorId: ObjectID,
1216
1252
  probeIds: Array<ObjectID>,
1217
1253
  ): Promise<void> {
1218
- if (probeIds.length === 0) {
1219
- return;
1220
- }
1221
-
1222
- const [globalProbes, projectProbes]: [Array<Probe>, Array<Probe>] =
1223
- await Promise.all([
1224
- ProbeService.findBy({
1225
- query: {
1226
- _id: QueryHelper.any(probeIds),
1227
- isGlobalProbe: true,
1228
- },
1229
- select: {
1230
- _id: true,
1231
- },
1232
- skip: 0,
1233
- limit: LIMIT_PER_PROJECT,
1234
- props: {
1235
- isRoot: true,
1236
- },
1237
- }),
1238
- ProbeService.findBy({
1239
- query: {
1240
- _id: QueryHelper.any(probeIds),
1241
- isGlobalProbe: false,
1242
- projectId: projectId,
1243
- },
1244
- select: {
1245
- _id: true,
1246
- },
1247
- skip: 0,
1248
- limit: LIMIT_PER_PROJECT,
1249
- props: {
1250
- isRoot: true,
1251
- },
1252
- }),
1253
- ]);
1254
+ const probes: Array<Probe> =
1255
+ await ProbeService.getProbesAttachableToProject({
1256
+ probeIds: probeIds,
1257
+ projectId: projectId,
1258
+ });
1254
1259
 
1255
1260
  await this.createMonitorProbes({
1256
1261
  projectId: projectId,
1257
1262
  monitorId: monitorId,
1258
- probes: [...globalProbes, ...projectProbes],
1263
+ probes: probes,
1259
1264
  });
1260
1265
  }
1261
1266
 
@@ -1,10 +1,80 @@
1
1
  import DatabaseService from "./DatabaseService";
2
2
  import Model from "../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
3
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
4
+ import CreateBy from "../Types/Database/CreateBy";
5
+ import UpdateBy from "../Types/Database/UpdateBy";
6
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
7
+ import BadDataException from "../../Types/Exception/BadDataException";
8
+ import ScanTargetUtil from "../../Utils/NetworkDiscovery/ScanTargetUtil";
3
9
 
4
10
  export class Service extends DatabaseService<Model> {
5
11
  public constructor() {
6
12
  super(Model);
7
13
  }
14
+
15
+ /*
16
+ * Validate the scan target at write time, with the same parser the probe
17
+ * expands it with (Common/Utils/NetworkDiscovery/ScanTargetUtil).
18
+ *
19
+ * Without this, a typo'd target is accepted, sits Pending until a probe
20
+ * claims it, and only surfaces minutes later as a Failed scan with the
21
+ * parser's complaint buried in statusMessage. Octet-range notation makes
22
+ * that far likelier than CIDR did — a reversed range ("10.22-16.0.1-20") or
23
+ * an out-of-range octet is easy to type and impossible to spot by eye — so
24
+ * the feedback belongs on the form, not on a scan result.
25
+ *
26
+ * The size ceiling is checked here too: an oversized target is rejected the
27
+ * moment it is entered rather than after a probe has been handed a sweep it
28
+ * refuses to run.
29
+ */
30
+ @CaptureSpan()
31
+ protected override async onBeforeCreate(
32
+ createBy: CreateBy<Model>,
33
+ ): Promise<OnCreate<Model>> {
34
+ this.validateScanTarget(createBy.data.cidr);
35
+
36
+ return { createBy, carryForward: null };
37
+ }
38
+
39
+ /*
40
+ * The target column is not user-updatable (its ColumnAccessControl grants no
41
+ * update permission), but root writers — the probe-ingest endpoints, workers,
42
+ * migrations — bypass that. Validating any update that carries the column
43
+ * keeps the invariant true for every writer instead of just the form.
44
+ */
45
+ @CaptureSpan()
46
+ protected override async onBeforeUpdate(
47
+ updateBy: UpdateBy<Model>,
48
+ ): Promise<OnUpdate<Model>> {
49
+ const dataKeys: Array<string> = Object.keys(updateBy.data || {});
50
+
51
+ if (dataKeys.includes("cidr")) {
52
+ this.validateScanTarget(
53
+ (updateBy.data as Record<string, unknown>)["cidr"],
54
+ );
55
+ }
56
+
57
+ return { updateBy, carryForward: null };
58
+ }
59
+
60
+ /*
61
+ * The value is passed through UNTOUCHED. It arrives straight from the
62
+ * request JSON — BaseAPI assigns ShortText columns verbatim, and this hook
63
+ * runs before the model's type and length checks — so it may be a number,
64
+ * an object or an array, not just a string. ScanTargetUtil already type-
65
+ * guards and trims internally; normalizing here (the obvious
66
+ * `(target || "").trim()`) would instead throw a TypeError on any truthy
67
+ * non-string and turn an intended 400 into a 500.
68
+ */
69
+ private validateScanTarget(target: unknown): void {
70
+ const validationError: string | null = ScanTargetUtil.getValidationError(
71
+ target as string,
72
+ );
73
+
74
+ if (validationError) {
75
+ throw new BadDataException(validationError);
76
+ }
77
+ }
8
78
  }
9
79
 
10
80
  export default new Service();
@@ -32,6 +32,7 @@ import MonitorService from "./MonitorService";
32
32
  import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
33
33
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
34
34
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
35
+ import QueryHelper from "../Types/Database/QueryHelper";
35
36
  import { IsBillingEnabled } from "../EnvironmentConfig";
36
37
  import GlobalCache from "../Infrastructure/GlobalCache";
37
38
  import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
@@ -434,6 +435,71 @@ export class Service extends DatabaseService<Model> {
434
435
  return { createBy: createBy, carryForward: [] };
435
436
  }
436
437
 
438
+ /*
439
+ * Only global probes and probes belonging to this project may be attached to
440
+ * that project's monitors. Probe ids reach the server from the browser - the
441
+ * monitor create form and the Monitor > Probes table both post them - so
442
+ * they cannot be trusted to stay inside the tenant. Every path that attaches
443
+ * a probe to a monitor filters through here.
444
+ */
445
+ @CaptureSpan()
446
+ public async getProbesAttachableToProject(data: {
447
+ probeIds: Array<ObjectID>;
448
+ projectId: ObjectID;
449
+ }): Promise<Array<Model>> {
450
+ if (data.probeIds.length === 0) {
451
+ return [];
452
+ }
453
+
454
+ const [globalProbes, projectProbes]: [Array<Model>, Array<Model>] =
455
+ await Promise.all([
456
+ this.findBy({
457
+ query: {
458
+ _id: QueryHelper.any(data.probeIds),
459
+ isGlobalProbe: true,
460
+ },
461
+ select: {
462
+ _id: true,
463
+ },
464
+ skip: 0,
465
+ limit: LIMIT_PER_PROJECT,
466
+ props: {
467
+ isRoot: true,
468
+ },
469
+ }),
470
+ this.findBy({
471
+ query: {
472
+ _id: QueryHelper.any(data.probeIds),
473
+ isGlobalProbe: false,
474
+ projectId: data.projectId,
475
+ },
476
+ select: {
477
+ _id: true,
478
+ },
479
+ skip: 0,
480
+ limit: LIMIT_PER_PROJECT,
481
+ props: {
482
+ isRoot: true,
483
+ },
484
+ }),
485
+ ]);
486
+
487
+ return [...globalProbes, ...projectProbes];
488
+ }
489
+
490
+ @CaptureSpan()
491
+ public async isProbeAttachableToProject(data: {
492
+ probeId: ObjectID;
493
+ projectId: ObjectID;
494
+ }): Promise<boolean> {
495
+ const probes: Array<Model> = await this.getProbesAttachableToProject({
496
+ probeIds: [data.probeId],
497
+ projectId: data.projectId,
498
+ });
499
+
500
+ return probes.length > 0;
501
+ }
502
+
437
503
  @CaptureSpan()
438
504
  public async getOwners(probeId: ObjectID): Promise<Array<User>> {
439
505
  if (!probeId) {
@@ -52,6 +52,7 @@ import {
52
52
  } from "../../Types/BrandColors";
53
53
  import Color from "../../Types/Color";
54
54
  import LIMIT_MAX from "../../Types/Database/LimitMax";
55
+ import QueryDeepPartialEntity from "../../Types/Database/PartialEntity";
55
56
  import OneUptimeDate from "../../Types/Date";
56
57
  import EmailTemplateType from "../../Types/Email/EmailTemplateType";
57
58
  import BadDataException from "../../Types/Exception/BadDataException";
@@ -609,6 +610,99 @@ export class ProjectService extends DatabaseService<Model> {
609
610
  await this.sendSubscriptionChangeWebhookSlackNotification(project.id!);
610
611
  }
611
612
 
613
+ /*
614
+ * Pushes a project's trial end date out to a new date, in the payment
615
+ * provider first and then on the project row.
616
+ *
617
+ * The payment provider is the source of truth for what the customer is
618
+ * actually charged, so it is updated first: if Stripe rejects the change
619
+ * the project row keeps its old trial date rather than showing the customer
620
+ * a trial that is not really there.
621
+ */
622
+ @CaptureSpan()
623
+ public async extendTrial(params: {
624
+ projectId: ObjectID;
625
+ trialEndsAt: Date;
626
+ extendedByUserId?: ObjectID | undefined;
627
+ }): Promise<void> {
628
+ if (!IsBillingEnabled) {
629
+ throw new BadDataException("Billing is not enabled for this server");
630
+ }
631
+
632
+ const project: Model | null = await this.findOneById({
633
+ id: params.projectId,
634
+ select: {
635
+ _id: true,
636
+ trialEndsAt: true,
637
+ paymentProviderSubscriptionId: true,
638
+ paymentProviderMeteredSubscriptionId: true,
639
+ },
640
+ props: {
641
+ isRoot: true,
642
+ },
643
+ });
644
+
645
+ if (!project) {
646
+ throw new BadDataException("Project not found");
647
+ }
648
+
649
+ if (!project.paymentProviderSubscriptionId) {
650
+ throw new BadDataException("Payment Provider subscription not found");
651
+ }
652
+
653
+ if (!project.paymentProviderMeteredSubscriptionId) {
654
+ throw new BadDataException(
655
+ "Payment Provider metered subscription not found",
656
+ );
657
+ }
658
+
659
+ await BillingService.extendTrial({
660
+ subscriptionId: project.paymentProviderSubscriptionId,
661
+ meteredSubscriptionId: project.paymentProviderMeteredSubscriptionId,
662
+ trialEndsAt: params.trialEndsAt,
663
+ });
664
+
665
+ /*
666
+ * Pushing trial_end out puts the subscriptions back into `trialing`. Read
667
+ * the statuses back rather than assuming them, so the project row - which
668
+ * drives the customer's trial banner and the paywall - matches Stripe.
669
+ */
670
+ const subscriptionStatus: SubscriptionStatus =
671
+ await BillingService.getSubscriptionStatus(
672
+ project.paymentProviderSubscriptionId,
673
+ );
674
+
675
+ const meteredSubscriptionStatus: SubscriptionStatus =
676
+ await BillingService.getSubscriptionStatus(
677
+ project.paymentProviderMeteredSubscriptionId,
678
+ );
679
+
680
+ await this.updateOneById({
681
+ id: project.id!,
682
+ data: {
683
+ trialEndsAt: params.trialEndsAt,
684
+ paymentProviderSubscriptionStatus: subscriptionStatus,
685
+ paymentProviderMeteredSubscriptionStatus: meteredSubscriptionStatus,
686
+ },
687
+ props: {
688
+ isRoot: true,
689
+ ignoreHooks: true,
690
+ },
691
+ });
692
+
693
+ /*
694
+ * The audit trail for handing out free service. There is no generic
695
+ * admin-action table, so the acting master admin goes in the log line.
696
+ */
697
+ logger.info(
698
+ `Trial for project ${project.id?.toString()} extended from ${
699
+ project.trialEndsAt?.toISOString() || "none"
700
+ } to ${params.trialEndsAt.toISOString()} by master admin ${
701
+ params.extendedByUserId?.toString() || "unknown"
702
+ }`,
703
+ );
704
+ }
705
+
612
706
  /*
613
707
  * The paid-conversion event for ad platforms: fires server-side (immune to
614
708
  * ad blockers) whenever a project's subscription plan changes, with enough
@@ -2040,6 +2134,7 @@ export class ProjectService extends DatabaseService<Model> {
2040
2134
  paymentProviderMeteredSubscriptionId: true,
2041
2135
  paymentProviderSubscriptionSeats: true,
2042
2136
  paymentProviderPlanId: true,
2137
+ trialEndsAt: true,
2043
2138
  },
2044
2139
  });
2045
2140
 
@@ -2081,6 +2176,21 @@ export class ProjectService extends DatabaseService<Model> {
2081
2176
  throw new BadDataException("Subscription plan not found");
2082
2177
  }
2083
2178
 
2179
+ /*
2180
+ * Reactivating goes through changePlan, which cancels both subscriptions
2181
+ * and creates new ones - so a trial the project still has to run has to be
2182
+ * carried onto the replacements explicitly. Otherwise a customer whose
2183
+ * trial a master admin extended as a goodwill gesture loses the extension
2184
+ * the moment their subscription is reactivated, and is billed right away.
2185
+ *
2186
+ * A trial that has already lapsed is not revived: reactivation is not the
2187
+ * place to hand out free service.
2188
+ */
2189
+ const endTrialAt: Date | undefined =
2190
+ project.trialEndsAt && OneUptimeDate.isInTheFuture(project.trialEndsAt)
2191
+ ? project.trialEndsAt
2192
+ : undefined;
2193
+
2084
2194
  const result: {
2085
2195
  subscriptionId: string;
2086
2196
  meteredSubscriptionId: string;
@@ -2093,7 +2203,7 @@ export class ProjectService extends DatabaseService<Model> {
2093
2203
  newPlan: subscriptionPlan,
2094
2204
  quantity: project.paymentProviderSubscriptionSeats,
2095
2205
  isYearly: SubscriptionPlan.isYearlyPlan(project.paymentProviderPlanId),
2096
- endTrialAt: undefined,
2206
+ endTrialAt: endTrialAt,
2097
2207
  });
2098
2208
 
2099
2209
  // refresh subscription status.
@@ -2109,14 +2219,28 @@ export class ProjectService extends DatabaseService<Model> {
2109
2219
 
2110
2220
  // now update project with new subscription id.
2111
2221
 
2222
+ const updateData: QueryDeepPartialEntity<Model> = {
2223
+ paymentProviderSubscriptionId: result.subscriptionId,
2224
+ paymentProviderMeteredSubscriptionId: result.meteredSubscriptionId,
2225
+ paymentProviderSubscriptionStatus: subscriptionState,
2226
+ paymentProviderMeteredSubscriptionStatus: meteredSubscriptionState,
2227
+ };
2228
+
2229
+ /*
2230
+ * The payment provider is the source of truth for the trial the new
2231
+ * subscriptions were created with, so its date wins over the one sent.
2232
+ * The column is left alone when there is no trial to record, rather than
2233
+ * stamping a project whose trial lapsed long ago with a fresh date.
2234
+ */
2235
+ const newTrialEndsAt: Date | undefined = result.trialEndsAt || endTrialAt;
2236
+
2237
+ if (newTrialEndsAt) {
2238
+ updateData.trialEndsAt = newTrialEndsAt;
2239
+ }
2240
+
2112
2241
  await this.updateOneById({
2113
2242
  id: project.id!,
2114
- data: {
2115
- paymentProviderSubscriptionId: result.subscriptionId,
2116
- paymentProviderMeteredSubscriptionId: result.meteredSubscriptionId,
2117
- paymentProviderSubscriptionStatus: subscriptionState,
2118
- paymentProviderMeteredSubscriptionStatus: meteredSubscriptionState,
2119
- },
2243
+ data: updateData,
2120
2244
  props: {
2121
2245
  isRoot: true,
2122
2246
  },
@@ -0,0 +1,52 @@
1
+ import CreateBy from "../Types/Database/CreateBy";
2
+ import { OnCreate } from "../Types/Database/Hooks";
3
+ import DatabaseService from "./DatabaseService";
4
+ import BadDataException from "../../Types/Exception/BadDataException";
5
+ import RecommendationType from "../../Types/Recommendation/RecommendationType";
6
+ import Model from "../../Models/DatabaseModels/RecommendationDismissal";
7
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
8
+
9
+ export class Service extends DatabaseService<Model> {
10
+ public constructor() {
11
+ super(Model);
12
+ }
13
+
14
+ /*
15
+ * `recommendationType` is a string column rather than a Postgres enum (the
16
+ * codebase keeps enums in TypeScript so adding a member needs no migration),
17
+ * which means nothing below this point would reject a typo. A dismissal
18
+ * written under "monitor" instead of "Monitor" is silently invisible: the
19
+ * page filters on the enum value, finds nothing, and the card the user
20
+ * dismissed simply reappears with no error anywhere. Validating at the write
21
+ * boundary is the only place that failure is still cheap.
22
+ */
23
+ @CaptureSpan()
24
+ protected override async onBeforeCreate(
25
+ createBy: CreateBy<Model>,
26
+ ): Promise<OnCreate<Model>> {
27
+ const recommendationType: string | undefined =
28
+ createBy.data.recommendationType;
29
+
30
+ if (!recommendationType) {
31
+ throw new BadDataException("Recommendation type is required.");
32
+ }
33
+
34
+ const validTypes: Array<string> = Object.values(RecommendationType);
35
+
36
+ if (!validTypes.includes(recommendationType)) {
37
+ throw new BadDataException(
38
+ `Invalid recommendation type: ${recommendationType}. Valid types are ${validTypes.join(
39
+ ", ",
40
+ )}.`,
41
+ );
42
+ }
43
+
44
+ if (!createBy.data.recommendationId) {
45
+ throw new BadDataException("Recommendation ID is required.");
46
+ }
47
+
48
+ return { createBy, carryForward: null };
49
+ }
50
+ }
51
+
52
+ export default new Service();
@@ -4,6 +4,7 @@ import BadDataException from "../../Types/Exception/BadDataException";
4
4
  import Model from "../../Models/DatabaseModels/RunbookAgentJob";
5
5
  import RunbookAgentJobStatus from "../../Types/Runbook/RunbookAgentJobStatus";
6
6
  import RunbookStepType from "../../Types/Runbook/RunbookStepType";
7
+ import SortOrder from "../../Types/BaseDatabase/SortOrder";
7
8
  import OneUptimeDate from "../../Types/Date";
8
9
  import { JSONObject } from "../../Types/JSON";
9
10
  import PostgresAppInstance from "../Infrastructure/PostgresDatabase";
@@ -26,6 +27,24 @@ const DEFAULT_CLAIM_TIMEOUT_MS: number = 2 * 60_000;
26
27
 
27
28
  const POLL_INTERVAL_MS: number = 500;
28
29
 
30
+ /*
31
+ * A job in one of these states will never change again, so its row is a
32
+ * durable record of what the step did. Everything else is still in flight and
33
+ * can be waited on.
34
+ */
35
+ const TERMINAL_STATUSES: Array<RunbookAgentJobStatus> = [
36
+ RunbookAgentJobStatus.Succeeded,
37
+ RunbookAgentJobStatus.Failed,
38
+ RunbookAgentJobStatus.TimedOut,
39
+ RunbookAgentJobStatus.Cancelled,
40
+ ];
41
+
42
+ export function isTerminalAgentJobStatus(
43
+ status: RunbookAgentJobStatus | undefined,
44
+ ): boolean {
45
+ return Boolean(status && TERMINAL_STATUSES.includes(status));
46
+ }
47
+
29
48
  /*
30
49
  * TypeORM's dataSource.query returns `[rows, rowCount]` for UPDATE/DELETE
31
50
  * (even with RETURNING), and just `rows` for SELECT/INSERT. This helper
@@ -95,6 +114,47 @@ export class Service extends DatabaseService<Model> {
95
114
  return this.create({ data: row, props: { isRoot: true } });
96
115
  }
97
116
 
117
+ /*
118
+ * The job this (execution, step) pair already produced, if any.
119
+ *
120
+ * A runbook execution runs inside a single BullMQ job, so a Worker restart
121
+ * mid-step means the execution is re-delivered and RunRunbook walks back to
122
+ * the same step — which is still persisted as Running. Without this lookup
123
+ * the step would be dispatched a second time and the agent would run the
124
+ * script again. The dispatcher calls this first: a terminal row is the
125
+ * step's result, a live row is something to re-attach to.
126
+ *
127
+ * A step is dispatched at most once per execution, so the newest row is the
128
+ * only one there can be; ordering is belt-and-braces against a torn write
129
+ * from a previous release.
130
+ */
131
+ @CaptureSpan()
132
+ public async findLatestJobForStep(data: {
133
+ runbookExecutionId: ObjectID;
134
+ stepId: string;
135
+ }): Promise<Model | null> {
136
+ const jobs: Array<Model> = await this.findBy({
137
+ query: {
138
+ runbookExecutionId: data.runbookExecutionId,
139
+ stepId: data.stepId,
140
+ },
141
+ select: {
142
+ _id: true,
143
+ status: true,
144
+ output: true,
145
+ exitCode: true,
146
+ errorMessage: true,
147
+ createdAt: true,
148
+ },
149
+ sort: { createdAt: SortOrder.Descending },
150
+ limit: 1,
151
+ skip: 0,
152
+ props: { isRoot: true },
153
+ });
154
+
155
+ return jobs[0] || null;
156
+ }
157
+
98
158
  /*
99
159
  * Atomically claim the oldest Pending job in the agent's project targeted
100
160
  * at this specific agent. Uses FOR UPDATE SKIP LOCKED so concurrent agents
@@ -279,15 +339,21 @@ export class Service extends DatabaseService<Model> {
279
339
  * POLL_INTERVAL_MS until the job reaches a terminal status, or until the
280
340
  * combined claim + execution window is exhausted (in which case we mark
281
341
  * the job TimedOut ourselves and return it).
342
+ *
343
+ * waitStartedAt anchors that window. It defaults to now — right for a job
344
+ * this Worker just enqueued — but a Worker re-attaching to a job left behind
345
+ * by a restart passes the job's createdAt, so the step keeps the single
346
+ * window its author configured instead of earning a fresh one per redelivery.
282
347
  */
283
348
  @CaptureSpan()
284
349
  public async pollUntilTerminal(data: {
285
350
  jobId: ObjectID;
286
351
  claimTimeoutInMs: number;
287
352
  executionTimeoutInMs: number;
353
+ waitStartedAt?: Date | undefined;
288
354
  }): Promise<Model> {
289
355
  const overallDeadline: Date = OneUptimeDate.addRemoveSeconds(
290
- OneUptimeDate.getCurrentDate(),
356
+ data.waitStartedAt || OneUptimeDate.getCurrentDate(),
291
357
  Math.ceil(
292
358
  (data.claimTimeoutInMs + data.executionTimeoutInMs + 5_000) / 1000,
293
359
  ),
@@ -318,12 +384,7 @@ export class Service extends DatabaseService<Model> {
318
384
  );
319
385
  }
320
386
 
321
- if (
322
- job.status === RunbookAgentJobStatus.Succeeded ||
323
- job.status === RunbookAgentJobStatus.Failed ||
324
- job.status === RunbookAgentJobStatus.TimedOut ||
325
- job.status === RunbookAgentJobStatus.Cancelled
326
- ) {
387
+ if (isTerminalAgentJobStatus(job.status)) {
327
388
  return job;
328
389
  }
329
390