@oneuptime/common 13.0.8 → 14.0.1

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 (514) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Index.ts +4 -0
  4. package/Models/DatabaseModels/Monitor.ts +3 -2
  5. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  6. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
  7. package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
  8. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
  9. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
  10. package/Scripts/generate-service-worker.js +65 -4
  11. package/Server/API/BaseAPI.ts +26 -5
  12. package/Server/API/GlobalConfigAPI.ts +228 -462
  13. package/Server/API/ProjectOIDC.ts +25 -16
  14. package/Server/API/ProjectSSO.ts +24 -16
  15. package/Server/API/StatusPageAPI.ts +100 -42
  16. package/Server/API/TeamMemberAPI.ts +10 -1
  17. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  18. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  19. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  20. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  21. package/Server/EnvironmentConfig.ts +147 -2
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  26. package/Server/Middleware/UserAuthorization.ts +38 -16
  27. package/Server/Services/AuditLogService.ts +84 -1079
  28. package/Server/Services/DatabaseService.ts +10 -0
  29. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  30. package/Server/Services/GlobalConfigService.ts +80 -1
  31. package/Server/Services/Index.ts +4 -0
  32. package/Server/Services/MonitorProbeService.ts +116 -20
  33. package/Server/Services/MonitorService.ts +59 -6
  34. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  35. package/Server/Services/MonitorTemplateService.ts +26 -0
  36. package/Server/Services/ProjectService.ts +284 -0
  37. package/Server/Services/RumSessionPinService.ts +25 -6
  38. package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
  39. package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
  40. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
  41. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
  42. package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
  43. package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
  44. package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
  45. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  46. package/Server/Services/StatusPageService.ts +78 -1
  47. package/Server/Services/TeamMemberService.ts +11 -0
  48. package/Server/Services/TeamService.ts +10 -0
  49. package/Server/Services/UserService.ts +10 -17
  50. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  51. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  52. package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
  53. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  54. package/Server/Types/Markdown.ts +41 -0
  55. package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
  56. package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
  57. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  58. package/Server/Utils/EditionEnforcement.ts +233 -0
  59. package/Server/Utils/FrontendEnvironment.ts +41 -2
  60. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  61. package/Server/Utils/Monitor/AffectedResourceList.ts +148 -0
  62. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  63. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  64. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
  65. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +720 -465
  66. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  67. package/Server/Utils/Monitor/RootCauseList.ts +139 -0
  68. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
  69. package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
  70. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  71. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  72. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  73. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  74. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  75. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  76. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  77. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  78. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  79. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  80. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  81. package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
  82. package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
  83. package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
  84. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  85. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
  86. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
  87. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  88. package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
  89. package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
  90. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
  91. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
  92. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
  93. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
  94. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  95. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  96. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
  97. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  98. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  99. package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
  100. package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
  101. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
  102. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
  103. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
  104. package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
  105. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  106. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  107. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  108. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  109. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
  110. package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
  111. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
  112. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  113. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  114. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  115. package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
  116. package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
  117. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  118. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  119. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  120. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  121. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  122. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  123. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  124. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  125. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  126. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  127. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  128. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  129. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  130. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  131. package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
  132. package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
  133. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
  134. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  135. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  136. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  137. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  138. package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
  139. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  140. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  141. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  142. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  143. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  144. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  145. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  146. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
  147. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
  148. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  149. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
  150. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
  151. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
  152. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
  153. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
  154. package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
  155. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
  156. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  157. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  158. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
  159. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  160. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  161. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  162. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
  163. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  164. package/Tests/Server/Types/Markdown.test.ts +183 -1
  165. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
  166. package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
  167. package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
  168. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  169. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  170. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  171. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  172. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  173. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  174. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
  175. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +1339 -96
  176. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
  177. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
  178. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  179. package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
  180. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
  181. package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
  182. package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
  183. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
  184. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  185. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  186. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  187. package/Tests/Types/API/Route.test.ts +56 -0
  188. package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
  189. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  190. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  191. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  192. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
  193. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  194. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  195. package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
  196. package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
  197. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  198. package/Tests/UI/Components/Icon.test.tsx +26 -0
  199. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  200. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  201. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  202. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  203. package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
  204. package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
  205. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  206. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  207. package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
  208. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  209. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  210. package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
  211. package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
  212. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
  213. package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
  214. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  215. package/Types/API/Hostname.ts +63 -16
  216. package/Types/API/Route.ts +19 -2
  217. package/Types/API/URL.ts +26 -2
  218. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  219. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  220. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  221. package/Types/Database/CanonicalModelInstance.ts +102 -0
  222. package/Types/Database/TableColumn.ts +20 -6
  223. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  224. package/Types/IP/IPv6.ts +6 -1
  225. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
  226. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
  227. package/Types/Monitor/MonitorStep.ts +6 -1
  228. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  229. package/Types/Permission.ts +90 -0
  230. package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
  231. package/Types/Rules/RuleRun.ts +4 -0
  232. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  233. package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
  234. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  235. package/UI/Components/Feed/FeedOptions.ts +427 -0
  236. package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
  237. package/UI/Components/Feed/useFeedItems.ts +28 -9
  238. package/UI/Components/Feed/useFeedOptions.ts +225 -0
  239. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  240. package/UI/Components/Icon/Icon.tsx +1 -1
  241. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  242. package/UI/Components/ModelList/ModelList.tsx +11 -1
  243. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  244. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  245. package/UI/esbuild-enterprise.js +263 -0
  246. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  247. package/Utils/HostAddressUtil.ts +94 -0
  248. package/Utils/LabelRuleImportExport.ts +27 -9
  249. package/Utils/Monitor/MetricValueFormatter.ts +2 -2
  250. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  251. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  252. package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
  253. package/Utils/Slo/SloDuration.ts +22 -6
  254. package/Utils/Slo/SloHealth.ts +8 -7
  255. package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
  256. package/Utils/Slo/SloOverviewText.ts +33 -0
  257. package/Utils/Slo/SloProjection.ts +4 -3
  258. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  259. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  260. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  261. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  262. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  263. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  264. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  265. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  266. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  267. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  268. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  269. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
  270. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
  271. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
  272. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
  273. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
  274. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
  275. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
  276. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
  277. package/build/dist/Server/API/BaseAPI.js +26 -5
  278. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  279. package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
  280. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  281. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  282. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  283. package/build/dist/Server/API/ProjectSSO.js +24 -16
  284. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  285. package/build/dist/Server/API/StatusPageAPI.js +93 -43
  286. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  287. package/build/dist/Server/API/TeamMemberAPI.js +8 -1
  288. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  289. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  290. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  291. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  292. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  293. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  294. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  295. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  296. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  297. package/build/dist/Server/EnvironmentConfig.js +84 -2
  298. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  299. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  300. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  301. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
  302. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
  303. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
  304. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
  305. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  306. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  307. package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
  308. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  309. package/build/dist/Server/Services/AuditLogService.js +71 -740
  310. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  311. package/build/dist/Server/Services/DatabaseService.js +7 -0
  312. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  313. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  314. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  315. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  316. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  317. package/build/dist/Server/Services/Index.js +4 -0
  318. package/build/dist/Server/Services/Index.js.map +1 -1
  319. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  320. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  321. package/build/dist/Server/Services/MonitorService.js +48 -6
  322. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  323. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  324. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  325. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  326. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  327. package/build/dist/Server/Services/ProjectService.js +214 -0
  328. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  329. package/build/dist/Server/Services/RumSessionPinService.js +24 -6
  330. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  331. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
  332. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
  333. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
  334. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
  335. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
  336. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  337. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
  338. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
  339. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
  340. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
  341. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
  342. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
  343. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
  344. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  345. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  346. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  347. package/build/dist/Server/Services/StatusPageService.js +71 -0
  348. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  349. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  350. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  351. package/build/dist/Server/Services/TeamService.js +9 -0
  352. package/build/dist/Server/Services/TeamService.js.map +1 -1
  353. package/build/dist/Server/Services/UserService.js +10 -16
  354. package/build/dist/Server/Services/UserService.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  356. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  358. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
  360. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  361. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  362. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  363. package/build/dist/Server/Types/Markdown.js +31 -0
  364. package/build/dist/Server/Types/Markdown.js.map +1 -1
  365. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
  366. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
  368. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  369. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  370. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  371. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  372. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  373. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  374. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  375. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  376. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  377. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +67 -0
  378. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  379. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  380. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  381. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  382. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
  383. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +585 -367
  384. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  385. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  386. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  387. package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
  388. package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
  389. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
  390. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  391. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
  392. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
  393. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  394. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  395. package/build/dist/Types/API/Hostname.js +59 -11
  396. package/build/dist/Types/API/Hostname.js.map +1 -1
  397. package/build/dist/Types/API/Route.js +18 -2
  398. package/build/dist/Types/API/Route.js.map +1 -1
  399. package/build/dist/Types/API/URL.js +23 -2
  400. package/build/dist/Types/API/URL.js.map +1 -1
  401. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  402. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  403. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  404. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  405. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  406. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  407. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  408. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  409. package/build/dist/Types/Database/TableColumn.js +19 -6
  410. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  411. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  412. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  413. package/build/dist/Types/IP/IPv6.js +6 -1
  414. package/build/dist/Types/IP/IPv6.js.map +1 -1
  415. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  416. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  417. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  418. package/build/dist/Types/Permission.js +84 -0
  419. package/build/dist/Types/Permission.js.map +1 -1
  420. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
  421. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  422. package/build/dist/Types/Rules/RuleRun.js +4 -0
  423. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  424. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  425. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  426. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
  427. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  428. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  429. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  430. package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
  431. package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
  432. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
  433. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
  434. package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
  435. package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
  436. package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
  437. package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
  438. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  439. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  440. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  441. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  442. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  443. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  444. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  445. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  446. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  447. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  448. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  449. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  450. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  451. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  452. package/build/dist/Utils/HostAddressUtil.js +80 -0
  453. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  454. package/build/dist/Utils/LabelRuleImportExport.js +22 -6
  455. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  456. package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
  457. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  458. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  459. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  460. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  461. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
  462. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
  463. package/build/dist/Utils/Slo/SloDuration.js +13 -5
  464. package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
  465. package/build/dist/Utils/Slo/SloHealth.js +8 -7
  466. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  467. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
  468. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
  469. package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
  470. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
  471. package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
  472. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  473. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  474. package/jest.config.json +3 -1
  475. package/package.json +1 -1
  476. package/test-setup.sh +103 -34
  477. package/tsconfig.json +13 -0
  478. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  479. package/Server/API/TeamComplianceAPI.ts +0 -158
  480. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  481. package/Server/Services/TeamComplianceService.ts +0 -733
  482. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  483. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  484. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  485. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  486. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
  487. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  488. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  489. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
  490. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  491. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  492. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  493. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  494. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  495. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  496. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  497. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  498. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  499. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  500. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  501. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  502. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  503. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  504. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  505. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  506. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  507. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  508. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  509. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  510. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  511. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  512. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  513. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  514. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -0,0 +1,436 @@
