@oneuptime/common 13.0.8 → 14.0.0

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 (333) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Monitor.ts +3 -2
  4. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  5. package/Scripts/generate-service-worker.js +65 -4
  6. package/Server/API/BaseAPI.ts +26 -5
  7. package/Server/API/GlobalConfigAPI.ts +228 -462
  8. package/Server/API/ProjectOIDC.ts +25 -16
  9. package/Server/API/ProjectSSO.ts +24 -16
  10. package/Server/API/StatusPageAPI.ts +100 -42
  11. package/Server/API/TeamMemberAPI.ts +10 -1
  12. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  13. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  14. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  15. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  16. package/Server/EnvironmentConfig.ts +147 -2
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  19. package/Server/Middleware/UserAuthorization.ts +38 -16
  20. package/Server/Services/AuditLogService.ts +84 -1079
  21. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  22. package/Server/Services/GlobalConfigService.ts +80 -1
  23. package/Server/Services/MonitorProbeService.ts +116 -20
  24. package/Server/Services/MonitorService.ts +56 -4
  25. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  26. package/Server/Services/MonitorTemplateService.ts +26 -0
  27. package/Server/Services/ProjectService.ts +284 -0
  28. package/Server/Services/RumSessionPinService.ts +25 -6
  29. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  30. package/Server/Services/StatusPageService.ts +78 -1
  31. package/Server/Services/TeamMemberService.ts +11 -0
  32. package/Server/Services/TeamService.ts +10 -0
  33. package/Server/Services/UserService.ts +10 -17
  34. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  35. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  36. package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
  37. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  38. package/Server/Types/Markdown.ts +41 -0
  39. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  40. package/Server/Utils/EditionEnforcement.ts +233 -0
  41. package/Server/Utils/FrontendEnvironment.ts +41 -2
  42. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  43. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  44. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  45. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  46. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  47. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  48. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  49. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  50. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  51. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  52. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  53. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  54. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  55. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  56. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  57. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  58. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  59. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  60. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
  61. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  62. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  63. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
  64. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  65. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  66. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  67. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  68. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  69. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  70. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  71. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  72. package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
  73. package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
  74. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  75. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  76. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  77. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  78. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  79. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  80. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  81. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  82. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  83. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  84. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  85. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  86. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  87. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  88. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  89. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  90. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  91. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  92. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  93. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  95. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  96. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  97. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  98. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  99. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  100. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  101. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  102. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  103. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  104. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  105. package/Tests/Server/Types/Markdown.test.ts +181 -0
  106. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  107. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  108. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  109. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  110. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  111. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  112. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  113. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  114. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  115. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  116. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  117. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  118. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  119. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  120. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
  121. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  122. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  123. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  124. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  125. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  126. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  127. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  128. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  129. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  130. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  131. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  132. package/Types/API/Hostname.ts +63 -16
  133. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  134. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  135. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  136. package/Types/Database/CanonicalModelInstance.ts +102 -0
  137. package/Types/Database/TableColumn.ts +20 -6
  138. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  139. package/Types/IP/IPv6.ts +6 -1
  140. package/Types/Monitor/MonitorStep.ts +6 -1
  141. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  142. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  143. package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
  144. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  145. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  146. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  147. package/UI/Components/ModelList/ModelList.tsx +11 -1
  148. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  149. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  150. package/UI/esbuild-enterprise.js +263 -0
  151. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  152. package/Utils/HostAddressUtil.ts +94 -0
  153. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  154. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  155. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  156. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  157. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  159. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  161. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  163. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  164. package/build/dist/Server/API/BaseAPI.js +26 -5
  165. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  166. package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
  167. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  168. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  169. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  170. package/build/dist/Server/API/ProjectSSO.js +24 -16
  171. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  172. package/build/dist/Server/API/StatusPageAPI.js +93 -43
  173. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  174. package/build/dist/Server/API/TeamMemberAPI.js +8 -1
  175. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  176. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  177. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  178. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  179. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  180. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  181. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  182. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  183. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  184. package/build/dist/Server/EnvironmentConfig.js +84 -2
  185. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  190. package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
  191. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  192. package/build/dist/Server/Services/AuditLogService.js +71 -740
  193. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  194. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  195. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  196. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  197. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  198. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  199. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  200. package/build/dist/Server/Services/MonitorService.js +45 -4
  201. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  202. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  203. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  204. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  205. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  206. package/build/dist/Server/Services/ProjectService.js +214 -0
  207. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  208. package/build/dist/Server/Services/RumSessionPinService.js +24 -6
  209. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  210. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  211. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  212. package/build/dist/Server/Services/StatusPageService.js +71 -0
  213. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  214. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  215. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  216. package/build/dist/Server/Services/TeamService.js +9 -0
  217. package/build/dist/Server/Services/TeamService.js.map +1 -1
  218. package/build/dist/Server/Services/UserService.js +10 -16
  219. package/build/dist/Server/Services/UserService.js.map +1 -1
  220. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  221. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  222. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  223. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  224. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
  225. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  226. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  227. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  228. package/build/dist/Server/Types/Markdown.js +31 -0
  229. package/build/dist/Server/Types/Markdown.js.map +1 -1
  230. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  231. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  232. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  233. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  234. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  235. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  236. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  237. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  238. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  239. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  240. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  241. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  242. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  243. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  244. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  245. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  246. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  247. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  248. package/build/dist/Types/API/Hostname.js +59 -11
  249. package/build/dist/Types/API/Hostname.js.map +1 -1
  250. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  251. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  252. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  253. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  254. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  255. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  256. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  257. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  258. package/build/dist/Types/Database/TableColumn.js +19 -6
  259. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  260. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  261. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  262. package/build/dist/Types/IP/IPv6.js +6 -1
  263. package/build/dist/Types/IP/IPv6.js.map +1 -1
  264. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  265. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  266. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  267. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  268. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  269. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
  270. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  271. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  272. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  273. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  274. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  275. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  276. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  277. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  278. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  279. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  280. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  281. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  282. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  283. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  284. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  285. package/build/dist/Utils/HostAddressUtil.js +80 -0
  286. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  287. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  288. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  289. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  290. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  291. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  292. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  293. package/jest.config.json +3 -1
  294. package/package.json +1 -1
  295. package/test-setup.sh +103 -34
  296. package/tsconfig.json +13 -0
  297. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  298. package/Server/API/TeamComplianceAPI.ts +0 -158
  299. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  300. package/Server/Services/TeamComplianceService.ts +0 -733
  301. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  302. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  303. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  304. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  305. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
  306. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  307. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  308. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
  309. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  310. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  311. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  312. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  313. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  314. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  315. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  316. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  317. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  318. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  319. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  320. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  321. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  322. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  323. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  324. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  325. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  326. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  327. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  328. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  329. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  330. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  331. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  332. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  333. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -0,0 +1,304 @@
1
+ import MonitoringIntervalUtil, {
2
+ MonitoringIntervalNormalization,
3
+ MonitoringIntervalNormalizationStatus,
4
+ } from "../../../Utils/Monitor/MonitoringIntervalUtil";
5
+ import CronTab from "../../../Utils/CronTab";
6
+ import { describe, expect, test } from "@jest/globals";
7
+
8
+ /*
9
+ * WHAT THIS FILE IS DEFENDING
10
+ *
11
+ * Monitor.monitoringInterval is free-text and was never validated. Rows
12
+ * accumulated holding a human cadence ("5m", "Every 5 minutes") where a cron
13
+ * expression was expected. cron-parser threw on those, the claim query
14
+ * caught the throw and fell back to "one minute from now", and a monitor
15
+ * configured for five minutes was probed every ~75 seconds — four times the
16
+ * requested rate, silently, for as long as the row existed.
17
+ *
18
+ * 103 monitors across 10 projects were in that state. The ten distinct bad
19
+ * values below are the REAL production census, not invented examples, and
20
+ * every one of them is asserted individually.
21
+ */
22
+
23
+ function normalize(value: string | null | undefined): string | null {
24
+ return MonitoringIntervalUtil.toCronOrNull(value);
25
+ }
26
+
27
+ describe("MonitoringIntervalUtil", () => {
28
+ describe("the production census — every bad value found in the wild", () => {
29
+ /*
30
+ * SELECT "monitoringInterval", count(*) FROM "Monitor"
31
+ * WHERE "deletedAt" IS NULL AND "monitoringInterval" is not cron
32
+ * 103 rows, 10 distinct values, 10 projects.
33
+ */
34
+ const PRODUCTION_VALUES: Array<[string, string, number]> = [
35
+ ["Every 5 minutes", "*/5 * * * *", 35],
36
+ ["5m", "*/5 * * * *", 19],
37
+ ["1 minute", "* * * * *", 18],
38
+ ["Every 5 Minutes", "*/5 * * * *", 14],
39
+ ["Every 1 minute", "* * * * *", 6],
40
+ ["Every 2 minutes", "*/2 * * * *", 4],
41
+ ["5 minutes", "*/5 * * * *", 3],
42
+ ["1 day", "0 0 * * *", 2],
43
+ ["every-1-min", "* * * * *", 1],
44
+ ["Every Five Minutes", "*/5 * * * *", 1],
45
+ ];
46
+
47
+ test.each(PRODUCTION_VALUES)(
48
+ "reads %j as %j (%i monitors in production)",
49
+ (input: string, expectedCron: string) => {
50
+ expect(normalize(input)).toBe(expectedCron);
51
+ },
52
+ );
53
+
54
+ test("every production value now yields a parseable cron", () => {
55
+ for (const [input] of PRODUCTION_VALUES) {
56
+ const cron: string | null = normalize(input);
57
+
58
+ /*
59
+ * "Every Five Minutes" spells its number out, which the duration
60
+ * grammar cannot read. It is covered by the exact-label table; if
61
+ * that entry is ever dropped this assertion is what catches it.
62
+ */
63
+ expect(cron).not.toBeNull();
64
+ expect(CronTab.isValid(cron as string)).toBe(true);
65
+ }
66
+ });
67
+
68
+ test("the customer's value is five minutes apart, not one", () => {
69
+ /*
70
+ * The regression in one line: "5m" must not mean "every minute".
71
+ */
72
+ expect(normalize("5m")).toBe("*/5 * * * *");
73
+ expect(normalize("5m")).not.toBe("* * * * *");
74
+ });
75
+ });
76
+
77
+ describe("valid cron is returned untouched", () => {
78
+ const VALID: Array<string> = [
79
+ "* * * * *",
80
+ "*/2 * * * *",
81
+ "*/5 * * * *",
82
+ "*/10 * * * *",
83
+ "*/15 * * * *",
84
+ "*/30 * * * *",
85
+ "0 * * * *",
86
+ "0 */6 * * *",
87
+ "0 0 * * *",
88
+ "0 0 * * 0",
89
+ "0 9 * * 1",
90
+ "3-59/5 * * * *",
91
+ "0,15,30,45 * * * *",
92
+ ];
93
+
94
+ test.each(VALID)("%j passes through byte-identical", (cron: string) => {
95
+ const result: MonitoringIntervalNormalization =
96
+ MonitoringIntervalUtil.normalize(cron);
97
+
98
+ expect(result.status).toBe(
99
+ MonitoringIntervalNormalizationStatus.AlreadyCanonical,
100
+ );
101
+ expect(result.cron).toBe(cron);
102
+ });
103
+
104
+ test("cron-parser shortcuts are never REWRITTEN into something else", () => {
105
+ /*
106
+ * "@hourly" is a deliberate seam between the two parsers. The
107
+ * isomorphic validator here is a hand-rolled 5-field check and does
108
+ * not know it; cron-parser, which actually schedules the probe, does.
109
+ *
110
+ * What matters is that this util never GUESSES: it reports
111
+ * Unrecognized with a null cron rather than rewriting "@hourly" into
112
+ * "0 * * * *". The server validator's cron-parser fallback is what
113
+ * accepts it on write, and resolveNextPingAt's raw-parse-first order
114
+ * is what honours it at runtime — both covered in their own suites.
115
+ * If this util ever started rewriting it, a stored value would change
116
+ * under the customer for no behavioural gain.
117
+ */
118
+ const result: MonitoringIntervalNormalization =
119
+ MonitoringIntervalUtil.normalize("@hourly");
120
+
121
+ expect(result.status).toBe(
122
+ MonitoringIntervalNormalizationStatus.Unrecognized,
123
+ );
124
+ expect(result.cron).toBeNull();
125
+ });
126
+ });
127
+
128
+ describe("empty is a legitimate state, never an error", () => {
129
+ /*
130
+ * ~4,177 production rows are NULL or blank. A Manual monitor is never
131
+ * probed; treating that as invalid would break every one of them.
132
+ */
133
+ test.each([null, undefined, "", " ", "\t\n"])(
134
+ "%j is Empty with a null cron",
135
+ (value: string | null | undefined) => {
136
+ const result: MonitoringIntervalNormalization =
137
+ MonitoringIntervalUtil.normalize(value);
138
+
139
+ expect(result.status).toBe(MonitoringIntervalNormalizationStatus.Empty);
140
+ expect(result.cron).toBeNull();
141
+ },
142
+ );
143
+ });
144
+
145
+ describe("label tolerance", () => {
146
+ test("is case-insensitive", () => {
147
+ expect(normalize("EVERY 5 MINUTES")).toBe("*/5 * * * *");
148
+ expect(normalize("every 5 minutes")).toBe("*/5 * * * *");
149
+ expect(normalize("Every 5 Minutes")).toBe("*/5 * * * *");
150
+ });
151
+
152
+ test("collapses runs of whitespace, including a non-breaking space", () => {
153
+ expect(normalize("Every 5 minutes")).toBe("*/5 * * * *");
154
+ expect(normalize(" Every 5 minutes ")).toBe("*/5 * * * *");
155
+ // U+00A0 survives a copy-paste out of rendered docs.
156
+ expect(normalize("Every 5 minutes")).toBe("*/5 * * * *");
157
+ });
158
+
159
+ test("strips wrapping quotes and a trailing period", () => {
160
+ expect(normalize('"Every 5 minutes"')).toBe("*/5 * * * *");
161
+ expect(normalize("'5m'")).toBe("*/5 * * * *");
162
+ expect(normalize("Every 5 minutes.")).toBe("*/5 * * * *");
163
+ });
164
+
165
+ test("reads hyphenated and underscored forms", () => {
166
+ expect(normalize("every-1-min")).toBe("* * * * *");
167
+ expect(normalize("every_5_minutes")).toBe("*/5 * * * *");
168
+ });
169
+
170
+ test("covers every label the shared preset list offers", () => {
171
+ /*
172
+ * The table is built by iterating CronTab.PRESETS, so a preset added
173
+ * there is understood here without anyone editing the util. This
174
+ * asserts that wiring rather than the individual labels.
175
+ */
176
+ for (const preset of CronTab.PRESETS) {
177
+ expect(normalize(preset.label)).toBe(preset.value);
178
+ }
179
+ });
180
+ });
181
+
182
+ describe("duration grammar", () => {
183
+ test.each([
184
+ ["1m", "* * * * *"],
185
+ ["1 min", "* * * * *"],
186
+ ["minutely", "* * * * *"],
187
+ ["2m", "*/2 * * * *"],
188
+ ["10 mins", "*/10 * * * *"],
189
+ ["15 minutes", "*/15 * * * *"],
190
+ ["30m", "*/30 * * * *"],
191
+ ["60m", "0 * * * *"],
192
+ ["1h", "0 * * * *"],
193
+ ["hourly", "0 * * * *"],
194
+ ["2 hours", "0 */2 * * *"],
195
+ ["6h", "0 */6 * * *"],
196
+ ["12 hrs", "0 */12 * * *"],
197
+ ["24h", "0 0 * * *"],
198
+ ["1d", "0 0 * * *"],
199
+ ["daily", "0 0 * * *"],
200
+ ["1 week", "0 0 * * 0"],
201
+ ["weekly", "0 0 * * 0"],
202
+ ])("reads %j as %j", (input: string, expected: string) => {
203
+ expect(normalize(input)).toBe(expected);
204
+ });
205
+ });
206
+
207
+ describe("refuses to store a lie", () => {
208
+ /*
209
+ * "*_/7 * * * *" fires at :00, :07 ... :56 and then :00 — a four-minute
210
+ * gap at every hour wrap. It is NOT "every 7 minutes", so normalizing
211
+ * "every 7 minutes" to it would store something false. Better to refuse
212
+ * and let the caller write the cron they actually mean.
213
+ */
214
+ test.each([
215
+ "7m",
216
+ "every 7 minutes",
217
+ "13 minutes",
218
+ "5 hours",
219
+ "7h",
220
+ "2 days",
221
+ "3 weeks",
222
+ ])(
223
+ "%j is Unrecognized rather than silently approximated",
224
+ (input: string) => {
225
+ const result: MonitoringIntervalNormalization =
226
+ MonitoringIntervalUtil.normalize(input);
227
+
228
+ expect(result.status).toBe(
229
+ MonitoringIntervalNormalizationStatus.Unrecognized,
230
+ );
231
+ expect(result.cron).toBeNull();
232
+ },
233
+ );
234
+
235
+ test("sub-minute cadences are not invented", () => {
236
+ /*
237
+ * The claim loop ticks once a minute, so a per-second cadence is a
238
+ * promise the probe fleet cannot keep.
239
+ */
240
+ for (const input of ["30s", "1s", "10 seconds", "1 sec"]) {
241
+ expect(normalize(input)).toBeNull();
242
+ }
243
+ });
244
+ });
245
+
246
+ describe("genuine nonsense stays Unrecognized", () => {
247
+ test.each([
248
+ "banana",
249
+ "asap",
250
+ "not a cron",
251
+ "soon",
252
+ "-1m",
253
+ "0m",
254
+ "every",
255
+ "minutes",
256
+ ])("%j", (input: string) => {
257
+ expect(normalize(input)).toBeNull();
258
+ });
259
+ });
260
+
261
+ describe("totality", () => {
262
+ /*
263
+ * It runs in a write hook, on the scheduler hot path and inside a
264
+ * migration. A throw in any of those is worse than a null.
265
+ */
266
+ test("never throws, whatever it is handed", () => {
267
+ const hostile: Array<unknown> = [
268
+ null,
269
+ undefined,
270
+ "",
271
+ "🙂",
272
+ "*".repeat(5000),
273
+ "{{ variable }}",
274
+ "; DROP TABLE Monitor; --",
275
+ "",
276
+ 12345,
277
+ {},
278
+ [],
279
+ ];
280
+
281
+ for (const value of hostile) {
282
+ expect(() => {
283
+ return MonitoringIntervalUtil.normalize(value as string);
284
+ }).not.toThrow();
285
+ }
286
+ });
287
+
288
+ test("is idempotent — normalizing its own output changes nothing", () => {
289
+ const inputs: Array<string> = [
290
+ "5m",
291
+ "Every 5 minutes",
292
+ "1 day",
293
+ "every-1-min",
294
+ "*/5 * * * *",
295
+ ];
296
+
297
+ for (const input of inputs) {
298
+ const once: string | null = normalize(input);
299
+ expect(once).not.toBeNull();
300
+ expect(normalize(once)).toBe(once);
301
+ }
302
+ });
303
+ });
304
+ });
@@ -0,0 +1,248 @@
1
+ import ObjectID from "../../../Types/ObjectID";
2
+ import { JSONObject } from "../../../Types/JSON";
3
+ import {
4
+ UptimeDailyAggregate,
5
+ UptimeDayBucket,
6
+ } from "../../../Types/StatusPage/UptimeDailyAggregate";
7
+ import UptimeDailyAggregateUtil from "../../../Utils/StatusPage/UptimeDailyAggregateUtil";
8
+ import { describe, expect, test } from "@jest/globals";
9
+
10
+ /*
11
+ * WHAT THIS FILE IS DEFENDING
12
+ *
13
+ * The wire format for the uptime aggregate, which replaced shipping raw
14
+ * MonitorStatusTimeline rows to the status page.
15
+ *
16
+ * Two properties matter more than the round trip itself:
17
+ *
18
+ * 1. It must be SMALL. The rows it replaces serialize to ~431 bytes each,
19
+ * almost entirely typed-JSON envelopes, so one real status page's 254,550
20
+ * matching rows came to ~105 MB. If this format ever grows envelopes it
21
+ * gives that back.
22
+ *
23
+ * 2. Parsing must be TOTAL. A status page that cannot read its aggregate has
24
+ * to fall back to "no data", which is honest. What it must never do is
25
+ * fall back to something that looks like uptime.
26
+ */
27
+
28
+ const MONITOR_A: string = "11111111-1111-4111-8111-111111111111";
29
+ const STATUS_UP: string = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
30
+
31
+ function sample(): UptimeDailyAggregate {
32
+ return {
33
+ monitors: [
34
+ {
35
+ monitorId: new ObjectID(MONITOR_A),
36
+ buckets: [
37
+ {
38
+ bucketStart: new Date("2026-07-01T00:00:00.000Z"),
39
+ bucketEnd: new Date("2026-07-02T00:00:00.000Z"),
40
+ daySeconds: 86400,
41
+ coveredSeconds: 86400,
42
+ statusDurations: [
43
+ {
44
+ monitorStatusId: new ObjectID(STATUS_UP),
45
+ seconds: 86400,
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ bucketStart: new Date("2026-07-02T00:00:00.000Z"),
51
+ bucketEnd: new Date("2026-07-03T00:00:00.000Z"),
52
+ daySeconds: 86400,
53
+ coveredSeconds: 0,
54
+ statusDurations: [],
55
+ },
56
+ ],
57
+ },
58
+ ],
59
+ isComplete: true,
60
+ completeFrom: null,
61
+ };
62
+ }
63
+
64
+ describe("UptimeDailyAggregateUtil", () => {
65
+ describe("round trip", () => {
66
+ test("survives toJSON -> fromJSON unchanged", () => {
67
+ const parsed: UptimeDailyAggregate = UptimeDailyAggregateUtil.fromJSON(
68
+ UptimeDailyAggregateUtil.toJSON(sample()),
69
+ );
70
+
71
+ expect(parsed.monitors).toHaveLength(1);
72
+ expect(parsed.monitors[0]!.monitorId.toString()).toBe(MONITOR_A);
73
+ expect(parsed.monitors[0]!.buckets).toHaveLength(2);
74
+
75
+ const first: UptimeDayBucket = parsed.monitors[0]!.buckets[0]!;
76
+
77
+ expect(first.coveredSeconds).toBe(86400);
78
+ expect(first.daySeconds).toBe(86400);
79
+ expect(first.bucketStart.toISOString()).toBe("2026-07-01T00:00:00.000Z");
80
+ expect(first.statusDurations[0]!.monitorStatusId.toString()).toBe(
81
+ STATUS_UP,
82
+ );
83
+ });
84
+
85
+ test("a no-data day survives as a no-data day", () => {
86
+ /*
87
+ * The single most important thing to preserve. If coverage were lost in
88
+ * transit the client would treat the day as ordinary and paint it with
89
+ * the operator's colour - which is the bug this whole change removes.
90
+ */
91
+ const parsed: UptimeDailyAggregate = UptimeDailyAggregateUtil.fromJSON(
92
+ UptimeDailyAggregateUtil.toJSON(sample()),
93
+ );
94
+
95
+ expect(parsed.monitors[0]!.buckets[1]!.coveredSeconds).toBe(0);
96
+ expect(parsed.monitors[0]!.buckets[1]!.statusDurations).toHaveLength(0);
97
+ });
98
+ });
99
+
100
+ describe("the wire format stays small", () => {
101
+ test("dates and ids are plain strings, not typed-JSON envelopes", () => {
102
+ const json: JSONObject = UptimeDailyAggregateUtil.toJSON(sample());
103
+ const encoded: string = JSON.stringify(json);
104
+
105
+ expect(encoded).not.toContain("_type");
106
+ expect(encoded).toContain("2026-07-01T00:00:00.000Z");
107
+ expect(encoded).toContain(MONITOR_A);
108
+ });
109
+
110
+ test("a 90-day, 50-monitor page stays far below the row payload it replaces", () => {
111
+ /*
112
+ * The rows this replaces were ~431 bytes each and a real page matched
113
+ * 254,550 of them (~105 MB). This asserts the replacement is bounded by
114
+ * structure rather than by how much a monitor flaps.
115
+ */
116
+ const aggregate: UptimeDailyAggregate = {
117
+ monitors: [],
118
+ isComplete: true,
119
+ completeFrom: null,
120
+ };
121
+
122
+ for (let m: number = 0; m < 50; m++) {
123
+ const buckets: Array<UptimeDayBucket> = [];
124
+
125
+ for (let d: number = 0; d < 90; d++) {
126
+ const start: Date = new Date(
127
+ Date.UTC(2026, 3, 1) + d * 24 * 60 * 60 * 1000,
128
+ );
129
+
130
+ buckets.push({
131
+ bucketStart: start,
132
+ bucketEnd: new Date(start.getTime() + 86400000),
133
+ daySeconds: 86400,
134
+ coveredSeconds: 86400,
135
+ statusDurations: [
136
+ { monitorStatusId: new ObjectID(STATUS_UP), seconds: 86000 },
137
+ { monitorStatusId: new ObjectID(MONITOR_A), seconds: 400 },
138
+ ],
139
+ });
140
+ }
141
+
142
+ aggregate.monitors.push({
143
+ monitorId: new ObjectID(
144
+ `${MONITOR_A.slice(0, -2)}${m % 10}${m % 10}`,
145
+ ),
146
+ buckets,
147
+ });
148
+ }
149
+
150
+ const bytes: number = JSON.stringify(
151
+ UptimeDailyAggregateUtil.toJSON(aggregate),
152
+ ).length;
153
+
154
+ // Comfortably under a megabyte for the largest page the product allows.
155
+ expect(bytes).toBeLessThan(1_500_000);
156
+ });
157
+ });
158
+
159
+ describe("parsing is total", () => {
160
+ test.each([
161
+ ["null", null],
162
+ ["undefined", undefined],
163
+ ["an empty object", {}],
164
+ ["monitors not an array", { monitors: "nope" }],
165
+ ])(
166
+ "%s yields an empty aggregate rather than throwing",
167
+ (_name: string, input: unknown) => {
168
+ expect(() => {
169
+ return UptimeDailyAggregateUtil.fromJSON(input as JSONObject);
170
+ }).not.toThrow();
171
+
172
+ const parsed: UptimeDailyAggregate = UptimeDailyAggregateUtil.fromJSON(
173
+ input as JSONObject,
174
+ );
175
+
176
+ expect(parsed.monitors).toHaveLength(0);
177
+ },
178
+ );
179
+
180
+ test("a bucket with an unparseable date is dropped, not turned into coverage", () => {
181
+ const parsed: UptimeDailyAggregate = UptimeDailyAggregateUtil.fromJSON({
182
+ monitors: [
183
+ {
184
+ monitorId: MONITOR_A,
185
+ buckets: [
186
+ {
187
+ bucketStart: "not-a-date",
188
+ bucketEnd: "also-not",
189
+ daySeconds: 1,
190
+ },
191
+ ],
192
+ },
193
+ ],
194
+ weird: true,
195
+ } as unknown as JSONObject);
196
+
197
+ expect(parsed.monitors[0]!.buckets).toHaveLength(0);
198
+ });
199
+
200
+ test("a missing isComplete means complete, not incomplete", () => {
201
+ /*
202
+ * An older server has no cap in this path either. Defaulting to
203
+ * incomplete would grey out every bar on a perfectly healthy page.
204
+ */
205
+ const parsed: UptimeDailyAggregate = UptimeDailyAggregateUtil.fromJSON({
206
+ monitors: [],
207
+ } as unknown as JSONObject);
208
+
209
+ expect(parsed.isComplete).toBe(true);
210
+ });
211
+
212
+ test("an explicit isComplete false is honoured", () => {
213
+ const parsed: UptimeDailyAggregate = UptimeDailyAggregateUtil.fromJSON({
214
+ monitors: [],
215
+ isComplete: false,
216
+ completeFrom: "2026-07-05T00:00:00.000Z",
217
+ } as unknown as JSONObject);
218
+
219
+ expect(parsed.isComplete).toBe(false);
220
+ expect(parsed.completeFrom?.toISOString()).toBe(
221
+ "2026-07-05T00:00:00.000Z",
222
+ );
223
+ });
224
+ });
225
+
226
+ describe("getBucketsForMonitor", () => {
227
+ test("returns that monitor's buckets", () => {
228
+ expect(
229
+ UptimeDailyAggregateUtil.getBucketsForMonitor(sample(), MONITOR_A),
230
+ ).toHaveLength(2);
231
+ });
232
+
233
+ test("returns empty for a monitor that is not in the aggregate", () => {
234
+ expect(
235
+ UptimeDailyAggregateUtil.getBucketsForMonitor(
236
+ sample(),
237
+ "99999999-9999-4999-8999-999999999999",
238
+ ),
239
+ ).toHaveLength(0);
240
+ });
241
+
242
+ test("returns empty rather than throwing when there is no aggregate", () => {
243
+ expect(
244
+ UptimeDailyAggregateUtil.getBucketsForMonitor(null, MONITOR_A),
245
+ ).toHaveLength(0);
246
+ });
247
+ });
248
+ });
@@ -3,6 +3,8 @@ import BadDataException from "../Exception/BadDataException";
3
3
  import { JSONObject, ObjectType } from "../JSON";
4
4
  import Port from "../Port";
5
5
  import Typeof from "../Typeof";
6
+ import HostAddressUtil from "../../Utils/HostAddressUtil";
7
+ import IP from "../IP/IP";
6
8
  import { FindOperator } from "typeorm";
7
9
 
8
10
  /*
@@ -22,10 +24,26 @@ const HOST_REGEX: RegExp =
22
24
  */
23
25
  const USER_INFO_REGEX: RegExp = /^[^@/?#\s[\]]*$/;
24
26
 
25
- const IPV6_CHARS_REGEX: RegExp = /^[0-9a-fA-F:.]+$/;
26
-
27
27
  const PORT_REGEX: RegExp = /^\d{1,5}$/;
28
28
 
29
+ /*
30
+ * The authority spellings of an IPv6 host: bracketed ("[::1]", "[::1]:8080")
31
+ * or bare. Two or more colons cannot be a "host:port", so every colon in a
32
+ * bare value belongs to the address itself.
33
+ *
34
+ * This only decides which SHAPE a value has. Whether what it holds is really
35
+ * an address is IP.isIP's job, and isValid below asks it -- the old code
36
+ * settled for a "[0-9a-fA-F:.]+" charset, which called "::::" and
37
+ * "1.2.3.4.5.6" valid hosts and then handed them to a socket.
38
+ */
39
+ function isIPv6Authority(authority: string): boolean {
40
+ return authority.startsWith("[") || (authority.match(/:/g) || []).length > 1;
41
+ }
42
+
43
+ function isIPv6Address(value: string): boolean {
44
+ return Boolean(value) && IP.isIP(value) && new IP(value).isIPv6();
45
+ }
46
+
29
47
  export default class Hostname extends DatabaseProperty {
30
48
  private _route: string = "";
31
49
  public get hostname(): string {
@@ -104,15 +122,15 @@ export default class Hostname extends DatabaseProperty {
104
122
  /^\[([0-9a-fA-F:.]+)\](?::(\d{1,5}))?$/,
105
123
  );
106
124
  if (bracketedIpv6Match) {
107
- return true;
125
+ return isIPv6Address(bracketedIpv6Match[1] as string);
108
126
  }
109
127
 
110
128
  /*
111
129
  * Unbracketed IPv6 literal. Two or more colons cannot be a "host:port",
112
130
  * so the colons must be part of the address itself.
113
131
  */
114
- if ((authority.match(/:/g) || []).length > 1) {
115
- return IPV6_CHARS_REGEX.test(authority);
132
+ if (isIPv6Authority(authority)) {
133
+ return isIPv6Address(authority);
116
134
  }
117
135
 
118
136
  const colonIndex: number = authority.indexOf(":");
@@ -158,28 +176,57 @@ export default class Hostname extends DatabaseProperty {
158
176
  throw new BadDataException("Invalid JSON: " + JSON.stringify(json));
159
177
  }
160
178
 
179
+ /*
180
+ * The authority form. An IPv6 host is BRACKETED once a port is appended,
181
+ * because "2001:db8::1" + ":179" is not an ambiguous-looking string — it is
182
+ * "2001:db8::1:179", which net.isIP() accepts as a different, perfectly
183
+ * valid address. Anything that read this back (or showed it to an operator)
184
+ * was therefore pointed at a host nobody configured.
185
+ *
186
+ * Without a port the host is returned exactly as it is held, brackets and
187
+ * all. A bracketed value came from a URL authority and has to go back into
188
+ * one — dropping the brackets there would make URL.toString() emit
189
+ * "https://2001:db8::1/", which no URL parser accepts.
190
+ */
161
191
  public override toString(): string {
162
- let hostname: string = this.hostname;
163
-
164
- if (this.port) {
165
- hostname += ":" + this.port.toString();
192
+ if (!this.port) {
193
+ return this.hostname;
166
194
  }
167
195
 
168
- return hostname;
196
+ return HostAddressUtil.formatHostAndPort({
197
+ host: this.hostname,
198
+ port: this.port.toString(),
199
+ });
169
200
  }
170
201
 
202
+ /*
203
+ * Reads a stored host, which may carry a ":port".
204
+ *
205
+ * IPv6 literals are handed to fromAuthority instead of being split, because
206
+ * splitting on the first colon is meaningless for an address that is made
207
+ * of colons: "2001:518:2800:9::2" used to come back as host "2001" with
208
+ * port 518 — and because 518 is a legal port number NOTHING threw. A
209
+ * monitor saved from the dashboard therefore silently pointed at a host
210
+ * called "2001", and the address the operator typed was simply gone.
211
+ *
212
+ * DNS names and IPv4 literals keep the original behaviour exactly: neither
213
+ * can contain two colons, so neither can reach the new branch.
214
+ */
171
215
  public static fromString(hostname: string | Hostname): Hostname {
172
216
  if (hostname instanceof Hostname) {
173
217
  hostname = hostname.toString();
174
218
  }
175
219
 
176
- if (hostname.includes(":")) {
177
- return new Hostname(
178
- hostname.split(":")[0] as string,
179
- hostname.split(":")[1],
180
- );
220
+ const value: string = (hostname || "").trim();
221
+
222
+ if (isIPv6Authority(value)) {
223
+ return Hostname.fromAuthority(value);
224
+ }
225
+
226
+ if (value.includes(":")) {
227
+ return new Hostname(value.split(":")[0] as string, value.split(":")[1]);
181
228
  }
182
- return new Hostname(hostname);
229
+ return new Hostname(value);
183
230
  }
184
231
 
185
232
  /*