1
+ /*
2
+ * How often a monitor is probed: the canonical reading of
3
+ * `Monitor.monitoringInterval` (and `MonitorTemplate.monitoringInterval`).
4
+ *
5
+ * WHY IT LIVES IN Common
6
+ *
7
+ * The column is free-text ShortText and is supposed to hold a 5-field cron
8
+ * expression, but nothing ever validated it. Four places have an opinion
9
+ * about what a stored value MEANS and they must not disagree:
10
+ *
11
+ * - the write hooks (MonitorService / MonitorTemplateService) — which now
12
+ * normalize on save, so the column converges on canonical cron;
13
+ * - the claim query (MonitorProbeService.claimMonitorProbesForProbing) —
14
+ * which derives `nextPingAt` and is the thing that actually schedules
15
+ * the probe;
16
+ * - the telemetry-monitor worker and the over-time criteria evaluator —
17
+ * which need the cadence to size their evaluation windows;
18
+ * - the data migration that cleans the rows written before any of this.
19
+ *
20
+ * It is isomorphic (no server-only imports) because the dashboard needs the
21
+ * same reading to render a stored value, and pure and TOTAL — it never
22
+ * throws. Policy (reject / rewrite / leave alone) belongs to the caller:
23
+ * the write hook throws, the scheduler falls back, the migration skips.
24
+ * Same reason RescanIntervalUtil and ScanTargetUtil sit in Common.
25
+ *
26
+ * WHAT WENT WRONG, so the next person does not undo it
27
+ *
28
+ * A monitor storing "5m" was not a cron expression, so cron-parser threw,
29
+ * and the claim query caught that and fell back to a hard-coded "one minute
30
+ * from now". A monitor configured for five minutes was probed every ~75
31
+ * seconds — four times the requested rate — silently, for as long as the row
32
+ * existed. 103 monitors across 10 projects were in that state. Most of them
33
+ * got there by copying OUR OWN Terraform examples, which passed the dropdown
34
+ * LABEL ("Every 5 minutes") where the API wanted the VALUE ("*\/5 * * * *").
35
+ *
36
+ * THE INTERPRETIVE RULE THIS ENCODES
37
+ *
38
+ * A bare duration in THIS column is always a CADENCE, never a clock
39
+ * position: "5m" means "every five minutes", not "at five past". That is
40
+ * unambiguous here because the column's only consumer is a polling
41
+ * schedule. Do not lift this parser somewhere a duration could mean an
42
+ * offset. Note CronTime.ts spells out the same cadence-shaped constants.
43
+ */
44
+
45
+ import CronTab from "../CronTab";
46
+
47
+ export enum MonitoringIntervalNormalizationStatus {
48
+ /*
49
+ * Null, undefined or blank. A legitimate state: the monitor is not probed
50
+ * on a schedule (Manual monitors, and ~4,177 rows in production).
51
+ */
52
+ Empty = "Empty",
53
+ /* Already a valid cron expression. Returned byte-identical. */
54
+ AlreadyCanonical = "AlreadyCanonical",
55
+ /* Understood, and rewritten to canonical cron. */
56
+ Normalized = "Normalized",
57
+ /* Not understood. The caller decides what that means. */
58
+ Unrecognized = "Unrecognized",
59
+ }
60
+
61
+ export interface MonitoringIntervalNormalization {
62
+ status: MonitoringIntervalNormalizationStatus;
63
+ /* The canonical cron expression, or null for Empty / Unrecognized. */
64
+ cron: string | null;
65
+ /* The value as it was handed to us, for error messages and logs. */
66
+ input: string | null;
67
+ }
68
+
69
+ /*
70
+ * Cadences that divide their unit evenly.
71
+ *
72
+ * A 7-minute step fires at :00, :07 ... :56 and then :00 again — a four-minute
73
+ * gap at every hour wrap. It is NOT "every 7 minutes", so storing it as the
74
+ * normalization of "every 7 minutes" would be storing a lie. We only accept
75
+ * n where the unit divides evenly; everything else is Unrecognized and the
76
+ * caller can tell the user to write the cron they actually want.
77
+ */
78
+ const minutesDivideEvenly: (n: number) => boolean = (n: number): boolean => {
79
+ return n >= 2 && n <= 59 && 60 % n === 0;
80
+ };
81
+
82
+ const hoursDivideEvenly: (n: number) => boolean = (n: number): boolean => {
83
+ return n >= 2 && n <= 23 && 24 % n === 0;
84
+ };
85
+
86
+ /*
87
+ * Labels the dashboard dropdown offers, mirrored here because Common cannot
88
+ * import from the App package. MonitoringIntervalLabelParity.test.ts fails
89
+ * if MonitorIntervalDropdownOptions.ts and this list ever drift apart — that
90
+ * test is the link, not this comment.
91
+ */
92
+ const DASHBOARD_DROPDOWN_LABELS: Record<string, string> = {
93
+ "every minute": "* * * * *",
94
+ "every 2 minutes": "*/2 * * * *",
95
+ "every 5 minutes": "*/5 * * * *",
96
+ "every 10 minutes": "*/10 * * * *",
97
+ "every 15 minutes": "*/15 * * * *",
98
+ "every 30 minutes": "*/30 * * * *",
99
+ "every hour": "0 * * * *",
100
+ "every 2 hours": "0 */2 * * *",
101
+ "every 3 hours": "0 */3 * * *",
102
+ "every 6 hours": "0 */6 * * *",
103
+ "every 12 hours": "0 */12 * * *",
104
+ "every day": "0 0 * * *",
105
+ };
106
+
107
+ /*
108
+ * Bare adverbs, which carry no number to run through the grammar.
109
+ */
110
+ const ADVERB_LABELS: Record<string, string> = {
111
+ minutely: "* * * * *",
112
+ hourly: "0 * * * *",
113
+ daily: "0 0 * * *",
114
+ nightly: "0 0 * * *",
115
+ weekly: "0 0 * * 0",
116
+ monthly: "0 0 1 * *",
117
+ yearly: "0 0 1 1 *",
118
+ annually: "0 0 1 1 *",
119
+ };
120
+
121
+ /*
122
+ * The unit vocabulary the duration grammar accepts. Seconds are deliberately
123
+ * absent: the claim loop runs once a minute, so a sub-minute cadence cannot
124
+ * be honoured and accepting one would promise a rate we never deliver.
125
+ */
126
+ /*
127
+ * Numbers written as words. "Every Five Minutes" is in the production
128
+ * census, so the grammar has to read these; the range stops where a cadence
129
+ * stops being plausible rather than trying to be a general number parser.
130
+ */
131
+ const SPELLED_NUMBERS: Record<string, number> = {
132
+ a: 1,
133
+ an: 1,
134
+ one: 1,
135
+ two: 2,
136
+ three: 3,
137
+ four: 4,
138
+ five: 5,
139
+ six: 6,
140
+ seven: 7,
141
+ eight: 8,
142
+ nine: 9,
143
+ ten: 10,
144
+ eleven: 11,
145
+ twelve: 12,
146
+ fifteen: 15,
147
+ twenty: 20,
148
+ thirty: 30,
149
+ sixty: 60,
150
+ };
151
+
152
+ const DIGITS_ONLY: RegExp = /^\d+$/u;
153
+
154
+ const MINUTE_UNITS: ReadonlyArray<string> = [
155
+ "m",
156
+ "min",
157
+ "mins",
158
+ "minute",
159
+ "minutes",
160
+ ];
161
+ const HOUR_UNITS: ReadonlyArray<string> = ["h", "hr", "hrs", "hour", "hours"];
162
+ const DAY_UNITS: ReadonlyArray<string> = ["d", "day", "days"];
163
+ const WEEK_UNITS: ReadonlyArray<string> = ["w", "week", "weeks"];
164
+
165
+ export default class MonitoringIntervalUtil {
166
+ /*
167
+ * Lowercase, collapse any run of whitespace (including the non-breaking
168
+ * space that survives a copy-paste out of our docs) to one space, and drop
169
+ * wrapping quotes and a trailing period. Lookup keys are built this way so
170
+ * "EVERY 5 MINUTES." and "every 5 minutes" are the same key.
171
+ *
172
+ * toLowerCase, not toLocaleLowerCase: in a Turkish locale the latter maps
173
+ * "I" to a dotless i and the table would stop matching.
174
+ */
175
+ private static toLookupKey(value: string): string {
176
+ return value
177
+ .replace(/[\s\u00a0]+/g, " ")
178
+ .trim()
179
+ .replace(/^["']+|["']+$/g, "")
180
+ .replace(/\.+$/, "")
181
+ .trim()
182
+ .toLowerCase();
183
+ }
184
+
185
+ /*
186
+ * The preset labels we ship, keyed for lookup. Built by iterating
187
+ * CronTab.PRESETS rather than retyping them, so a preset added there is
188
+ * understood here without anyone remembering to update this file.
189
+ */
190
+ private static buildLabelTable(): Record<string, string> {
191
+ const table: Record<string, string> = {};
192
+
193
+ for (const preset of CronTab.PRESETS) {
194
+ table[MonitoringIntervalUtil.toLookupKey(preset.label)] = preset.value;
195
+ }
196
+
197
+ /*
198
+ * The dashboard's own labels win where the two lists disagree on
199
+ * wording, because that is what an operator sees on screen and what
200
+ * they are most likely to paste into an API call.
201
+ */
202
+ for (const [label, cron] of Object.entries(DASHBOARD_DROPDOWN_LABELS)) {
203
+ table[label] = cron;
204
+ }
205
+
206
+ for (const [label, cron] of Object.entries(ADVERB_LABELS)) {
207
+ table[label] = cron;
208
+ }
209
+
210
+ return table;
211
+ }
212
+
213
+ private static labelTable: Record<string, string> | null = null;
214
+
215
+ private static getLabelTable(): Record<string, string> {
216
+ if (!MonitoringIntervalUtil.labelTable) {
217
+ MonitoringIntervalUtil.labelTable =
218
+ MonitoringIntervalUtil.buildLabelTable();
219
+ }
220
+
221
+ return MonitoringIntervalUtil.labelTable;
222
+ }
223
+
224
+ /*
225
+ * "5m", "every 5 minutes", "2 hrs", "1 day" -> cron, or null.
226
+ *
227
+ * A missing count means one ("every minute", "hourly" handled above;
228
+ * "minute" and "m" land here). The divisor guards above decide whether a
229
+ * count is expressible as a cron step at all.
230
+ */
231
+ private static fromDurationGrammar(key: string): string | null {
232
+ /*
233
+ * A digit may sit flush against its unit ("5m"); a spelled-out number
234
+ * needs a separator or "fiveminutes" would parse. Hence the two
235
+ * alternatives rather than one `\s*`.
236
+ */
237
+ const match: RegExpMatchArray | null = key.match(
238
+ /^(every\s+)?(?:(\d+)\s*|([a-z]+)\s+)?([a-z]+)$/,
239
+ );
240
+
241
+ if (!match) {
242
+ return null;
243
+ }
244
+
245
+ const hasEveryPrefix: boolean = Boolean(match[1]);
246
+ const digitCount: string | undefined = match[2];
247
+ const wordCount: string | undefined = match[3];
248
+ const unit: string = match[4]!;
249
+ const rawCount: string | undefined = digitCount ?? wordCount;
250
+
251
+ let count: number;
252
+
253
+ if (rawCount === undefined) {
254
+ /*
255
+ * A bare unit with no count and no "every" is not a cadence, it is a
256
+ * noun: "minutes" tells us nothing. Require one or the other, so
257
+ * "every minute" and "1 minute" are read and "minutes" is refused.
258
+ */
259
+ if (!hasEveryPrefix) {
260
+ return null;
261
+ }
262
+
263
+ count = 1;
264
+ } else if (DIGITS_ONLY.test(rawCount)) {
265
+ count = parseInt(rawCount, 10);
266
+ } else {
267
+ /*
268
+ * Spelled-out numbers: "Every Five Minutes" is a real production
269
+ * value, so the grammar has to read words as well as digits.
270
+ */
271
+ const spelled: number | undefined = SPELLED_NUMBERS[rawCount];
272
+
273
+ if (spelled === undefined) {
274
+ return null;
275
+ }
276
+
277
+ count = spelled;
278
+ }
279
+
280
+ if (!Number.isFinite(count) || count < 1) {
281
+ return null;
282
+ }
283
+
284
+ if (MINUTE_UNITS.includes(unit)) {
285
+ if (count === 1) {
286
+ return "* * * * *";
287
+ }
288
+
289
+ if (count === 60) {
290
+ return "0 * * * *";
291
+ }
292
+
293
+ return minutesDivideEvenly(count) ? `*/${count} * * * *` : null;
294
+ }
295
+
296
+ if (HOUR_UNITS.includes(unit)) {
297
+ if (count === 1) {
298
+ return "0 * * * *";
299
+ }
300
+
301
+ if (count === 24) {
302
+ return "0 0 * * *";
303
+ }
304
+
305
+ return hoursDivideEvenly(count) ? `0 */${count} * * *` : null;
306
+ }
307
+
308
+ if (DAY_UNITS.includes(unit)) {
309
+ return count === 1 ? "0 0 * * *" : null;
310
+ }
311
+
312
+ if (WEEK_UNITS.includes(unit)) {
313
+ return count === 1 ? "0 0 * * 0" : null;
314
+ }
315
+
316
+ return null;
317
+ }
318
+
319
+ /**
320
+ * Read a stored (or inbound) monitoring interval.
321
+ *
322
+ * Never throws. Never guesses at something it does not understand — an
323
+ * unrecognized value comes back as Unrecognized with a null cron, and the
324
+ * caller decides whether that is a 400, a log line, or a skipped row.
325
+ */
326
+ public static normalize(
327
+ value: string | null | undefined,
328
+ ): MonitoringIntervalNormalization {
329
+ if (value === null || value === undefined || typeof value !== "string") {
330
+ return {
331
+ status: MonitoringIntervalNormalizationStatus.Empty,
332
+ cron: null,
333
+ input: null,
334
+ };
335
+ }
336
+
337
+ const collapsed: string = value.replace(/[\s\u00a0]+/g, " ").trim();
338
+
339
+ if (collapsed === "") {
340
+ return {
341
+ status: MonitoringIntervalNormalizationStatus.Empty,
342
+ cron: null,
343
+ input: value,
344
+ };
345
+ }
346
+
347
+ /*
348
+ * Valid cron first, and on the RAW value.
349
+ *
350
+ * This is what keeps the ~4,700 rows that were always correct on an
351
+ * identical code path, byte for byte. It also means anything cron-parser
352
+ * accepts but this hand-rolled grammar does not — "@hourly", six-field
353
+ * expressions, step/range combinations nobody here anticipated — is
354
+ * passed through untouched rather than being "corrected" into something
355
+ * else. A normalizer that disagrees with the runtime parser would
356
+ * reintroduce this very bug in mirror image.
357
+ */
358
+ if (CronTab.isValid(collapsed)) {
359
+ return {
360
+ status:
361
+ collapsed === value
362
+ ? MonitoringIntervalNormalizationStatus.AlreadyCanonical
363
+ : MonitoringIntervalNormalizationStatus.Normalized,
364
+ cron: collapsed,
365
+ input: value,
366
+ };
367
+ }
368
+
369
+ const key: string = MonitoringIntervalUtil.toLookupKey(collapsed);
370
+
371
+ const fromLabel: string | undefined =
372
+ MonitoringIntervalUtil.getLabelTable()[key];
373
+
374
+ if (fromLabel) {
375
+ return {
376
+ status: MonitoringIntervalNormalizationStatus.Normalized,
377
+ cron: fromLabel,
378
+ input: value,
379
+ };
380
+ }
381
+
382
+ /*
383
+ * "every-1-min" and friends: treat a hyphen or underscore as a space and
384
+ * try the label table and the grammar again. Done after the plain
385
+ * lookup so a legitimate cron is never mangled by it.
386
+ */
387
+ const looseKey: string = key.replace(/[-_]+/g, " ").replace(/\s+/g, " ");
388
+
389
+ const fromLooseLabel: string | undefined =
390
+ MonitoringIntervalUtil.getLabelTable()[looseKey];
391
+
392
+ if (fromLooseLabel) {
393
+ return {
394
+ status: MonitoringIntervalNormalizationStatus.Normalized,
395
+ cron: fromLooseLabel,
396
+ input: value,
397
+ };
398
+ }
399
+
400
+ const fromGrammar: string | null =
401
+ MonitoringIntervalUtil.fromDurationGrammar(key) ||
402
+ MonitoringIntervalUtil.fromDurationGrammar(looseKey);
403
+
404
+ if (fromGrammar) {
405
+ return {
406
+ status: MonitoringIntervalNormalizationStatus.Normalized,
407
+ cron: fromGrammar,
408
+ input: value,
409
+ };
410
+ }
411
+
412
+ return {
413
+ status: MonitoringIntervalNormalizationStatus.Unrecognized,
414
+ cron: null,
415
+ input: value,
416
+ };
417
+ }
418
+
419
+ /**
420
+ * The cron this value means, or null if it is empty or not understood.
421
+ *
422
+ * The convenience form for read paths that only want a schedule and have
423
+ * their own fallback for "no schedule".
424
+ */
425
+ public static toCronOrNull(value: string | null | undefined): string | null {
426
+ return MonitoringIntervalUtil.normalize(value).cron;
427
+ }
428
+
429
+ /**
430
+ * The canonical cron for a recognised cadence label, for building error
431
+ * messages that tell the caller what to write instead.
432
+ */
433
+ public static getSuggestedCronForMessage(): string {
434
+ return "*/5 * * * *";
435
+ }
436
+ }
@@ -0,0 +1,31 @@
1
+ import MonitorType from "../../Types/Monitor/MonitorType";
2
+ import {
3
+ RuleCriteriaFilter,
4
+ RuleCriteriaOperator,
5
+ } from "../../Types/Rules/RuleCriteria";
6
+
7
+ const MONITOR_TYPES: ReadonlySet<string> = new Set(Object.values(MonitorType));
8
+
9
+ export function isMonitorTypeCriteriaValue(
10
+ value: unknown,
11
+ ): value is MonitorType {
12
+ return typeof value === "string" && MONITOR_TYPES.has(value);
13
+ }
14
+
15
+ /** Monitor types are enum choices, so text and relation operators do not apply. */
16
+ export function getMonitorTypeCriteriaValidationError(
17
+ filter: RuleCriteriaFilter,
18
+ ): string | null {
19
+ if (
20
+ filter.operator !== RuleCriteriaOperator.Equals &&
21
+ filter.operator !== RuleCriteriaOperator.NotEquals
22
+ ) {
23
+ return "Monitor type criteria only support Equals and Does not equal operators.";
24
+ }
25
+
26
+ if (!isMonitorTypeCriteriaValue(filter.value)) {
27
+ return "Monitor type criteria require a valid monitor type.";
28
+ }
29
+
30
+ return null;
31
+ }
@@ -15,9 +15,6 @@
15
15
  * DOM.
16
16
  */
17
17
 
18
- /** U+2212 MINUS SIGN — reads as a minus rather than a hyphen at tile sizes. */
19
- const MINUS_SIGN: string = "−";
20
-
21
18
  /**
22
19
  * Seconds per unit, largest first. Only the two most significant non-zero
23
20
  * units are rendered: "2d 5h" is what an on-call engineer needs, "2d 5h
@@ -97,6 +94,16 @@ export const formatDurationCompact: FormatDurationCompactFunction = (
97
94
  return parts.join(" ");
98
95
  };
99
96
 
97
+ type FormatBudgetDurationFunction = (seconds: number) => string;
98
+
99
+ const formatBudgetDuration: FormatBudgetDurationFunction = (
100
+ seconds: number,
101
+ ): string => {
102
+ return seconds !== 0 && Math.abs(seconds) < 1
103
+ ? "under 1s"
104
+ : formatDurationCompact(seconds);
105
+ };
106
+
100
107
  /**
101
108
  * `errorBudgetRemainingSeconds` is SIGNED: negative means the budget is
102
109
  * overspent, and that overage is the single most useful number during a
@@ -121,10 +128,10 @@ export const formatErrorBudgetRemaining: FormatErrorBudgetRemainingFunction = (
121
128
  }
122
129
 
123
130
  if (numericSeconds < 0) {
124
- return `${MINUS_SIGN}${formatDurationCompact(numericSeconds)} over budget`;
131
+ return `${formatBudgetDuration(numericSeconds)} over budget`;
125
132
  }
126
133
 
127
- return `${formatDurationCompact(numericSeconds)} left`;
134
+ return `${formatBudgetDuration(numericSeconds)} left`;
128
135
  };
129
136
 
130
137
  /**
@@ -161,5 +168,14 @@ export const formatErrorBudgetRemainingOfTotal: FormatErrorBudgetRemainingOfTota
161
168
  return remainingText;
162
169
  }
163
170
 
164
- return `${remainingText} of ${formatDurationCompact(totalSeconds)}`;
171
+ const totalText: string = formatBudgetDuration(totalSeconds);
172
+
173
+ if (
174
+ typeof data.remainingSeconds === "number" &&
175
+ data.remainingSeconds < 0
176
+ ) {
177
+ return `${remainingText} · ${totalText} allowed`;
178
+ }
179
+
180
+ return `${remainingText} of ${totalText}`;
165
181
  };
@@ -248,16 +248,17 @@ export const getSloNotice: GetSloNoticeFunction = (
248
248
  }
249
249
 
250
250
  /*
251
- * What is left are the worker's two data guards: the attached monitors
252
- * no longer exist, or none of them has reported a status yet. Both are
253
- * answered on the Monitors page.
251
+ * Monitors may have been attached since the last evaluation, or the
252
+ * worker may still be waiting for their status data. A stored
253
+ * Misconfigured status alone does not mean the current setup is wrong.
254
+ * Keep the monitor link so users can check data that is still missing.
254
255
  */
255
256
  return {
256
- type: SloNoticeType.Warning,
257
- title: "This SLO cannot be evaluated",
258
- body: "Check that its monitors still exist and have started reporting a status, then wait a few minutes for the next evaluation.",
257
+ type: SloNoticeType.Info,
258
+ title: "Waiting for SLO evaluation",
259
+ body: "Monitors are attached. OneUptime evaluates this SLO every few minutes. Results will appear once monitor status data is available and the next evaluation completes.",
259
260
  action: {
260
- label: "Review monitors",
261
+ label: "View monitors",
261
262
  target: SloNoticeActionTarget.Monitors,
262
263
  },
263
264
  };
@@ -1,10 +1,15 @@
1
1
  import FilterCondition from "../../Types/Filter/FilterCondition";
2
2
  import ObjectID from "../../Types/ObjectID";
3
+ import MonitorType from "../../Types/Monitor/MonitorType";
3
4
  import RuleCriteria, {
4
5
  RuleCriteriaFilter,
5
6
  RuleCriteriaOperator,
6
7
  } from "../../Types/Rules/RuleCriteria";
7
8
  import { isValidRuleCriteria } from "../Rules/RuleCriteriaMatcher";
9
+ import {
10
+ getMonitorTypeCriteriaValidationError,
11
+ isMonitorTypeCriteriaValue,
12
+ } from "../Rules/MonitorTypeRuleCriteria";
8
13
 
9
14
  /*
10
15
  * Plain-English descriptions of what an SLO monitor rule matches, for the
@@ -33,6 +38,7 @@ export const SLO_MONITOR_RULE_CRITERIA_FIELD_TITLES: Readonly<
33
38
  Record<string, string>
34
39
  > = {
35
40
  monitorLabels: "Labels",
41
+ monitorType: "Type",
36
42
  monitorNamePattern: "Name",
37
43
  monitorDescriptionPattern: "Description",
38
44
  };
@@ -78,6 +84,7 @@ export interface SloMonitorRuleLabelReference {
78
84
  export interface SloMonitorRuleCriteriaCarrier {
79
85
  criteria?: RuleCriteria | null | undefined;
80
86
  monitorLabels?: Array<SloMonitorRuleLabelReference> | undefined;
87
+ monitorType?: MonitorType | null | undefined;
81
88
  monitorNamePattern?: string | null | undefined;
82
89
  monitorDescriptionPattern?: string | null | undefined;
83
90
  }
@@ -247,7 +254,15 @@ export const describeSloMonitorRuleCriteria: DescribeSloMonitorRuleCriteriaFunct
247
254
  }
248
255
 
249
256
  if (hasConfiguredCriteria(rule)) {
250
- if (!isValidRuleCriteria(rule.criteria)) {
257
+ if (
258
+ !isValidRuleCriteria(rule.criteria) ||
259
+ rule.criteria.filters.some((filter: RuleCriteriaFilter): boolean => {
260
+ return (
261
+ filter.field === "monitorType" &&
262
+ getMonitorTypeCriteriaValidationError(filter) !== null
263
+ );
264
+ })
265
+ ) {
251
266
  return SLO_MONITOR_RULE_INVALID_CRITERIA_DESCRIPTION;
252
267
  }
253
268
 
@@ -267,6 +282,14 @@ export const describeSloMonitorRuleCriteria: DescribeSloMonitorRuleCriteriaFunct
267
282
  .join(connector);
268
283
  }
269
284
 
285
+ if (
286
+ rule.monitorType !== undefined &&
287
+ rule.monitorType !== null &&
288
+ !isMonitorTypeCriteriaValue(rule.monitorType)
289
+ ) {
290
+ return SLO_MONITOR_RULE_INVALID_CRITERIA_DESCRIPTION;
291
+ }
292
+
270
293
  const parts: Array<string> = [];
271
294
 
272
295
  const legacyLabelIds: Array<string> = getSloMonitorRuleLabelIds(rule);
@@ -279,6 +302,14 @@ export const describeSloMonitorRuleCriteria: DescribeSloMonitorRuleCriteriaFunct
279
302
  );
280
303
  }
281
304
 
305
+ if (rule.monitorType) {
306
+ parts.push(
307
+ `${SLO_MONITOR_RULE_CRITERIA_FIELD_TITLES["monitorType"]} ${
308
+ SLO_MONITOR_RULE_OPERATOR_PHRASES[RuleCriteriaOperator.Equals]
309
+ } ${quote(rule.monitorType)}`,
310
+ );
311
+ }
312
+
282
313
  if (rule.monitorNamePattern) {
283
314
  parts.push(
284
315
  `${SLO_MONITOR_RULE_CRITERIA_FIELD_TITLES["monitorNamePattern"]} ${
@@ -358,6 +389,7 @@ export const getSloMonitorRuleCriteriaKey: GetSloMonitorRuleCriteriaKeyFunction
358
389
  return id.toLowerCase();
359
390
  })
360
391
  .sort(),
392
+ monitorType: rule.monitorType || "",
361
393
  name: rule.monitorNamePattern || "",
362
394
  description: rule.monitorDescriptionPattern || "",
363
395
  })}`;
@@ -102,6 +102,39 @@ export const getSloTargetText: GetSloTargetTextFunction = (
102
102
  return target === null ? null : `Target ${target}`;
103
103
  };
104
104
 
105
+ /**
106
+ * A budget below zero is exceeded, not a useful negative percentage left.
107
+ * Classify before rounding so even a small overage cannot read as 0%.
108
+ * The signed value remains available to calculations and history charts.
109
+ */
110
+ export type GetSloBudgetRemainingTextFunction = (
111
+ percentage: number | undefined | null,
112
+ ) => string | null;
113
+
114
+ export const getSloBudgetRemainingText: GetSloBudgetRemainingTextFunction = (
115
+ percentage: number | undefined | null,
116
+ ): string | null => {
117
+ const remaining: number | null = toFiniteNumber(percentage);
118
+
119
+ if (remaining === null) {
120
+ return null;
121
+ }
122
+
123
+ if (remaining < 0) {
124
+ return "Budget exceeded";
125
+ }
126
+
127
+ if (remaining === 0) {
128
+ return "No budget left";
129
+ }
130
+
131
+ if (remaining < 0.1) {
132
+ return "<0.1%";
133
+ }
134
+
135
+ return formatSloPercent(Math.min(remaining, 100), 1);
136
+ };
137
+
105
138
  /** "No monitors" / "1 monitor" / "12 monitors". */
106
139
  export type GetSloMonitorCountTextFunction = (count: number) => string;
107
140
 
@@ -518,15 +518,16 @@ export interface SloBudgetBarGeometry {
518
518
  atRiskThresholdPercentage: number;
519
519
  /** Same tier as every other red/amber/green on the SLO pages. */
520
520
  tier: SloBudgetTier;
521
- /** Remaining budget is below zero — the fill is clamped, the text is not. */
521
+ /** Remaining budget is below zero; preserve this even though the fill is zero. */
522
522
  isOverspent: boolean;
523
523
  }
524
524
 
525
525
  /**
526
526
  * Everything the budget bar draws, from the SIGNED remaining percentage.
527
527
  *
528
- * The percentage is unrounded and uncapped below zero (an overspent budget
529
- * reads -340%), so only the drawing is clamped; the colour comes from
528
+ * The input percentage is unrounded and uncapped below zero, so an overspent
529
+ * budget still gets an explicit exceeded state when the drawing is clamped.
530
+ * The colour comes from
530
531
  * SloHealth.getSloBudgetTier with the SLO's own threshold, so the bar can
531
532
  * never be green while the status pill says At Risk.
532
533
  */