@oneuptime/common 13.0.7 → 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 (759) 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/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +1 -1
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +1 -1
  7. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +1 -1
  8. package/Scripts/generate-service-worker.js +65 -4
  9. package/Server/API/AIBillingAPI.ts +1 -0
  10. package/Server/API/AIInsightAPI.ts +1 -4
  11. package/Server/API/AIInvestigationAPI.ts +1 -4
  12. package/Server/API/AIReadinessAPI.ts +4 -5
  13. package/Server/API/AlertAPI.ts +1 -0
  14. package/Server/API/AutoRemediationAPI.ts +1 -4
  15. package/Server/API/BaseAPI.ts +34 -0
  16. package/Server/API/BaseAnalyticsAPI.ts +80 -9
  17. package/Server/API/BillingAPI.ts +2 -0
  18. package/Server/API/BillingInvoiceAPI.ts +1 -0
  19. package/Server/API/BillingPaymentMethodAPI.ts +2 -0
  20. package/Server/API/CodeFixRunAPI.ts +1 -3
  21. package/Server/API/CommonAPI.ts +68 -4
  22. package/Server/API/DashboardAPI.ts +12 -12
  23. package/Server/API/GlobalConfigAPI.ts +243 -460
  24. package/Server/API/IncidentAPI.ts +2 -0
  25. package/Server/API/IncidentEpisodeAPI.ts +1 -0
  26. package/Server/API/NotificationAPI.ts +1 -0
  27. package/Server/API/OnCallCalendarAPI.ts +4 -5
  28. package/Server/API/OnCallDutyPolicyAPI.ts +7 -0
  29. package/Server/API/OnCallReadinessAPI.ts +4 -1
  30. package/Server/API/OnCallScheduleTimelineAPI.ts +731 -0
  31. package/Server/API/ProjectAPI.ts +1 -0
  32. package/Server/API/ProjectOIDC.ts +25 -16
  33. package/Server/API/ProjectSSO.ts +24 -16
  34. package/Server/API/ScheduledMaintenanceAPI.ts +1 -0
  35. package/Server/API/StatusPageAPI.ts +261 -74
  36. package/Server/API/TeamMemberAPI.ts +13 -1
  37. package/Server/API/UserCallAPI.ts +2 -0
  38. package/Server/API/UserEmailAPI.ts +2 -0
  39. package/Server/API/UserIncomingCallNumberAPI.ts +2 -0
  40. package/Server/API/UserMicrosoftTeamsAPI.ts +1 -0
  41. package/Server/API/UserSlackAPI.ts +1 -0
  42. package/Server/API/UserSmsAPI.ts +2 -0
  43. package/Server/API/UserTotpAuthAPI.ts +1 -0
  44. package/Server/API/UserWebAuthnAPI.ts +2 -0
  45. package/Server/API/UserWebhookAPI.ts +1 -0
  46. package/Server/API/UserWhatsAppAPI.ts +2 -0
  47. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  48. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  49. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  50. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  51. package/Server/EnvironmentConfig.ts +147 -2
  52. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  54. package/Server/Middleware/MasterAdminAuthorization.ts +13 -2
  55. package/Server/Middleware/UserAuthorization.ts +117 -21
  56. package/Server/Services/ApiKeyPermissionService.ts +101 -4
  57. package/Server/Services/AuditLogService.ts +84 -1079
  58. package/Server/Services/DatabaseService.ts +245 -0
  59. package/Server/Services/DetectionRuleService.ts +5 -4
  60. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  61. package/Server/Services/GlobalConfigService.ts +80 -1
  62. package/Server/Services/MonitorProbeService.ts +116 -20
  63. package/Server/Services/MonitorService.ts +56 -4
  64. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  65. package/Server/Services/MonitorTemplateService.ts +26 -0
  66. package/Server/Services/OnCallDutyPolicyChildService.ts +261 -0
  67. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -2
  68. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -2
  69. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -2
  70. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -2
  71. package/Server/Services/PayAsYouGoBillingService.ts +23 -28
  72. package/Server/Services/ProjectService.ts +302 -0
  73. package/Server/Services/RumSessionPinService.ts +293 -22
  74. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  75. package/Server/Services/StatusPageService.ts +78 -1
  76. package/Server/Services/TeamMemberService.ts +11 -0
  77. package/Server/Services/TeamPermissionService.ts +120 -26
  78. package/Server/Services/TeamService.ts +10 -0
  79. package/Server/Services/ThreatIntelFeedService.ts +6 -3
  80. package/Server/Services/UserService.ts +10 -17
  81. package/Server/Types/Database/Permissions/BasePermission.ts +9 -7
  82. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  83. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  84. package/Server/Types/Database/Permissions/Index.ts +75 -11
  85. package/Server/Types/Database/Permissions/TablePermission.ts +34 -31
  86. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  87. package/Server/Types/Markdown.ts +41 -0
  88. package/Server/Utils/AI/AIChatPrivacyFilter.ts +8 -0
  89. package/Server/Utils/AI/AIRunPrivacyFilter.ts +8 -0
  90. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  91. package/Server/Utils/EditionEnforcement.ts +233 -0
  92. package/Server/Utils/FrontendEnvironment.ts +41 -2
  93. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  94. package/Server/Utils/MasterAdminPageAccess.ts +61 -0
  95. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  96. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  97. package/Server/Utils/Monitor/MonitorAlert.ts +3 -1
  98. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  99. package/Server/Utils/Monitor/MonitorIncident.ts +3 -1
  100. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  101. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1 -1
  102. package/Server/Utils/Realtime.ts +163 -45
  103. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +11 -28
  104. package/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.ts +170 -0
  105. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +1 -1
  106. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +6 -2
  107. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +10 -9
  108. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  109. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  110. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  111. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  112. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  113. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  114. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  115. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  116. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +1059 -0
  117. package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +376 -0
  118. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +757 -0
  119. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +307 -0
  120. package/Tests/App/Dashboard/ArgumentsFormGroupByUnlistedAttributes.test.tsx +131 -0
  121. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  122. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +1 -1
  123. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +9 -1
  124. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +24 -0
  125. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +162 -8
  126. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  127. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  128. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +105 -6
  129. package/Tests/App/Dashboard/EntityFilterDropdownUnlistedSelection.test.tsx +434 -0
  130. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +276 -1
  131. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  132. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +9 -1
  133. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  134. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +58 -42
  135. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +42 -5
  136. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +34 -23
  137. package/Tests/App/Dashboard/ScheduleTimeline.test.tsx +1580 -0
  138. package/Tests/App/Dashboard/ScheduleTimelineAPI.test.ts +280 -0
  139. package/Tests/App/Dashboard/ScheduleTimelineModel.test.ts +673 -0
  140. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +133 -70
  141. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  142. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  143. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +86 -3
  144. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -58
  145. package/Tests/App/Dashboard/SecurityEventsGuideContent.test.ts +1313 -0
  146. package/Tests/App/Dashboard/SecurityEventsHowItWorksCard.test.tsx +536 -0
  147. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  148. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +222 -0
  149. package/Tests/App/Dashboard/SecurityEventsThreatIntelPage.test.tsx +199 -0
  150. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  151. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +171 -36
  152. package/Tests/App/Dashboard/SloViewSideMenu.test.tsx +215 -0
  153. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +40 -3
  154. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  155. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +12 -4
  156. package/Tests/App/PublicDashboard/PublicDashboardWidgetListAuth.test.tsx +623 -0
  157. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  158. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  159. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  160. package/Tests/Models/OnCallCalendarFeedModels.test.ts +23 -1
  161. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  162. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +138 -5
  163. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +187 -0
  164. package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +51 -3
  165. package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +55 -2
  166. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  167. package/Tests/Server/API/BaseAnalyticsAPIAggregateCache.test.ts +729 -0
  168. package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +93 -7
  169. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +36 -3
  170. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +1003 -24
  171. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +6 -3
  172. package/Tests/Server/API/ExpiredSessionAIRoutes.test.ts +708 -0
  173. package/Tests/Server/API/ExpiredSessionCustomRoutes.test.ts +928 -0
  174. package/Tests/Server/API/ExpiredSessionMonitorTemplate.test.ts +319 -0
  175. package/Tests/Server/API/ExpiredSessionOnCallDutyPolicy.test.ts +336 -0
  176. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +820 -0
  177. package/Tests/Server/API/GlobalConfigLicense.test.ts +766 -381
  178. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +115 -12
  179. package/Tests/Server/API/OnCallCalendarAPI.test.ts +375 -8
  180. package/Tests/Server/API/OnCallReadinessAPI.test.ts +196 -912
  181. package/Tests/Server/API/OnCallScheduleTimelineAPI.test.ts +1513 -0
  182. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +119 -7
  183. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +111 -4
  184. package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +615 -0
  185. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +89 -2
  186. package/Tests/Server/API/SessionReplayAPI.test.ts +121 -0
  187. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  188. package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +4 -2
  189. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +441 -0
  190. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +664 -0
  191. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  192. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +7 -0
  193. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +6 -0
  194. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +65 -4
  195. package/Tests/Server/API/UserProjectsAPI.test.ts +63 -3
  196. package/Tests/Server/API/UserTotpAuthAPI.test.ts +48 -2
  197. package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +11 -2
  198. package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +46 -3
  199. package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +18 -2
  200. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +24 -7
  201. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  202. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  203. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  204. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  205. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  206. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  207. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  208. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  209. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  210. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  211. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  212. package/Tests/Server/Middleware/ExpiredSessionUserMiddleware.test.ts +563 -0
  213. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +299 -19
  214. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +453 -0
  215. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  216. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  217. package/Tests/Server/Services/AIChatPrivacyPin.test.ts +24 -0
  218. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +2 -0
  219. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +3 -3
  220. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  221. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  222. package/Tests/Server/Services/DatabaseServiceCreateSuppliedId.test.ts +147 -0
  223. package/Tests/Server/Services/DatabaseServiceLoginBeforeHooks.test.ts +1078 -0
  224. package/Tests/Server/Services/DatabaseServiceTenantRelationGuard.test.ts +259 -0
  225. package/Tests/Server/Services/DatabaseServiceWriteOperatorValues.test.ts +147 -0
  226. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  227. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  228. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  229. package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +115 -1
  230. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  231. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  232. package/Tests/Server/Services/OnCallChildCreateAuthorizationPostgres.test.ts +711 -0
  233. package/Tests/Server/Services/OnCallDutyPolicyChildService.test.ts +733 -0
  234. package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +190 -140
  235. package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +4 -9
  236. package/Tests/Server/Services/PermissionBlockDeletion.test.ts +780 -0
  237. package/Tests/Server/Services/PermissionBlockDeletionPostgres.test.ts +628 -0
  238. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  239. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  240. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +77 -1
  241. package/Tests/Server/Services/RumSessionPinCreatePermission.test.ts +715 -0
  242. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  243. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  244. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  245. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  246. package/Tests/Server/Services/TeamMemberMultiTenantReadPostgres.test.ts +394 -0
  247. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +6 -0
  248. package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +131 -0
  249. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  250. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +13 -2
  251. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  252. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  253. package/Tests/Server/Types/Database/Permissions/ModelPermissionAnonymousRefusal.test.ts +518 -0
  254. package/Tests/Server/Types/Database/Permissions/MultiTenantPermissions.test.ts +350 -0
  255. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +282 -25
  256. package/Tests/Server/Types/Database/Permissions/TableLevelBlockPermission.test.ts +459 -0
  257. package/Tests/Server/Types/Markdown.test.ts +181 -0
  258. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +101 -9
  259. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +88 -4
  260. package/Tests/Server/Utils/AI/Toolbox/ExceptionTools.test.ts +459 -0
  261. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +794 -0
  262. package/Tests/Server/Utils/AI/Toolbox/RecentChangesTools.test.ts +574 -0
  263. package/Tests/Server/Utils/AI/Toolbox/SecurityEventTools.test.ts +596 -0
  264. package/Tests/Server/Utils/CaptchaRequestAndLogging.test.ts +395 -0
  265. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  266. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  267. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  268. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  269. package/Tests/Server/Utils/MasterAdminPageAccess.test.ts +251 -0
  270. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  271. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  272. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +616 -0
  273. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  274. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  275. package/Tests/Server/Utils/Realtime.test.ts +502 -0
  276. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorSeverity.test.ts +216 -0
  277. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +142 -0
  278. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.test.ts +140 -0
  279. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepairClickhouseIntegration.test.ts +542 -0
  280. package/Tests/Server/Utils/Telemetry/ChangeEventRow.test.ts +743 -0
  281. package/Tests/Server/Utils/Telemetry/RuntimeMetrics.test.ts +465 -0
  282. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Alert.test.ts +202 -0
  283. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.test.ts +194 -0
  284. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/ScheduledMaintenance.test.ts +231 -0
  285. package/Tests/Server/Utils/Workspace/Slack/Messages/Alert.test.ts +194 -0
  286. package/Tests/Server/Utils/Workspace/Slack/Messages/AlertEpisode.test.ts +194 -0
  287. package/Tests/Server/Utils/Workspace/Slack/Messages/Incident.test.ts +194 -0
  288. package/Tests/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.test.ts +194 -0
  289. package/Tests/Server/Utils/Workspace/Slack/Messages/ScheduledMaintenance.test.ts +247 -0
  290. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  291. package/Tests/Types/AI/AIInsightEvidence.test.ts +206 -0
  292. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  293. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  294. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtilAnonymous.test.ts +144 -0
  295. package/Tests/Types/Call/CallProvider.test.ts +528 -0
  296. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  297. package/Tests/Types/Kubernetes/KubernetesCostIngest.test.ts +236 -0
  298. package/Tests/Types/OnCallDutyPolicy/ScheduleTimeline.test.ts +736 -0
  299. package/Tests/Types/OnCallDutyPolicy/ScheduleTimelineLayout.test.ts +996 -0
  300. package/Tests/Types/SecurityEvent/SigmaRule.test.ts +142 -0
  301. package/Tests/Types/Workflow/Components/JSON.test.ts +182 -0
  302. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +116 -1
  303. package/Tests/UI/Components/Dropdown/DropdownAsyncOptions.test.tsx +81 -0
  304. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  305. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  306. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +165 -171
  307. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  308. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  309. package/Tests/UI/Components/EventAttachmentList.test.tsx +174 -0
  310. package/Tests/UI/Components/Forms/BasicFormBurnRateTemplateDefaults.test.tsx +395 -0
  311. package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +286 -0
  312. package/Tests/UI/Components/Forms/FormSummary.test.tsx +128 -0
  313. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  314. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  315. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  316. package/Tests/UI/Components/ModelTable/BaseModelTableQueryChange.test.tsx +365 -0
  317. package/Tests/UI/Components/OpenAuthenticatedUrl.test.ts +358 -0
  318. package/Tests/UI/Components/SessionAwareImage.test.tsx +496 -0
  319. package/Tests/UI/Components/TelemetryHistogramZeroBuckets.test.tsx +190 -0
  320. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  321. package/Tests/UI/Components/TooltipInteractive.test.tsx +97 -0
  322. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  323. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +1 -1
  324. package/Tests/UI/Monitor/EvaluationLogListTimestamps.test.tsx +367 -0
  325. package/Tests/UI/Utils/API/BaseAPIRefreshSession.test.ts +1087 -0
  326. package/Tests/UI/Utils/API/DashboardHost.ts +12 -0
  327. package/Tests/UI/Utils/API/FakeAxiosServer.ts +183 -0
  328. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  329. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  330. package/Tests/UI/Utils/Cookie.test.ts +440 -0
  331. package/Tests/UI/Utils/ModelAPIRequestClient.test.ts +278 -0
  332. package/Tests/UI/Utils/Navigation.test.ts +33 -0
  333. package/Tests/UI/Utils/PricingPlan.test.ts +295 -0
  334. package/Tests/UI/Utils/Realtime.test.ts +759 -0
  335. package/Tests/UI/Utils/Telemetry/Telemetry.test.ts +500 -0
  336. package/Tests/Utils/APIInstanceDispatch.test.ts +311 -0
  337. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  338. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  339. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  340. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizerSeverity.test.ts +565 -0
  341. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  342. package/Types/API/Hostname.ts +63 -16
  343. package/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +40 -0
  344. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  345. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  346. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  347. package/Types/Database/CanonicalModelInstance.ts +102 -0
  348. package/Types/Database/TableColumn.ts +20 -6
  349. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  350. package/Types/IP/IPv6.ts +6 -1
  351. package/Types/Monitor/MonitorEvaluationSummary.ts +3 -0
  352. package/Types/Monitor/MonitorStep.ts +6 -1
  353. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  354. package/Types/OnCallDutyPolicy/ScheduleTimeline.ts +507 -0
  355. package/Types/OnCallDutyPolicy/ScheduleTimelineLayout.ts +589 -0
  356. package/Types/Realtime/EventName.ts +11 -0
  357. package/Types/SecurityEvent/DetectionFindingConstants.ts +14 -0
  358. package/Types/SecurityEvent/OcsfSeverity.ts +31 -26
  359. package/Types/SecurityEvent/SigmaRule.ts +51 -0
  360. package/Types/SecurityEvent/ThreatIntelConstants.ts +18 -0
  361. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  362. package/UI/Components/AttachmentList/EventAttachmentList.tsx +30 -2
  363. package/UI/Components/Dashboard/DashboardVariableControl.tsx +12 -2
  364. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  365. package/UI/Components/EditionLabel/EditionLabel.tsx +808 -1195
  366. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  367. package/UI/Components/EventItem/EventItem.tsx +12 -1
  368. package/UI/Components/Forms/BasicForm.tsx +151 -126
  369. package/UI/Components/Forms/CollapsibleFormSection.tsx +63 -0
  370. package/UI/Components/Forms/Fields/FormField.tsx +10 -4
  371. package/UI/Components/Forms/FormSummary.tsx +7 -0
  372. package/UI/Components/Forms/Types/Field.ts +10 -0
  373. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  374. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  375. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -1
  376. package/UI/Components/Markdown.tsx/SessionAwareImage.tsx +122 -0
  377. package/UI/Components/ModelList/ModelList.tsx +11 -1
  378. package/UI/Components/ModelTable/BaseModelTable.tsx +34 -5
  379. package/UI/Components/ModelTable/TableView.tsx +1 -1
  380. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  381. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  382. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +8 -1
  383. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  384. package/UI/Utils/API/API.ts +173 -12
  385. package/UI/Utils/API/RequestOptions.ts +11 -0
  386. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +31 -7
  387. package/UI/Utils/ModelAPI/ModelAPI.ts +30 -9
  388. package/UI/Utils/Navigation.ts +9 -0
  389. package/UI/Utils/OpenAuthenticatedUrl.ts +154 -0
  390. package/UI/Utils/Realtime.ts +176 -30
  391. package/UI/esbuild-enterprise.js +263 -0
  392. package/Utils/API.ts +14 -5
  393. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  394. package/Utils/HostAddressUtil.ts +94 -0
  395. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  396. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  397. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +157 -7
  398. package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +9 -17
  399. package/Utils/Slo/SloBurnRateTemplate.ts +2 -2
  400. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  401. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  402. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  403. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  404. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  405. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  406. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  407. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  408. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  409. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +1 -1
  410. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -1
  411. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +1 -1
  412. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -1
  413. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +1 -1
  414. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -1
  415. package/build/dist/Server/API/AIBillingAPI.js +1 -1
  416. package/build/dist/Server/API/AIBillingAPI.js.map +1 -1
  417. package/build/dist/Server/API/AIInsightAPI.js +1 -4
  418. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  419. package/build/dist/Server/API/AIInvestigationAPI.js +1 -4
  420. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  421. package/build/dist/Server/API/AIReadinessAPI.js +1 -3
  422. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -1
  423. package/build/dist/Server/API/AlertAPI.js +1 -1
  424. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  425. package/build/dist/Server/API/AutoRemediationAPI.js +1 -4
  426. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  427. package/build/dist/Server/API/BaseAPI.js +33 -0
  428. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  429. package/build/dist/Server/API/BaseAnalyticsAPI.js +55 -10
  430. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  431. package/build/dist/Server/API/BillingAPI.js +2 -2
  432. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  433. package/build/dist/Server/API/BillingInvoiceAPI.js +1 -1
  434. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  435. package/build/dist/Server/API/BillingPaymentMethodAPI.js +2 -2
  436. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  437. package/build/dist/Server/API/CodeFixRunAPI.js +1 -3
  438. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  439. package/build/dist/Server/API/CommonAPI.js +50 -5
  440. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  441. package/build/dist/Server/API/DashboardAPI.js +12 -12
  442. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  443. package/build/dist/Server/API/GlobalConfigAPI.js +158 -342
  444. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  445. package/build/dist/Server/API/IncidentAPI.js +2 -2
  446. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  447. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  448. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  449. package/build/dist/Server/API/NotificationAPI.js +1 -1
  450. package/build/dist/Server/API/NotificationAPI.js.map +1 -1
  451. package/build/dist/Server/API/OnCallCalendarAPI.js +1 -4
  452. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -1
  453. package/build/dist/Server/API/OnCallDutyPolicyAPI.js +6 -0
  454. package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -1
  455. package/build/dist/Server/API/OnCallReadinessAPI.js +4 -1
  456. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -1
  457. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +494 -0
  458. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -0
  459. package/build/dist/Server/API/ProjectAPI.js +1 -1
  460. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  461. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  462. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  463. package/build/dist/Server/API/ProjectSSO.js +24 -16
  464. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  465. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  466. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  467. package/build/dist/Server/API/StatusPageAPI.js +218 -76
  468. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  469. package/build/dist/Server/API/TeamMemberAPI.js +11 -4
  470. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  471. package/build/dist/Server/API/UserCallAPI.js +2 -2
  472. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  473. package/build/dist/Server/API/UserEmailAPI.js +2 -2
  474. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  475. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +2 -2
  476. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  477. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +1 -1
  478. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -1
  479. package/build/dist/Server/API/UserSlackAPI.js +1 -1
  480. package/build/dist/Server/API/UserSlackAPI.js.map +1 -1
  481. package/build/dist/Server/API/UserSmsAPI.js +2 -2
  482. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  483. package/build/dist/Server/API/UserTotpAuthAPI.js +1 -1
  484. package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
  485. package/build/dist/Server/API/UserWebAuthnAPI.js +2 -2
  486. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  487. package/build/dist/Server/API/UserWebhookAPI.js +1 -1
  488. package/build/dist/Server/API/UserWebhookAPI.js.map +1 -1
  489. package/build/dist/Server/API/UserWhatsAppAPI.js +2 -2
  490. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  491. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  492. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  493. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  494. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  495. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  496. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  497. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  498. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  499. package/build/dist/Server/EnvironmentConfig.js +84 -2
  500. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  501. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  502. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  503. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  504. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  505. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -2
  506. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  507. package/build/dist/Server/Middleware/UserAuthorization.js +94 -15
  508. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  509. package/build/dist/Server/Services/ApiKeyPermissionService.js +80 -4
  510. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  511. package/build/dist/Server/Services/AuditLogService.js +71 -740
  512. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  513. package/build/dist/Server/Services/DatabaseService.js +171 -0
  514. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  515. package/build/dist/Server/Services/DetectionRuleService.js +4 -4
  516. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  517. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  518. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  519. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  520. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  521. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  522. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  523. package/build/dist/Server/Services/MonitorService.js +45 -4
  524. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  525. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  526. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  527. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  528. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  529. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js +170 -0
  530. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js.map +1 -0
  531. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -2
  532. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  533. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +2 -2
  534. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  535. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -2
  536. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  537. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -2
  538. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  539. package/build/dist/Server/Services/PayAsYouGoBillingService.js +20 -27
  540. package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -1
  541. package/build/dist/Server/Services/ProjectService.js +231 -0
  542. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  543. package/build/dist/Server/Services/RumSessionPinService.js +198 -16
  544. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  545. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  546. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  547. package/build/dist/Server/Services/StatusPageService.js +71 -0
  548. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  549. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  550. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  551. package/build/dist/Server/Services/TeamPermissionService.js +89 -27
  552. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  553. package/build/dist/Server/Services/TeamService.js +9 -0
  554. package/build/dist/Server/Services/TeamService.js.map +1 -1
  555. package/build/dist/Server/Services/ThreatIntelFeedService.js +5 -4
  556. package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -1
  557. package/build/dist/Server/Services/UserService.js +10 -16
  558. package/build/dist/Server/Services/UserService.js.map +1 -1
  559. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +9 -6
  560. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  561. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  562. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  563. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  564. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  565. package/build/dist/Server/Types/Database/Permissions/Index.js +60 -6
  566. package/build/dist/Server/Types/Database/Permissions/Index.js.map +1 -1
  567. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +24 -16
  568. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  569. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  570. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  571. package/build/dist/Server/Types/Markdown.js +31 -0
  572. package/build/dist/Server/Types/Markdown.js.map +1 -1
  573. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +7 -0
  574. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -1
  575. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +7 -0
  576. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -1
  577. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  578. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  579. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  580. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  581. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  582. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  583. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  584. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  585. package/build/dist/Server/Utils/MasterAdminPageAccess.js +39 -0
  586. package/build/dist/Server/Utils/MasterAdminPageAccess.js.map +1 -0
  587. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  588. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  589. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  590. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  591. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +3 -1
  592. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  593. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  594. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  595. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +3 -1
  596. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  597. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  598. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  599. package/build/dist/Server/Utils/Realtime.js +126 -37
  600. package/build/dist/Server/Utils/Realtime.js.map +1 -1
  601. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +7 -25
  602. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  603. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js +127 -0
  604. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js.map +1 -0
  605. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +1 -1
  606. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  607. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +3 -2
  608. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -1
  609. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +4 -8
  610. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -1
  611. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  612. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  613. package/build/dist/Types/API/Hostname.js +59 -11
  614. package/build/dist/Types/API/Hostname.js.map +1 -1
  615. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js +31 -1
  616. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js.map +1 -1
  617. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  618. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  619. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  620. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  621. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  622. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  623. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  624. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  625. package/build/dist/Types/Database/TableColumn.js +19 -6
  626. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  627. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  628. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  629. package/build/dist/Types/IP/IPv6.js +6 -1
  630. package/build/dist/Types/IP/IPv6.js.map +1 -1
  631. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  632. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  633. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  634. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js +321 -0
  635. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js.map +1 -0
  636. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js +372 -0
  637. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js.map +1 -0
  638. package/build/dist/Types/Realtime/EventName.js +10 -0
  639. package/build/dist/Types/Realtime/EventName.js.map +1 -1
  640. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +11 -0
  641. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  642. package/build/dist/Types/SecurityEvent/OcsfSeverity.js +30 -25
  643. package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -1
  644. package/build/dist/Types/SecurityEvent/SigmaRule.js +47 -0
  645. package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -1
  646. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +14 -0
  647. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -1
  648. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  649. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  650. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +9 -4
  651. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -1
  652. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +10 -2
  653. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -1
  654. package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -1
  655. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  656. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +425 -585
  657. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  658. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  659. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  660. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  661. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  662. package/build/dist/UI/Components/Forms/BasicForm.js +70 -64
  663. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  664. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +27 -0
  665. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -0
  666. package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -7
  667. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  668. package/build/dist/UI/Components/Forms/FormSummary.js +7 -0
  669. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  670. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  671. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  672. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  673. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  674. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -1
  675. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  676. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js +84 -0
  677. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js.map +1 -0
  678. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  679. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  680. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +14 -1
  681. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  682. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  683. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  684. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  685. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  686. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  687. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  688. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -1
  689. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  690. package/build/dist/UI/Components/Tooltip/Tooltip.js +2 -1
  691. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  692. package/build/dist/UI/Utils/API/API.js +143 -19
  693. package/build/dist/UI/Utils/API/API.js.map +1 -1
  694. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +21 -7
  695. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  696. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +26 -9
  697. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  698. package/build/dist/UI/Utils/Navigation.js +8 -0
  699. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  700. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js +92 -0
  701. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js.map +1 -0
  702. package/build/dist/UI/Utils/Realtime.js +120 -15
  703. package/build/dist/UI/Utils/Realtime.js.map +1 -1
  704. package/build/dist/Utils/API.js +13 -5
  705. package/build/dist/Utils/API.js.map +1 -1
  706. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  707. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  708. package/build/dist/Utils/HostAddressUtil.js +80 -0
  709. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  710. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  711. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  712. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  713. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  714. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +123 -6
  715. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
  716. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +4 -18
  717. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -1
  718. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +2 -2
  719. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  720. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  721. package/jest.config.json +3 -1
  722. package/package.json +1 -1
  723. package/test-setup.sh +103 -34
  724. package/tsconfig.json +13 -0
  725. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  726. package/Server/API/TeamComplianceAPI.ts +0 -158
  727. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  728. package/Server/Services/TeamComplianceService.ts +0 -733
  729. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  730. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  731. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  732. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  733. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  734. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  735. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  736. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  737. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  738. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  739. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  740. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  741. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  742. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  743. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  744. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  745. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  746. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  747. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  748. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  749. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  750. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  751. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  752. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  753. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  754. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  755. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  756. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  757. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  758. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  759. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -1,15 +1,19 @@
1
1
  import Modal, { ModalWidth } from "../Modal/Modal";
2
2
  import Icon from "../Icon/Icon";
3
3
  import IconProp from "../../../Types/Icon/IconProp";
4
- import Input from "../Input/Input";
5
4
  import Button, { ButtonStyleType } from "../Button/Button";
5
+ import {
6
+ LicenseManagerComponent,
7
+ LicenseManagerDialogParts,
8
+ LicenseSnapshot,
9
+ SeatTone,
10
+ } from "./LicenseManager";
6
11
  import React, {
7
12
  FunctionComponent,
8
13
  ReactElement,
9
14
  useCallback,
10
15
  useEffect,
11
16
  useMemo,
12
- useRef,
13
17
  useState,
14
18
  } from "react";
15
19
  import GlobalConfig from "../../../Models/DatabaseModels/GlobalConfig";
@@ -25,14 +29,31 @@ import {
25
29
  APP_API_URL,
26
30
  BILLING_ENABLED,
27
31
  IS_ENTERPRISE_EDITION,
32
+ env,
28
33
  } from "../../Config";
29
- import Alert, { AlertType } from "../Alerts/Alert";
30
- import EnterpriseLicenseInstanceSummary from "../../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
31
34
  import VersionUtil from "../../../Utils/VersionUtil";
35
+ import {
36
+ ENTERPRISE_LICENSE_GRACE_PERIOD_IN_DAYS,
37
+ ENTERPRISE_LICENSE_TRIAL_PERIOD_IN_DAYS,
38
+ } from "../../../Types/EnterpriseLicense/EnterpriseLicensePeriods";
32
39
  import UserUtil from "../../Utils/User";
33
40
 
41
+ /*
42
+ * The edition pill and its dialog. This component only READS the license
43
+ * (GET /global-config/license): the pill, the status notices and the version
44
+ * card. Activating, refreshing and replacing a license, and the seat usage and
45
+ * instance tables, are the Enterprise Edition's license manager (see
46
+ * ./LicenseManager.ts), which the Dashboard and the Admin Dashboard pass in.
47
+ */
34
48
  export interface ComponentProps {
35
49
  className?: string | undefined;
50
+ /*
51
+ * The Enterprise plugin's LicenseManager, read from the plugin door inside
52
+ * the caller's render (never at module load). Undefined in the Community
53
+ * Edition and on the login page: the dialog then shows the license status
54
+ * only.
55
+ */
56
+ licenseManager?: LicenseManagerComponent | undefined;
36
57
  }
37
58
 
38
59
  const ENTERPRISE_URL: string = "https://oneuptime.com/enterprise/demo";
@@ -47,105 +68,83 @@ const ENTERPRISE_URL: string = "https://oneuptime.com/enterprise/demo";
47
68
  */
48
69
  const UPGRADE_GUIDE_URL: string =
49
70
  "https://oneuptime.com/docs/installation/upgrading";
50
- const SALES_EMAIL: string = "sales@oneuptime.com";
51
71
  const SALES_MAILTO_URL: string = "mailto:sales@oneuptime.com";
52
72
 
73
+ const DAY_IN_MS: number = 24 * 60 * 60 * 1000;
74
+
53
75
  /*
54
- * Seat usage at or above this percent asks the admin to talk to OneUptime about
55
- * expanding. Matches CRITICAL_CAPACITY_PERCENT in
56
- * App/FeatureSet/Workers/Jobs/InstanceHealth/EvaluateClickhouseCapacity.ts.
76
+ * What stops when the license lapses, in one sentence set, so every notice in
77
+ * the dialog says it the same way. It has to be said plainly and before it
78
+ * happens: after the trial or the grace period, single sign-on (SAML and
79
+ * OIDC) and SCIM provisioning stop, "Require SSO" is no longer enforced (so
80
+ * nobody is locked out: users sign in with their password), and audit logging
81
+ * stops recording - the Community Edition's behaviour - until a license is
82
+ * activated, when everything resumes without a restart. Core monitoring is
83
+ * never touched.
57
84
  */
58
- const SEAT_WARNING_PERCENT: number = 90;
59
-
60
- type SeatTone = "healthy" | "approaching" | "breached";
85
+ export const LICENSE_LAPSE_CONSEQUENCES: string =
86
+ 'single sign-on (SAML and OIDC) stops and "Require SSO" is no longer enforced, so users sign in with their password; SCIM provisioning stops; audit logging stops recording; enterprise configuration becomes read-only; and the enterprise admin dashboards are locked. Everything resumes as soon as a license is activated, and core monitoring is never affected.';
61
87
 
62
- type PillTone = "normal" | "warning" | "alerted";
63
-
64
- type ParseLicenseInstancesFunction = (
65
- value: unknown,
66
- ) => Array<EnterpriseLicenseInstanceSummary>;
88
+ /*
89
+ * The two periods have different names and different lengths: an unlicensed
90
+ * installation's first ENTERPRISE_LICENSE_TRIAL_PERIOD_IN_DAYS days are its
91
+ * trial, and only a license that expired has a grace period
92
+ * (ENTERPRISE_LICENSE_GRACE_PERIOD_IN_DAYS days after its expiry). The
93
+ * numbers come from the constants the license classifier uses.
94
+ */
95
+ export const TRIAL_ENFORCEMENT_SUMMARY: string = `Without a valid license (after the ${ENTERPRISE_LICENSE_TRIAL_PERIOD_IN_DAYS}-day trial), ${LICENSE_LAPSE_CONSEQUENCES}`;
67
96
 
68
- const parseLicenseInstances: ParseLicenseInstancesFunction = (
69
- value: unknown,
70
- ): Array<EnterpriseLicenseInstanceSummary> => {
71
- if (!Array.isArray(value)) {
72
- return [];
73
- }
97
+ export const GRACE_ENFORCEMENT_SUMMARY: string = `Without a valid license (after the ${ENTERPRISE_LICENSE_GRACE_PERIOD_IN_DAYS}-day grace period), ${LICENSE_LAPSE_CONSEQUENCES}`;
74
98
 
75
- const instances: Array<EnterpriseLicenseInstanceSummary> = [];
99
+ /*
100
+ * What has stopped once the license lapsed (expired past its grace period,
101
+ * missing after the trial, or invalid), after the reason the notice gives.
102
+ */
103
+ export const LICENSE_LAPSED_STATE: string =
104
+ 'Single sign-on (SAML and OIDC) and SCIM provisioning are off: "Require SSO" is not enforced, so users sign in with their password, and your identity provider\'s SCIM requests are refused. Audit logging is not recording. Enterprise configuration is read-only and the enterprise admin dashboards are locked. Everything resumes, without a restart, as soon as a valid license is added, and core monitoring is never affected.';
76
105
 
77
- for (const item of value) {
78
- if (!item || typeof item !== "object") {
79
- continue;
80
- }
106
+ type LicenseStatus = "valid" | "grace" | "expired" | "missing" | "invalid";
81
107
 
82
- const instance: JSONObject = item as JSONObject;
108
+ type ParseLicenseStatusFunction = (value: unknown) => LicenseStatus | null;
83
109
 
84
- instances.push({
85
- instanceId:
86
- typeof instance["instanceId"] === "string"
87
- ? instance["instanceId"]
88
- : "",
89
- host:
90
- typeof instance["host"] === "string" && instance["host"]
91
- ? instance["host"]
92
- : null,
93
- userCount:
94
- typeof instance["userCount"] === "number"
95
- ? instance["userCount"]
96
- : null,
97
- isCountedTowardsUsage:
98
- typeof instance["isCountedTowardsUsage"] === "boolean"
99
- ? instance["isCountedTowardsUsage"]
100
- : undefined,
101
- lastReportedAt:
102
- typeof instance["lastReportedAt"] === "string"
103
- ? instance["lastReportedAt"]
104
- : null,
105
- version:
106
- typeof instance["version"] === "string" && instance["version"]
107
- ? instance["version"]
108
- : null,
109
- });
110
+ const parseLicenseStatus: ParseLicenseStatusFunction = (
111
+ value: unknown,
112
+ ): LicenseStatus | null => {
113
+ if (
114
+ value === "valid" ||
115
+ value === "grace" ||
116
+ value === "expired" ||
117
+ value === "missing" ||
118
+ value === "invalid"
119
+ ) {
120
+ return value;
110
121
  }
111
122
 
112
- return instances;
123
+ return null;
113
124
  };
114
125
 
115
- type FormatInstanceReportedAtFunction = (
116
- lastReportedAt: string | null,
117
- ) => string;
118
-
119
- const formatInstanceReportedAt: FormatInstanceReportedAtFunction = (
120
- lastReportedAt: string | null,
121
- ): string => {
122
- if (!lastReportedAt) {
123
- return "No usage reported yet.";
124
- }
125
-
126
- const reportedAt: Date = OneUptimeDate.fromString(lastReportedAt);
127
-
128
- if (Number.isNaN(reportedAt.getTime())) {
129
- return "No usage reported yet.";
130
- }
126
+ /*
127
+ * Seat usage at or above this percent asks the admin to talk to OneUptime about
128
+ * expanding. Matches CRITICAL_CAPACITY_PERCENT in
129
+ * App/FeatureSet/Workers/Jobs/InstanceHealth/EvaluateClickhouseCapacity.ts.
130
+ */
131
+ const SEAT_WARNING_PERCENT: number = 90;
131
132
 
132
- return `Last reported ${reportedAt.toLocaleString()}.`;
133
- };
133
+ type PillTone = "normal" | "warning" | "alerted";
134
134
 
135
135
  const EditionLabel: FunctionComponent<ComponentProps> = (
136
136
  props: ComponentProps,
137
137
  ): ReactElement => {
138
138
  const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
139
139
  const [globalConfig, setGlobalConfig] = useState<GlobalConfig | null>(null);
140
+ /*
141
+ * The last successful license GET, as the server sent it. Handed to the
142
+ * license manager, which reads its own fields (the instances, the
143
+ * activation mode) from it.
144
+ */
145
+ const [licensePayload, setLicensePayload] = useState<JSONObject | null>(null);
140
146
  const [isConfigLoading, setIsConfigLoading] = useState<boolean>(false);
141
147
  const [configError, setConfigError] = useState<string>("");
142
- const [licenseKeyInput, setLicenseKeyInput] = useState<string>("");
143
- const [validationError, setValidationError] = useState<string>("");
144
- const [successMessage, setSuccessMessage] = useState<string>("");
145
- const [isValidating, setIsValidating] = useState<boolean>(false);
146
- const [isRefreshingLicense, setIsRefreshingLicense] =
147
- useState<boolean>(false);
148
- const [isChangingLicense, setIsChangingLicense] = useState<boolean>(false);
149
148
  /*
150
149
  * The seat limit as this installation actually enforces it, rather than as
151
150
  * oneuptime.com last reported it. The server computes it from the live User
@@ -161,10 +160,6 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
161
160
  null,
162
161
  );
163
162
  const [canAddMoreUsers, setCanAddMoreUsers] = useState<boolean>(true);
164
- const [licenseInstances, setLicenseInstances] = useState<
165
- Array<EnterpriseLicenseInstanceSummary>
166
- >([]);
167
- const [thisInstanceId, setThisInstanceId] = useState<string>("");
168
163
  /*
169
164
  * Version state comes from the server rather than APP_VERSION in the
170
165
  * browser bundle: the frontend env var is empty in every build that was
@@ -195,16 +190,65 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
195
190
  const [serverLicenseValid, setServerLicenseValid] = useState<boolean | null>(
196
191
  null,
197
192
  );
198
- const licenseInputEditedRef: React.MutableRefObject<boolean> =
199
- useRef<boolean>(false);
193
+ /*
194
+ * Where the license stands, as the Enterprise license client classified it:
195
+ * valid, grace (an expired license's grace period, or an unlicensed
196
+ * installation's trial counted from its first run), expired, missing or
197
+ * invalid - and whether it could be verified offline.
198
+ */
199
+ const [licenseStatus, setLicenseStatus] = useState<LicenseStatus | null>(
200
+ null,
201
+ );
202
+ const [licenseVerification, setLicenseVerification] = useState<string>("");
203
+ const [graceReason, setGraceReason] = useState<string>("");
204
+ const [graceEndsAt, setGraceEndsAt] = useState<string>("");
205
+ // Why the license is not valid, when the server says (master admins only).
206
+ const [licenseMessage, setLicenseMessage] = useState<string>("");
200
207
 
201
- if (BILLING_ENABLED) {
202
- return <></>;
203
- }
208
+ const isMasterAdmin: boolean = useMemo(() => {
209
+ return Boolean(UserUtil.isMasterAdmin());
210
+ }, []);
211
+
212
+ /*
213
+ * The Enterprise Edition's license manager, when the caller passed one (the
214
+ * Dashboard and the Admin Dashboard of the Enterprise image do). Never on
215
+ * the Community Edition, whatever the bundle holds: there is no license to
216
+ * manage there.
217
+ */
218
+ const LicenseManager: LicenseManagerComponent | undefined =
219
+ IS_ENTERPRISE_EDITION ? props.licenseManager : undefined;
220
+
221
+ /*
222
+ * Activating, replacing and refreshing the license are master-admin actions:
223
+ * the license is instance-wide, and its seat limit is what UserService
224
+ * enforces against, so being able to rewrite it is being able to raise the
225
+ * ceiling on the whole installation. The server enforces this
226
+ * (MasterAdminAuthorization on both POSTs, and it answers everybody else
227
+ * with the edition pill's fields only); hiding the controls just stops
228
+ * everyone else being offered a button that can only fail.
229
+ *
230
+ * It also takes a license manager to manage the license with. The login
231
+ * page (where a signed-out visitor sees the same pill) and a Community
232
+ * build pass none, and telling somebody to "add a license below" there
233
+ * would point at nothing.
234
+ */
235
+ const canManageLicense: boolean = isMasterAdmin && Boolean(LicenseManager);
204
236
 
205
237
  /*
206
- * Both the license GET and the two license POSTs answer with the same seat
207
- * fields, so the three responses are unpacked in one place.
238
+ * The server says it was asked for the Enterprise Edition
239
+ * (IS_ENTERPRISE_EDITION=true) but runs the Community Edition image, which
240
+ * has no enterprise code. Only a master admin can fix that, so only a
241
+ * master admin is told.
242
+ */
243
+ const showEditionMismatchNotice: boolean =
244
+ !IS_ENTERPRISE_EDITION &&
245
+ isMasterAdmin &&
246
+ env("ENTERPRISE_EDITION_REQUESTED_BUT_NOT_LOADED") === "true";
247
+
248
+ /*
249
+ * The license GET and the license manager's writes answer with the same
250
+ * seat fields, so every response is unpacked in one place (the manager
251
+ * calls this through applySeatEnforcement).
208
252
  * isSeatLimitEnforced is what tells "this installation does not enforce a
209
253
  * seat limit" apart from "this build did not report one" - without it, an
210
254
  * older server's silence would read as a limit of zero.
@@ -240,6 +284,18 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
240
284
  new Route("/global-config/license"),
241
285
  );
242
286
 
287
+ /*
288
+ * The route also serves the login page, so on its own it answers a
289
+ * caller with no session with the reduced anonymous payload and a 200.
290
+ * Inside a signed-in app that caller is us with an expired session,
291
+ * and the reduced payload reads as "no license, no instances". Saying
292
+ * we expect to be signed in makes the server answer 401 instead,
293
+ * which the API client refreshes and replays.
294
+ */
295
+ if (UserUtil.isLoggedIn()) {
296
+ licenseUrl.addQueryParam("signedIn", "true");
297
+ }
298
+
243
299
  const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
244
300
  await API.fetch<JSONObject>({
245
301
  method: HTTPMethod.GET,
@@ -284,24 +340,36 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
284
340
  ] as number;
285
341
  }
286
342
 
287
- if (payload["userCountUpdatedAt"]) {
288
- configModel.enterpriseLicenseUserCountUpdatedAt =
289
- OneUptimeDate.fromString(payload["userCountUpdatedAt"] as string);
290
- }
291
-
292
343
  setGlobalConfig(configModel);
293
- setLicenseInstances(parseLicenseInstances(payload["instances"]));
294
- setThisInstanceId(
295
- typeof payload["instanceId"] === "string"
296
- ? payload["instanceId"]
297
- : "",
298
- );
344
+ setLicensePayload(payload);
299
345
  setServerLicenseValid(
300
346
  typeof payload["licenseValid"] === "boolean"
301
347
  ? payload["licenseValid"]
302
348
  : null,
303
349
  );
304
- setIsEvaluationLicense(payload["isEvaluationLicense"] === true);
350
+ setIsEvaluationLicense(
351
+ payload["isEvaluation"] === true ||
352
+ payload["isEvaluationLicense"] === true,
353
+ );
354
+ setLicenseStatus(parseLicenseStatus(payload["status"]));
355
+ setLicenseVerification(
356
+ typeof payload["verification"] === "string"
357
+ ? payload["verification"]
358
+ : "",
359
+ );
360
+ setGraceReason(
361
+ typeof payload["graceReason"] === "string"
362
+ ? payload["graceReason"]
363
+ : "",
364
+ );
365
+ setGraceEndsAt(
366
+ typeof payload["graceEndsAt"] === "string"
367
+ ? payload["graceEndsAt"]
368
+ : "",
369
+ );
370
+ setLicenseMessage(
371
+ typeof payload["message"] === "string" ? payload["message"] : "",
372
+ );
305
373
 
306
374
  setCurrentVersion(
307
375
  typeof payload["currentVersion"] === "string"
@@ -326,15 +394,16 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
326
394
  setIsUpdateAvailable(payload["isUpdateAvailable"] === true);
327
395
  setIsUpdateCheckDisabled(payload["isUpdateCheckDisabled"] === true);
328
396
  applySeatEnforcementPayload(payload);
329
-
330
- if (!licenseInputEditedRef.current) {
331
- setLicenseKeyInput(configModel.enterpriseLicenseKey || "");
332
- }
333
397
  } catch (err) {
334
398
  setGlobalConfig(null);
335
- setLicenseInstances([]);
399
+ setLicensePayload(null);
336
400
  setServerLicenseValid(null);
337
401
  setIsEvaluationLicense(false);
402
+ setLicenseStatus(null);
403
+ setLicenseVerification("");
404
+ setGraceReason("");
405
+ setGraceEndsAt("");
406
+ setLicenseMessage("");
338
407
  setCurrentVersion("");
339
408
  setLatestVersion("");
340
409
  setLatestVersionPublishedAt("");
@@ -357,7 +426,7 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
357
426
  * opened rather than adding a request to every page load.
358
427
  */
359
428
  useEffect(() => {
360
- if (!IS_ENTERPRISE_EDITION) {
429
+ if (BILLING_ENABLED || !IS_ENTERPRISE_EDITION) {
361
430
  return;
362
431
  }
363
432
 
@@ -392,6 +461,88 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
392
461
  serverLicenseValid,
393
462
  ]);
394
463
 
464
+ /*
465
+ * Whether a license token IS installed, whatever the license client made of
466
+ * it.
467
+ *
468
+ * It matters because "the unlicensed trial" is no longer only for an
469
+ * installation with no license: one whose token has no expiry recorded
470
+ * beside it falls back to the same trial and the same lapse (the license
471
+ * client's classifyUnverifiedWithoutExpiry), so the copy below must not tell
472
+ * a paying customer that no license is installed.
473
+ *
474
+ * Two signals, because the two audiences are told different things. A master
475
+ * admin is sent the token itself. Everybody else is sent only the
476
+ * verification, and the license client reports "none" there exactly when
477
+ * there was no token to judge - so an "unverified" or "verified" grace
478
+ * period is a grace period with a license behind it. Neither field is new:
479
+ * both already come back from GET /global-config/license.
480
+ */
481
+ const hasLicenseInstalled: boolean =
482
+ IS_ENTERPRISE_EDITION &&
483
+ (Boolean(globalConfig?.enterpriseLicenseToken) ||
484
+ licenseVerification === "unverified" ||
485
+ licenseVerification === "verified");
486
+
487
+ /*
488
+ * The two faces of "grace". An unlicensed installation's trial (counted from
489
+ * the first run of the Enterprise Edition) is not an expired license, and
490
+ * the dialog must not tell somebody who never had a license to renew one.
491
+ */
492
+ const isUnlicensedTrial: boolean =
493
+ IS_ENTERPRISE_EDITION &&
494
+ licenseStatus === "grace" &&
495
+ graceReason === "unlicensed";
496
+
497
+ const isExpiredGrace: boolean =
498
+ IS_ENTERPRISE_EDITION &&
499
+ licenseStatus === "grace" &&
500
+ graceReason !== "unlicensed";
501
+
502
+ /*
503
+ * A license issued before OneUptime signed its licenses: accepted, but it
504
+ * cannot be verified offline.
505
+ */
506
+ const isUnverifiedLegacyLicense: boolean =
507
+ IS_ENTERPRISE_EDITION &&
508
+ licenseValid &&
509
+ !isUnlicensedTrial &&
510
+ licenseVerification === "unverified";
511
+
512
+ const graceEndsAtText: string | null = useMemo(() => {
513
+ if (!graceEndsAt) {
514
+ return null;
515
+ }
516
+
517
+ const endsAt: Date = OneUptimeDate.fromString(graceEndsAt);
518
+
519
+ if (Number.isNaN(endsAt.getTime())) {
520
+ return null;
521
+ }
522
+
523
+ return endsAt.toLocaleDateString();
524
+ }, [graceEndsAt]);
525
+
526
+ // Whole days left in the trial or the grace period, rounded up; never negative.
527
+ const graceDaysLeft: number | null = useMemo(() => {
528
+ if (!graceEndsAt) {
529
+ return null;
530
+ }
531
+
532
+ const endsAt: Date = OneUptimeDate.fromString(graceEndsAt);
533
+
534
+ if (Number.isNaN(endsAt.getTime())) {
535
+ return null;
536
+ }
537
+
538
+ return Math.max(0, Math.ceil((endsAt.getTime() - Date.now()) / DAY_IN_MS));
539
+ }, [graceEndsAt]);
540
+
541
+ const graceDaysLeftText: string =
542
+ graceDaysLeft === null
543
+ ? ""
544
+ : `${graceDaysLeft} ${graceDaysLeft === 1 ? "day" : "days"} left`;
545
+
395
546
  const licenseExpiresAtText: string | null = useMemo(() => {
396
547
  if (!globalConfig?.enterpriseLicenseExpiresAt) {
397
548
  return null;
@@ -538,22 +689,6 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
538
689
  return currentUserCount;
539
690
  }, [isSeatLimitEnforced, enforcedSeatsInUse, currentUserCount]);
540
691
 
541
- const userCountUpdatedAtText: string | null = useMemo(() => {
542
- if (!globalConfig?.enterpriseLicenseUserCountUpdatedAt) {
543
- return null;
544
- }
545
-
546
- const reportedAt: Date = OneUptimeDate.fromString(
547
- globalConfig.enterpriseLicenseUserCountUpdatedAt,
548
- );
549
-
550
- if (Number.isNaN(reportedAt.getTime())) {
551
- return null;
552
- }
553
-
554
- return reportedAt.toLocaleString();
555
- }, [globalConfig?.enterpriseLicenseUserCountUpdatedAt]);
556
-
557
692
  const isUserLimitBreached: boolean = useMemo(() => {
558
693
  if (!licenseValid) {
559
694
  return false;
@@ -607,52 +742,6 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
607
742
  return rounded;
608
743
  }, [rawUserUsagePercent]);
609
744
 
610
- /*
611
- * The displayed percent clamped to 0-100. Drives the bar width and
612
- * aria-valuenow, so the fill can never overflow its track and aria-valuenow
613
- * can never exceed aria-valuemax. Derived from the displayed percent rather
614
- * than the raw ratio so the fill always agrees with the caption beneath it:
615
- * 599/600 reads 99% and fills to 99%, not to a visually full 100%. The true
616
- * figure for a breach goes in aria-valuetext.
617
- */
618
- const seatUsageBarPercent: number = useMemo(() => {
619
- if (seatUsageDisplayPercent === null) {
620
- return 0;
621
- }
622
-
623
- return Math.min(100, Math.max(0, seatUsageDisplayPercent));
624
- }, [seatUsageDisplayPercent]);
625
-
626
- const seatsRemaining: number | null = useMemo(() => {
627
- if (typeof userLimit !== "number" || userLimit <= 0) {
628
- return null;
629
- }
630
-
631
- if (typeof effectiveUserCount !== "number") {
632
- return null;
633
- }
634
-
635
- return userLimit - effectiveUserCount;
636
- }, [userLimit, effectiveUserCount]);
637
-
638
- const seatsRemainingText: string = useMemo(() => {
639
- if (typeof seatsRemaining !== "number") {
640
- return "";
641
- }
642
-
643
- if (seatsRemaining > 0) {
644
- return `${seatsRemaining.toLocaleString()} ${
645
- seatsRemaining === 1 ? "seat" : "seats"
646
- } remaining`;
647
- }
648
-
649
- if (seatsRemaining === 0) {
650
- return "No seats remaining";
651
- }
652
-
653
- return `${Math.abs(seatsRemaining).toLocaleString()} over limit`;
654
- }, [seatsRemaining]);
655
-
656
745
  /*
657
746
  * Total and mutually exclusive. isUserLimitBreached is tested first, so
658
747
  * reaching the >= 90 test structurally guarantees the limit is not exceeded
@@ -696,107 +785,47 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
696
785
  seatUsageDisplayPercent,
697
786
  ]);
698
787
 
699
- /*
700
- * Copy containing apostrophes lives in string literals rather than JSX text,
701
- * because react/no-unescaped-entities is enforced in this repo.
702
- */
703
- const seatAdvisoryTitle: string = useMemo(() => {
704
- if (seatTone === "breached") {
705
- const over: number = Math.abs(seatsRemaining || 0);
706
-
707
- /*
708
- * Reachable now that "every seat taken" is a breach in its own right:
709
- * an installation sitting exactly on its limit is not "0 users over".
710
- */
711
- if (over === 0) {
712
- return "Every licensed seat is in use";
713
- }
714
-
715
- return over === 1
716
- ? "1 user over your licensed seats"
717
- : `${over.toLocaleString()} users over your licensed seats`;
788
+ const editionName: string = useMemo(() => {
789
+ if (!IS_ENTERPRISE_EDITION) {
790
+ return "Community Edition";
718
791
  }
719
792
 
720
- if (seatTone === "approaching") {
721
- if (seatsRemaining === 0) {
722
- return "Every licensed seat is in use";
723
- }
724
-
725
- return seatsRemaining === 1
726
- ? "Only 1 seat left on your license"
727
- : `Only ${(seatsRemaining || 0).toLocaleString()} seats left on your license`;
793
+ if (isConfigLoading) {
794
+ return "Enterprise Edition (Checking...)";
728
795
  }
729
796
 
730
- return "";
731
- }, [seatTone, seatsRemaining]);
732
-
733
- const seatAdvisoryBody: string = useMemo(() => {
734
- const limitText: string = (userLimit || 0).toLocaleString();
735
- const inUse: string = `${(effectiveUserCount || 0).toLocaleString()} of ${limitText}`;
736
-
737
- if (seatTone === "breached") {
738
- /*
739
- * When the limit is being enforced this is no longer advice, it is a
740
- * description of what the installation is already doing to invitations
741
- * and signups - so it says so plainly rather than suggesting an upgrade
742
- * as though adding people were still an option.
743
- */
744
- if (isSeatLimitEnforced && !canAddMoreUsers) {
745
- return `This installation is using ${inUse} licensed seats. New users cannot be invited, signed up or provisioned until a seat is freed up or the license is expanded.`;
746
- }
747
-
748
- return `This installation is using ${inUse} licensed seats. Expand your license so it covers everyone on the platform.`;
797
+ if (isUnlicensedTrial) {
798
+ return graceDaysLeftText
799
+ ? `Enterprise Edition (Trial, ${graceDaysLeftText})`
800
+ : "Enterprise Edition (Trial)";
749
801
  }
750
802
 
751
- if (seatTone === "approaching") {
752
- return `You're using ${inUse} licensed seats. Anyone you add beyond ${limitText} puts this installation over its licensed limit — if more people are joining, it's worth talking to OneUptime about expanding now.`;
803
+ if (isExpiredGrace) {
804
+ return "Enterprise Edition (License Expired, Grace Period)";
753
805
  }
754
806
 
755
- return "";
756
- }, [
757
- seatTone,
758
- effectiveUserCount,
759
- userLimit,
760
- isSeatLimitEnforced,
761
- canAddMoreUsers,
762
- ]);
763
-
764
- const seatUsageAriaValueText: string = useMemo(() => {
765
- if (typeof seatUsageDisplayPercent !== "number") {
766
- return "";
807
+ if (licenseValid) {
808
+ return "Enterprise Edition";
767
809
  }
768
810
 
769
- if (seatTone === "breached") {
770
- return `${seatUsageDisplayPercent}% of licensed seats in use — ${seatAdvisoryTitle}`;
811
+ if (licenseStatus === "expired") {
812
+ return "Enterprise Edition (License Expired)";
771
813
  }
772
814
 
773
- if (typeof seatsRemaining === "number") {
774
- return `${seatUsageDisplayPercent}% of licensed seats in use — ${seatsRemainingText}`;
815
+ if (licenseStatus === "invalid") {
816
+ return "Enterprise Edition (License Invalid)";
775
817
  }
776
818
 
777
- return `${seatUsageDisplayPercent}% of licensed seats in use`;
819
+ return "Enterprise Edition (License Required)";
778
820
  }, [
779
- seatUsageDisplayPercent,
780
- seatTone,
781
- seatAdvisoryTitle,
782
- seatsRemaining,
783
- seatsRemainingText,
821
+ isConfigLoading,
822
+ licenseValid,
823
+ licenseStatus,
824
+ isUnlicensedTrial,
825
+ isExpiredGrace,
826
+ graceDaysLeftText,
784
827
  ]);
785
828
 
786
- const editionName: string = useMemo(() => {
787
- if (!IS_ENTERPRISE_EDITION) {
788
- return "Community Edition";
789
- }
790
-
791
- if (isConfigLoading) {
792
- return "Enterprise Edition (Checking...)";
793
- }
794
-
795
- return licenseValid
796
- ? "Enterprise Edition"
797
- : "Enterprise Edition (License Required)";
798
- }, [isConfigLoading, licenseValid]);
799
-
800
829
  const indicatorColor: string = useMemo(() => {
801
830
  if (!IS_ENTERPRISE_EDITION) {
802
831
  return "bg-indigo-400";
@@ -814,23 +843,39 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
814
843
  return "bg-red-500";
815
844
  }
816
845
 
817
- if (seatTone === "approaching") {
846
+ if (seatTone === "approaching" || licenseStatus === "grace") {
818
847
  return "bg-amber-500";
819
848
  }
820
849
 
821
850
  return "bg-emerald-500";
822
- }, [isConfigLoading, licenseValid, seatTone]);
851
+ }, [isConfigLoading, licenseValid, seatTone, licenseStatus]);
823
852
 
824
853
  const ctaLabel: string = useMemo(() => {
825
854
  if (!IS_ENTERPRISE_EDITION) {
826
- return "Learn more";
855
+ return showEditionMismatchNotice ? "Action needed" : "Learn more";
827
856
  }
828
857
 
829
858
  if (isConfigLoading) {
830
859
  return "Checking";
831
860
  }
832
861
 
862
+ if (isUnlicensedTrial) {
863
+ return "Add license";
864
+ }
865
+
866
+ if (isExpiredGrace) {
867
+ return "Renew license";
868
+ }
869
+
833
870
  if (!licenseValid) {
871
+ if (licenseStatus === "expired") {
872
+ return "Renew license";
873
+ }
874
+
875
+ if (licenseStatus === "invalid") {
876
+ return "Fix license";
877
+ }
878
+
834
879
  return "Validate license";
835
880
  }
836
881
 
@@ -843,21 +888,36 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
843
888
  }
844
889
 
845
890
  return "View details";
846
- }, [isConfigLoading, licenseValid, seatTone]);
891
+ }, [
892
+ isConfigLoading,
893
+ licenseValid,
894
+ licenseStatus,
895
+ seatTone,
896
+ isUnlicensedTrial,
897
+ isExpiredGrace,
898
+ showEditionMismatchNotice,
899
+ ]);
847
900
 
848
901
  const pillTone: PillTone = useMemo(() => {
849
- if (!IS_ENTERPRISE_EDITION || isConfigLoading) {
902
+ if (!IS_ENTERPRISE_EDITION) {
903
+ return showEditionMismatchNotice ? "warning" : "normal";
904
+ }
905
+
906
+ if (isConfigLoading) {
850
907
  return "normal";
851
908
  }
852
909
 
853
910
  if (
854
911
  seatTone === "breached" ||
855
- (!licenseValid && Boolean(globalConfig?.enterpriseLicenseKey))
912
+ (!licenseValid &&
913
+ (Boolean(globalConfig?.enterpriseLicenseKey) ||
914
+ licenseStatus === "expired" ||
915
+ licenseStatus === "invalid"))
856
916
  ) {
857
917
  return "alerted";
858
918
  }
859
919
 
860
- if (seatTone === "approaching") {
920
+ if (seatTone === "approaching" || licenseStatus === "grace") {
861
921
  return "warning";
862
922
  }
863
923
 
@@ -865,8 +925,10 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
865
925
  }, [
866
926
  isConfigLoading,
867
927
  licenseValid,
928
+ licenseStatus,
868
929
  seatTone,
869
930
  globalConfig?.enterpriseLicenseKey,
931
+ showEditionMismatchNotice,
870
932
  ]);
871
933
 
872
934
  const modalDescription: string = useMemo(() => {
@@ -878,41 +940,20 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
878
940
  return "Checking your license with OneUptime...";
879
941
  }
880
942
 
881
- if (!licenseValid) {
882
- return "Validate your license key to activate Enterprise Edition.";
943
+ if (isUnlicensedTrial) {
944
+ return "No license is installed yet. Enterprise features are available during the trial.";
883
945
  }
884
946
 
885
- return "License, version, seat usage, and the instances covered by this key.";
886
- }, [isConfigLoading, licenseValid]);
887
-
888
- /*
889
- * The per-instance counts only exceed the unique total when someone actually
890
- * appears on more than one instance, and the component cannot know whether
891
- * they do — it receives per-instance counts and the unique total separately.
892
- * So the summation is stated as a possibility, and is dropped entirely when
893
- * there is no unique total to compare against. It names the unique user count
894
- * rather than "the total above", which would be ambiguous next to a hero
895
- * reading "119 / 120 seats".
896
- */
897
- const instanceOverlapText: string = useMemo(() => {
898
- const countingRule: string = `Seats are counted uniquely across all ${licenseInstances.length} instances that share this license — the same person on multiple instances uses one seat.`;
899
-
900
- if (typeof currentUserCount !== "number") {
901
- return countingRule;
947
+ if (isExpiredGrace) {
948
+ return "Your license has expired. Renew it before the grace period ends.";
902
949
  }
903
950
 
904
- return `${countingRule} Per-instance counts can therefore add up to more than the ${currentUserCount.toLocaleString()} unique ${
905
- currentUserCount === 1 ? "user" : "users"
906
- } counted above.`;
907
- }, [licenseInstances.length, currentUserCount]);
908
-
909
- const licenseKeyHelperText: string = useMemo(() => {
910
- if (isChangingLicense) {
911
- return "Enter the new enterprise license key and validate it to replace the current one. Your existing license stays active until the new key is validated.";
951
+ if (!licenseValid) {
952
+ return "Add a valid license to turn single sign-on, SCIM and audit logging back on and make enterprise configuration editable.";
912
953
  }
913
954
 
914
- return "You have installed Enterprise Edition of OneUptime. You need to validate your license key. Need a license key? Contact our sales team at";
915
- }, [isChangingLicense]);
955
+ return "License, version, seat usage, and the instances covered by this key.";
956
+ }, [isConfigLoading, licenseValid, isUnlicensedTrial, isExpiredGrace]);
916
957
 
917
958
  const communityFeatures: Array<string> = useMemo(() => {
918
959
  return [
@@ -935,34 +976,18 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
935
976
  ];
936
977
  }, []);
937
978
 
979
+ /*
980
+ * The license manager clears its own messages (and abandons a license
981
+ * change in progress) when the dialog opens or closes.
982
+ */
938
983
  const openDialog: () => void = () => {
939
984
  setIsDialogOpen(true);
940
- setValidationError("");
941
- setSuccessMessage("");
942
985
 
943
986
  void fetchGlobalConfig();
944
987
  };
945
988
 
946
989
  const closeDialog: () => void = () => {
947
990
  setIsDialogOpen(false);
948
- setValidationError("");
949
- setSuccessMessage("");
950
- setIsChangingLicense(false);
951
- };
952
-
953
- const handleStartChangingLicense: () => void = () => {
954
- setIsChangingLicense(true);
955
- setValidationError("");
956
- setSuccessMessage("");
957
- setLicenseKeyInput("");
958
- licenseInputEditedRef.current = true;
959
- };
960
-
961
- const handleCancelChangingLicense: () => void = () => {
962
- setIsChangingLicense(false);
963
- setValidationError("");
964
- licenseInputEditedRef.current = false;
965
- setLicenseKeyInput(globalConfig?.enterpriseLicenseKey || "");
966
991
  };
967
992
 
968
993
  const handlePrimaryAction: () => void = () => {
@@ -973,181 +998,12 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
973
998
  closeDialog();
974
999
  };
975
1000
 
976
- type BuildSeatExpansionMailtoFunction = () => string;
977
-
978
- const buildSeatExpansionMailto: BuildSeatExpansionMailtoFunction =
979
- (): string => {
980
- const subject: string =
981
- seatTone === "breached"
982
- ? "Expand OneUptime license - over seat limit"
983
- : "Expand OneUptime license - nearly out of seats";
984
-
985
- const body: string = [
986
- "Hi OneUptime,",
987
- "",
988
- "We would like to expand the seat count on our enterprise license.",
989
- "",
990
- `Company: ${globalConfig?.enterpriseCompanyName || "Not specified"}`,
991
- `Seats in use: ${(effectiveUserCount || 0).toLocaleString()} of ${(
992
- userLimit || 0
993
- ).toLocaleString()}`,
994
- `Instances on this license: ${licenseInstances.length}`,
995
- ].join("\n");
996
-
997
- return `${SALES_MAILTO_URL}?subject=${encodeURIComponent(
998
- subject,
999
- )}&body=${encodeURIComponent(body)}`;
1000
- };
1001
-
1002
- const handleRequestMoreSeats: () => void = () => {
1003
- if (typeof window !== "undefined") {
1004
- window.location.href = buildSeatExpansionMailto();
1005
- }
1006
- };
1007
-
1008
- const runLicenseValidation: (
1009
- key: string,
1010
- setLoading: React.Dispatch<React.SetStateAction<boolean>>,
1011
- ) => Promise<void> = useCallback(
1012
- async (
1013
- key: string,
1014
- setLoading: React.Dispatch<React.SetStateAction<boolean>>,
1015
- ): Promise<void> => {
1016
- const trimmedKey: string = key.trim();
1017
-
1018
- if (!trimmedKey) {
1019
- setValidationError("Please enter a license key before validating.");
1020
- setSuccessMessage("");
1021
- return;
1022
- }
1023
-
1024
- setValidationError("");
1025
- setSuccessMessage("");
1026
- setLoading(true);
1027
-
1028
- try {
1029
- const validationUrl: URL = URL.fromURL(APP_API_URL).addRoute(
1030
- new Route("/global-config/license"),
1031
- );
1032
-
1033
- const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
1034
- await API.fetch<JSONObject>({
1035
- method: HTTPMethod.POST,
1036
- url: validationUrl,
1037
- data: {
1038
- licenseKey: trimmedKey,
1039
- },
1040
- });
1041
-
1042
- if (!response.isSuccess()) {
1043
- throw response;
1044
- }
1045
-
1046
- const payload: JSONObject = response.data as JSONObject;
1047
-
1048
- licenseInputEditedRef.current = false;
1049
- setLicenseKeyInput((payload["licenseKey"] as string) || trimmedKey);
1050
- setSuccessMessage("License validated successfully.");
1051
- setIsChangingLicense(false);
1052
-
1053
- await fetchGlobalConfig();
1054
- } catch (err) {
1055
- setValidationError(API.getFriendlyMessage(err));
1056
- } finally {
1057
- setLoading(false);
1058
- }
1059
- },
1060
- [fetchGlobalConfig],
1061
- );
1062
-
1063
- const handleValidateClick: () => void = () => {
1064
- if (isValidating) {
1065
- return;
1066
- }
1067
-
1068
- void runLicenseValidation(licenseKeyInput, setIsValidating);
1069
- };
1070
-
1071
- /*
1072
- * Re-ask oneuptime.com about the license this installation already holds.
1073
- *
1074
- * The seat limit and the expiry are changed on oneuptime.com, and the daily
1075
- * report job is what normally carries them here - so without this, an
1076
- * administrator who has just bought seats waits up to a day before the
1077
- * installation stops refusing invitations. Deliberately sends no key: the
1078
- * server refreshes the stored one.
1079
- */
1080
- const handleRefreshLicense: () => void = useCallback((): void => {
1081
- if (isRefreshingLicense) {
1082
- return;
1083
- }
1084
-
1085
- const refresh: () => Promise<void> = async (): Promise<void> => {
1086
- setValidationError("");
1087
- setSuccessMessage("");
1088
- setIsRefreshingLicense(true);
1089
-
1090
- try {
1091
- const refreshUrl: URL = URL.fromURL(APP_API_URL).addRoute(
1092
- new Route("/global-config/license/refresh"),
1093
- );
1094
-
1095
- const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
1096
- await API.fetch<JSONObject>({
1097
- method: HTTPMethod.POST,
1098
- url: refreshUrl,
1099
- });
1100
-
1101
- if (!response.isSuccess()) {
1102
- throw response;
1103
- }
1104
-
1105
- applySeatEnforcementPayload(response.data as JSONObject);
1106
-
1107
- setSuccessMessage("License refreshed from OneUptime.");
1108
-
1109
- /*
1110
- * The POST already returned the new terms, but the modal renders from
1111
- * what the GET reports - re-reading keeps one source of truth for the
1112
- * whole dialog instead of two that can disagree.
1113
- */
1114
- await fetchGlobalConfig();
1115
- } catch (err) {
1116
- setValidationError(API.getFriendlyMessage(err));
1117
- } finally {
1118
- setIsRefreshingLicense(false);
1119
- }
1120
- };
1121
-
1122
- void refresh();
1123
- }, [isRefreshingLicense, fetchGlobalConfig, applySeatEnforcementPayload]);
1124
-
1125
1001
  const handleRetryFetch: () => void = () => {
1126
1002
  if (!isConfigLoading) {
1127
1003
  void fetchGlobalConfig();
1128
1004
  }
1129
1005
  };
1130
1006
 
1131
- /*
1132
- * Activating, replacing and refreshing the license are master-admin actions:
1133
- * the license is instance-wide, and its seat limit is what UserService
1134
- * enforces against, so being able to rewrite it is being able to raise the
1135
- * ceiling on the whole installation. The server enforces this
1136
- * (MasterAdminAuthorization on both POSTs); hiding the controls here just
1137
- * stops everyone else being offered a button that can only fail.
1138
- *
1139
- * False for a signed-out visitor, which is what keeps the license key input
1140
- * off the login page where this same component renders the edition pill.
1141
- */
1142
- const canManageLicense: boolean = useMemo(() => {
1143
- return Boolean(UserUtil.isMasterAdmin());
1144
- }, []);
1145
-
1146
- const showLicenseKeyInput: boolean =
1147
- IS_ENTERPRISE_EDITION &&
1148
- canManageLicense &&
1149
- (!licenseValid || isChangingLicense);
1150
-
1151
1007
  /*
1152
1008
  * Someone who cannot fix the license still needs to be told why the
1153
1009
  * installation is behaving like an unlicensed one, and who can fix it.
@@ -1159,14 +1015,21 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
1159
1015
  !licenseValid &&
1160
1016
  !canManageLicense;
1161
1017
 
1162
- const shouldShowEnterpriseValidationButton: boolean = showLicenseKeyInput;
1163
-
1164
1018
  /*
1165
1019
  * Collapses the !configError && !isConfigLoading && licenseValid chain that
1166
- * gates the stat strip, the seat card and the instances card.
1020
+ * gates the stat strip here, and the license manager's seat card, instances
1021
+ * card and footer actions.
1167
1022
  */
1168
1023
  const showLicenseDetails: boolean =
1169
- IS_ENTERPRISE_EDITION && !configError && !isConfigLoading && licenseValid;
1024
+ IS_ENTERPRISE_EDITION &&
1025
+ !configError &&
1026
+ !isConfigLoading &&
1027
+ licenseValid &&
1028
+ !isUnlicensedTrial;
1029
+
1030
+ // Status notices describe the license, so they wait for it to load.
1031
+ const showLicenseStatusNotices: boolean =
1032
+ IS_ENTERPRISE_EDITION && !configError && !isConfigLoading;
1170
1033
 
1171
1034
  /*
1172
1035
  * The evaluation notice rides along with the license details: it is only
@@ -1184,7 +1047,10 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
1184
1047
  IS_ENTERPRISE_EDITION && licenseValid && isEvaluationLicense;
1185
1048
 
1186
1049
  const showEnterpriseFeatureList: boolean =
1187
- IS_ENTERPRISE_EDITION && !configError && !isConfigLoading && !licenseValid;
1050
+ IS_ENTERPRISE_EDITION &&
1051
+ !configError &&
1052
+ !isConfigLoading &&
1053
+ (!licenseValid || isUnlicensedTrial);
1188
1054
 
1189
1055
  /*
1190
1056
  * Gated on neither edition nor licenseValid: which build you run, and
@@ -1196,40 +1062,6 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
1196
1062
  const showVersionCard: boolean =
1197
1063
  !configError && !isConfigLoading && Boolean(currentVersion);
1198
1064
 
1199
- /*
1200
- * GlobalConfigAPI returns userLimit and currentUserCount to anonymous callers
1201
- * because the same route serves the signed-out login page, and gates only
1202
- * licenseKey, token, instances and instanceId on isAuthenticatedUser. Gate
1203
- * the sales ask on a field the server does redact, so a signed-out visitor is
1204
- * never shown an administrator-targeted CTA.
1205
- */
1206
- const canSeeLicenseAdmin: boolean = Boolean(
1207
- globalConfig?.enterpriseLicenseKey,
1208
- );
1209
-
1210
- const modalSubmitButtonText: string | undefined = IS_ENTERPRISE_EDITION
1211
- ? shouldShowEnterpriseValidationButton
1212
- ? "Validate License"
1213
- : undefined
1214
- : "Talk to Sales";
1215
-
1216
- const modalOnSubmit: (() => void) | undefined = IS_ENTERPRISE_EDITION
1217
- ? shouldShowEnterpriseValidationButton
1218
- ? handleValidateClick
1219
- : undefined
1220
- : handlePrimaryAction;
1221
-
1222
- const modalIsLoading: boolean =
1223
- IS_ENTERPRISE_EDITION && shouldShowEnterpriseValidationButton
1224
- ? isValidating
1225
- : false;
1226
-
1227
- const modalDisableSubmitButton: boolean | undefined = IS_ENTERPRISE_EDITION
1228
- ? shouldShowEnterpriseValidationButton
1229
- ? !licenseKeyInput.trim() || isValidating || isConfigLoading
1230
- : undefined
1231
- : false;
1232
-
1233
1065
  const modalRightElement: ReactElement | undefined = showLicenseDetails ? (
1234
1066
  <span
1235
1067
  className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium ${
@@ -1253,41 +1085,12 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
1253
1085
  ? "Seat limit exceeded"
1254
1086
  : seatTone === "approaching"
1255
1087
  ? "Seats nearly full"
1256
- : "License active"}
1088
+ : isExpiredGrace
1089
+ ? "Grace period"
1090
+ : "License active"}
1257
1091
  </span>
1258
1092
  ) : undefined;
1259
1093
 
1260
- const modalLeftFooterElement: ReactElement | undefined =
1261
- showLicenseDetails && !isChangingLicense && canManageLicense ? (
1262
- <div className="flex flex-wrap items-center gap-2">
1263
- {/*
1264
- * The seat limit and the expiry are changed on oneuptime.com and reach
1265
- * this installation through the daily report job. This is the button
1266
- * for the administrator who has just bought seats and would rather not
1267
- * wait a day for the installation to stop refusing invitations.
1268
- */}
1269
- <Button
1270
- title="Refresh license"
1271
- icon={IconProp.Refresh}
1272
- buttonStyle={ButtonStyleType.NORMAL}
1273
- onClick={handleRefreshLicense}
1274
- isLoading={isRefreshingLicense}
1275
- disabled={isRefreshingLicense || isConfigLoading}
1276
- tooltip="Fetch the latest seat limit and expiry for this license from OneUptime."
1277
- dataTestId="refresh-enterprise-license"
1278
- className="!mt-0 md:!ml-0"
1279
- />
1280
- <Button
1281
- title="Change license key"
1282
- icon={IconProp.Edit}
1283
- buttonStyle={ButtonStyleType.NORMAL}
1284
- onClick={handleStartChangingLicense}
1285
- disabled={isRefreshingLicense}
1286
- className="!mt-0 md:!ml-0"
1287
- />
1288
- </div>
1289
- ) : undefined;
1290
-
1291
1094
  const pillClassName: string =
1292
1095
  pillTone === "alerted"
1293
1096
  ? "group inline-flex items-center gap-2 rounded-full border border-red-200 bg-red-50 px-3 py-1 text-xs font-medium text-red-700 shadow-sm transition hover:border-red-300 hover:bg-red-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-400"
@@ -1472,6 +1275,450 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
1472
1275
  </section>
1473
1276
  ) : null;
1474
1277
 
1278
+ /*
1279
+ * What the license status means for this installation, in plain words.
1280
+ * Before the lapse (the trial, the grace period) every branch warns what
1281
+ * will stop and when (LICENSE_LAPSE_CONSEQUENCES); after it, what has
1282
+ * stopped (LICENSE_LAPSED_STATE).
1283
+ */
1284
+ const licenseStatusNoticeElement: ReactElement | null = (() => {
1285
+ if (!showLicenseStatusNotices) {
1286
+ return null;
1287
+ }
1288
+
1289
+ if (isUnlicensedTrial) {
1290
+ return (
1291
+ <section
1292
+ role="status"
1293
+ data-testid="enterprise-license-trial-notice"
1294
+ className="rounded-xl border border-amber-200 bg-amber-50 p-5"
1295
+ >
1296
+ <h4 className="text-sm font-semibold text-amber-900">
1297
+ {graceDaysLeftText
1298
+ ? `Enterprise Edition trial: ${graceDaysLeftText}`
1299
+ : "Enterprise Edition trial"}
1300
+ </h4>
1301
+ <p className="mt-1 text-xs leading-relaxed text-amber-800">
1302
+ {`${
1303
+ hasLicenseInstalled
1304
+ ? "A license is installed, but this installation cannot tell whether it is still current, so it is treating this as an unlicensed install."
1305
+ : "No Enterprise license is installed."
1306
+ } Enterprise features stay fully available${
1307
+ graceEndsAtText ? ` until ${graceEndsAtText}` : ""
1308
+ }, counted from the first time this installation ran the Enterprise Edition. ${TRIAL_ENFORCEMENT_SUMMARY}`}
1309
+ </p>
1310
+ {/*
1311
+ * What the license client actually said about the installed license.
1312
+ * Only a master admin is sent it, and only this state has one to
1313
+ * show: a genuinely unlicensed install's message is the sentence
1314
+ * above.
1315
+ */}
1316
+ {hasLicenseInstalled && licenseMessage && (
1317
+ <p
1318
+ data-testid="enterprise-license-status-message"
1319
+ className="mt-2 text-xs leading-relaxed text-amber-800"
1320
+ >
1321
+ {licenseMessage}
1322
+ </p>
1323
+ )}
1324
+ <p className="mt-2 text-xs leading-relaxed text-amber-800">
1325
+ {canManageLicense
1326
+ ? hasLicenseInstalled
1327
+ ? "Re-activate the license below before the trial ends, or let the daily license sync fetch its expiry from OneUptime, to keep single sign-on, SCIM and audit logging running and enterprise configuration editable."
1328
+ : "Add a license below before the trial ends to keep single sign-on, SCIM and audit logging running and enterprise configuration editable."
1329
+ : hasLicenseInstalled
1330
+ ? "Ask a master admin of this installation to re-activate the license."
1331
+ : "Ask a master admin of this installation to add a license."}
1332
+ </p>
1333
+ </section>
1334
+ );
1335
+ }
1336
+
1337
+ if (isExpiredGrace) {
1338
+ return (
1339
+ <section
1340
+ role="status"
1341
+ data-testid="enterprise-license-grace-notice"
1342
+ className="rounded-xl border border-amber-200 bg-amber-50 p-5"
1343
+ >
1344
+ <h4 className="text-sm font-semibold text-amber-900">
1345
+ {graceEndsAtText
1346
+ ? `License expired, grace period until ${graceEndsAtText}`
1347
+ : "License expired, grace period"}
1348
+ </h4>
1349
+ <p className="mt-1 text-xs leading-relaxed text-amber-800">
1350
+ {`The Enterprise license expired${
1351
+ licenseExpiresAtText ? ` on ${licenseExpiresAtText}` : ""
1352
+ }. Every enterprise feature keeps working until the grace period ends${
1353
+ graceDaysLeftText ? ` (${graceDaysLeftText})` : ""
1354
+ }. ${GRACE_ENFORCEMENT_SUMMARY}`}
1355
+ </p>
1356
+ <p className="mt-2 text-xs leading-relaxed text-amber-800">
1357
+ {canManageLicense
1358
+ ? "Renew the license with OneUptime, then refresh it here."
1359
+ : "Ask a master admin of this installation to renew the license."}
1360
+ </p>
1361
+ </section>
1362
+ );
1363
+ }
1364
+
1365
+ if (!licenseValid) {
1366
+ const title: string =
1367
+ licenseStatus === "expired"
1368
+ ? "License expired"
1369
+ : licenseStatus === "invalid"
1370
+ ? "License not valid"
1371
+ : "No valid license";
1372
+
1373
+ const reason: string =
1374
+ licenseStatus === "expired"
1375
+ ? `The Enterprise license expired${
1376
+ licenseExpiresAtText ? ` on ${licenseExpiresAtText}` : ""
1377
+ } and its grace period is over.`
1378
+ : licenseStatus === "invalid"
1379
+ ? licenseMessage ||
1380
+ "The stored license could not be verified. Validate the license again."
1381
+ : hasLicenseInstalled
1382
+ ? "A license is installed, but this installation cannot confirm that it is current, so nothing is licensed by it."
1383
+ : "This installation has no valid Enterprise license.";
1384
+
1385
+ /*
1386
+ * Whatever the license client said, for EVERY status and not only
1387
+ * "invalid".
1388
+ *
1389
+ * The reason above is this component's own reading of a status code, and
1390
+ * a status code cannot carry a diagnosis: "missing" is both "no license"
1391
+ * and "a license whose expiry was never recorded, re-activate it or let
1392
+ * the daily sync fetch it" (the license client's
1393
+ * classifyUnverifiedWithoutExpiry). The second one is the whole point of
1394
+ * the message field, and it used to be rendered nowhere but here in the
1395
+ * "invalid" branch - which that state deliberately no longer is.
1396
+ *
1397
+ * Skipped when the reason already IS the message ("invalid"), so a master
1398
+ * admin is never shown the same sentence twice.
1399
+ */
1400
+ const explanation: string =
1401
+ licenseMessage && licenseMessage !== reason ? licenseMessage : "";
1402
+
1403
+ return (
1404
+ <section
1405
+ role="alert"
1406
+ data-testid="enterprise-license-required-notice"
1407
+ className="rounded-xl border border-red-200 bg-red-50 p-5"
1408
+ >
1409
+ <h4 className="text-sm font-semibold text-red-900">{title}</h4>
1410
+ {explanation && (
1411
+ <p
1412
+ data-testid="enterprise-license-status-message"
1413
+ className="mt-1 text-xs leading-relaxed text-red-800"
1414
+ >
1415
+ {explanation}
1416
+ </p>
1417
+ )}
1418
+ <p className="mt-1 text-xs leading-relaxed text-red-800">
1419
+ {`${reason} ${LICENSE_LAPSED_STATE}`}
1420
+ </p>
1421
+ </section>
1422
+ );
1423
+ }
1424
+
1425
+ if (isUnverifiedLegacyLicense && canManageLicense) {
1426
+ return (
1427
+ <section
1428
+ data-testid="enterprise-license-unverified-notice"
1429
+ className="rounded-xl border border-gray-200 bg-gray-50 p-4"
1430
+ >
1431
+ <h4 className="text-sm font-semibold text-gray-900">
1432
+ Legacy license
1433
+ </h4>
1434
+ <p className="mt-1 text-xs leading-relaxed text-gray-600">
1435
+ This license was issued before OneUptime signed its licenses, so
1436
+ this installation cannot verify it offline. It keeps working;
1437
+ refreshing the license replaces it with a signed one once OneUptime
1438
+ issues them.
1439
+ </p>
1440
+ </section>
1441
+ );
1442
+ }
1443
+
1444
+ return null;
1445
+ })();
1446
+
1447
+ /*
1448
+ * What the license manager needs to know, all of it read from the license
1449
+ * GET (see LicenseManager.ts).
1450
+ */
1451
+ const licenseSnapshot: LicenseSnapshot = {
1452
+ payload: licensePayload,
1453
+ isLoading: isConfigLoading,
1454
+ loadError: configError,
1455
+ canManageLicense: canManageLicense,
1456
+ licenseValid: licenseValid,
1457
+ isUnlicensedTrial: isUnlicensedTrial,
1458
+ showLicenseDetails: showLicenseDetails,
1459
+ companyName: globalConfig?.enterpriseCompanyName || "",
1460
+ licenseKey: globalConfig?.enterpriseLicenseKey || "",
1461
+ userLimit: userLimit,
1462
+ currentUserCount: currentUserCount,
1463
+ effectiveUserCount: effectiveUserCount,
1464
+ isSeatLimitEnforced: isSeatLimitEnforced,
1465
+ canAddMoreUsers: canAddMoreUsers,
1466
+ seatTone: seatTone,
1467
+ seatUsageDisplayPercent: seatUsageDisplayPercent,
1468
+ currentVersion: currentVersion,
1469
+ latestVersion: latestVersion,
1470
+ hasComparableVersion: hasComparableVersion,
1471
+ };
1472
+
1473
+ /*
1474
+ * The dialog, with whatever the license manager adds to it: its messages,
1475
+ * the seat usage and instance cards, the activation input and the footer
1476
+ * actions. With no manager (the Community Edition, the login page) the
1477
+ * parts are empty and the dialog shows the license status only.
1478
+ */
1479
+ const renderDialog: (parts: LicenseManagerDialogParts) => ReactElement = (
1480
+ parts: LicenseManagerDialogParts,
1481
+ ): ReactElement => {
1482
+ if (!isDialogOpen) {
1483
+ return <></>;
1484
+ }
1485
+
1486
+ return (
1487
+ <Modal
1488
+ title={editionName}
1489
+ description={modalDescription}
1490
+ rightElement={modalRightElement}
1491
+ submitButtonText={
1492
+ IS_ENTERPRISE_EDITION ? parts.submitButtonText : "Talk to Sales"
1493
+ }
1494
+ closeButtonText="Close"
1495
+ onClose={closeDialog}
1496
+ onSubmit={IS_ENTERPRISE_EDITION ? parts.onSubmit : handlePrimaryAction}
1497
+ modalWidth={ModalWidth.Medium}
1498
+ isLoading={IS_ENTERPRISE_EDITION ? Boolean(parts.isSubmitting) : false}
1499
+ disableSubmitButton={
1500
+ IS_ENTERPRISE_EDITION ? parts.disableSubmitButton : false
1501
+ }
1502
+ isBodyLoading={IS_ENTERPRISE_EDITION ? isConfigLoading : false}
1503
+ leftFooterElement={
1504
+ IS_ENTERPRISE_EDITION ? parts.leftFooterElement : undefined
1505
+ }
1506
+ >
1507
+ <div className="space-y-4 text-sm text-gray-600">
1508
+ {IS_ENTERPRISE_EDITION ? (
1509
+ <>
1510
+ {parts.messages}
1511
+
1512
+ {configError && (
1513
+ <div className="rounded-xl border border-red-200 bg-red-50 p-4">
1514
+ <div className="flex items-start gap-2.5">
1515
+ <Icon
1516
+ icon={IconProp.Alert}
1517
+ className="mt-0.5 h-4 w-4 shrink-0 text-red-600"
1518
+ />
1519
+ <div className="min-w-0 flex-1">
1520
+ <p className="text-sm font-semibold text-red-900">
1521
+ Unable to load license details
1522
+ </p>
1523
+ <p className="mt-1 text-xs leading-relaxed text-red-800">
1524
+ {configError}
1525
+ </p>
1526
+ <div className="mt-3">
1527
+ <Button
1528
+ title="Try again"
1529
+ buttonStyle={ButtonStyleType.DANGER}
1530
+ onClick={handleRetryFetch}
1531
+ isLoading={isConfigLoading}
1532
+ className="!mt-0 md:!ml-0"
1533
+ />
1534
+ </div>
1535
+ </div>
1536
+ </div>
1537
+ </div>
1538
+ )}
1539
+
1540
+ {evaluationNoticeElement}
1541
+
1542
+ {licenseStatusNoticeElement}
1543
+
1544
+ {showLicenseDetails && (
1545
+ <dl className="grid grid-cols-1 gap-px overflow-hidden rounded-xl border border-gray-200 bg-gray-200 sm:grid-cols-2">
1546
+ <div className="min-w-0 bg-white px-4 py-3">
1547
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-gray-500">
1548
+ Licensed to
1549
+ </dt>
1550
+ <dd
1551
+ className="mt-1 truncate text-sm font-medium text-gray-900"
1552
+ title={globalConfig?.enterpriseCompanyName || undefined}
1553
+ >
1554
+ {globalConfig?.enterpriseCompanyName || "Not specified"}
1555
+ </dd>
1556
+ </div>
1557
+ <div className="bg-white px-4 py-3">
1558
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-gray-500">
1559
+ Expires
1560
+ </dt>
1561
+ <dd className="mt-1 text-sm font-medium tabular-nums text-gray-900">
1562
+ {licenseExpiresAtText || "—"}
1563
+ </dd>
1564
+ </div>
1565
+ </dl>
1566
+ )}
1567
+
1568
+ {versionCardElement}
1569
+
1570
+ {parts.usage}
1571
+
1572
+ {showLicenseAdminRequiredNotice && (
1573
+ <section className="rounded-xl border border-amber-200 bg-amber-50 p-5">
1574
+ <h4 className="text-sm font-semibold text-amber-900">
1575
+ A master admin has to activate this license
1576
+ </h4>
1577
+ <p className="mt-1 text-xs leading-relaxed text-amber-800">
1578
+ This installation does not have a valid enterprise license.
1579
+ Ask a master admin of this OneUptime installation to enter
1580
+ or refresh the license key from this dialog.
1581
+ </p>
1582
+ </section>
1583
+ )}
1584
+
1585
+ {parts.activation}
1586
+
1587
+ {showEnterpriseFeatureList && (
1588
+ <section
1589
+ aria-labelledby="edition-features-heading"
1590
+ className="rounded-xl border border-indigo-100 bg-gradient-to-br from-indigo-50 via-white to-white p-5"
1591
+ >
1592
+ <h4
1593
+ id="edition-features-heading"
1594
+ className="text-sm font-semibold text-indigo-900"
1595
+ >
1596
+ What your license unlocks
1597
+ </h4>
1598
+ <p className="mt-0.5 text-xs text-indigo-700">
1599
+ {canManageLicense
1600
+ ? "A valid license that includes them keeps single sign-on, SCIM provisioning and audit logging running, enterprise configuration (SSO, SCIM, team compliance, audit log settings) editable and the enterprise admin dashboards unlocked."
1601
+ : "A master admin can add the license to keep single sign-on, SCIM provisioning and audit logging running and enterprise configuration editable."}{" "}
1602
+ Core monitoring never depends on it.
1603
+ </p>
1604
+ <ul className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2">
1605
+ {enterpriseFeatures.map(
1606
+ (feature: string, index: number) => {
1607
+ return (
1608
+ <li
1609
+ key={index}
1610
+ className="flex items-start gap-2 rounded-lg border border-gray-100 bg-white px-3 py-2.5"
1611
+ >
1612
+ <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-indigo-50">
1613
+ <Icon
1614
+ icon={IconProp.Check}
1615
+ className="h-3 w-3 text-indigo-600"
1616
+ />
1617
+ </span>
1618
+ <span className="text-xs leading-snug text-gray-700">
1619
+ {feature}
1620
+ </span>
1621
+ </li>
1622
+ );
1623
+ },
1624
+ )}
1625
+ </ul>
1626
+ </section>
1627
+ )}
1628
+ </>
1629
+ ) : (
1630
+ <>
1631
+ {showEditionMismatchNotice && (
1632
+ <section
1633
+ role="alert"
1634
+ data-testid="enterprise-edition-image-mismatch"
1635
+ className="rounded-xl border border-amber-200 bg-amber-50 p-5"
1636
+ >
1637
+ <h4 className="text-sm font-semibold text-amber-900">
1638
+ This is the Community Edition image
1639
+ </h4>
1640
+ <p className="mt-1 text-xs leading-relaxed text-amber-800">
1641
+ IS_ENTERPRISE_EDITION is set but this is the Community
1642
+ Edition image — switch to the enterprise image (the
1643
+ enterprise-* image tags) to use Enterprise Edition features.
1644
+ Until then they are off; everything else keeps working.
1645
+ </p>
1646
+ </section>
1647
+ )}
1648
+ {versionCardElement}
1649
+ <p>
1650
+ You are running the Community Edition of OneUptime. Here is a
1651
+ quick comparison to help you decide if Enterprise is the right
1652
+ fit for your team.
1653
+ </p>
1654
+ <div className="grid gap-4 md:grid-cols-2">
1655
+ <div className="rounded-xl border border-gray-200 bg-white p-4">
1656
+ <h4 className="text-sm font-semibold text-gray-900">
1657
+ Community Edition
1658
+ </h4>
1659
+ <ul className="mt-3 space-y-2 text-sm text-gray-600">
1660
+ {communityFeatures.map((feature: string, index: number) => {
1661
+ return (
1662
+ <li key={index} className="flex items-start gap-2">
1663
+ <Icon
1664
+ icon={IconProp.Check}
1665
+ className="mt-0.5 h-3 w-3 shrink-0 text-gray-400"
1666
+ />
1667
+ <span className="leading-snug">{feature}</span>
1668
+ </li>
1669
+ );
1670
+ })}
1671
+ </ul>
1672
+ <p className="mt-3 text-xs text-gray-500">
1673
+ Best for small teams experimenting with reliability
1674
+ workflows.
1675
+ </p>
1676
+ </div>
1677
+ <div className="rounded-xl border border-indigo-100 bg-gradient-to-br from-indigo-50 via-white to-white p-4">
1678
+ <h4 className="text-sm font-semibold text-indigo-900">
1679
+ Enterprise Edition
1680
+ </h4>
1681
+ <ul className="mt-3 space-y-2 text-sm text-indigo-900">
1682
+ {enterpriseFeatures.map(
1683
+ (feature: string, index: number) => {
1684
+ return (
1685
+ <li key={index} className="flex items-start gap-2">
1686
+ <Icon
1687
+ icon={IconProp.Check}
1688
+ className="mt-0.5 h-3 w-3 shrink-0 text-indigo-600"
1689
+ />
1690
+ <span className="leading-snug">{feature}</span>
1691
+ </li>
1692
+ );
1693
+ },
1694
+ )}
1695
+ </ul>
1696
+ <p className="mt-3 text-xs text-indigo-700">
1697
+ Everything in Community plus white-glove onboarding,
1698
+ enterprise SLAs, and a partner dedicated to your reliability
1699
+ goals.
1700
+ </p>
1701
+ </div>
1702
+ </div>
1703
+ <p className="text-xs text-gray-500">
1704
+ Ready to unlock enterprise capabilities? Click &quot;Talk to
1705
+ Sales&quot; to start the conversation.
1706
+ </p>
1707
+ </>
1708
+ )}
1709
+ </div>
1710
+ </Modal>
1711
+ );
1712
+ };
1713
+
1714
+ /*
1715
+ * Kept after every hook: oneuptime.com (billing enabled) bounds seats and
1716
+ * plans through subscriptions and shows no edition pill at all.
1717
+ */
1718
+ if (BILLING_ENABLED) {
1719
+ return <></>;
1720
+ }
1721
+
1475
1722
  return (
1476
1723
  <>
1477
1724
  <button
@@ -1511,650 +1758,16 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
1511
1758
  <span className={pillCtaTextClassName}>{ctaLabel}</span>
1512
1759
  </button>
1513
1760
 
1514
- {isDialogOpen && (
1515
- <Modal
1516
- title={editionName}
1517
- description={modalDescription}
1518
- rightElement={modalRightElement}
1519
- submitButtonText={modalSubmitButtonText}
1520
- closeButtonText="Close"
1521
- onClose={closeDialog}
1522
- onSubmit={modalOnSubmit}
1523
- modalWidth={ModalWidth.Medium}
1524
- isLoading={modalIsLoading}
1525
- disableSubmitButton={modalDisableSubmitButton}
1526
- isBodyLoading={IS_ENTERPRISE_EDITION ? isConfigLoading : false}
1527
- leftFooterElement={modalLeftFooterElement}
1528
- >
1529
- <div className="space-y-4 text-sm text-gray-600">
1530
- {IS_ENTERPRISE_EDITION ? (
1531
- <>
1532
- {!configError && successMessage && (
1533
- <Alert type={AlertType.SUCCESS} title={successMessage} />
1534
- )}
1535
-
1536
- {/*
1537
- * A failed refresh has nowhere else to go: the license key
1538
- * section carries its own copy of this alert, but that
1539
- * section is not rendered at all while the license is valid,
1540
- * which is exactly when refreshing is worth doing.
1541
- */}
1542
- {!configError && validationError && !showLicenseKeyInput && (
1543
- <Alert type={AlertType.DANGER} title={validationError} />
1544
- )}
1545
-
1546
- {configError && (
1547
- <div className="rounded-xl border border-red-200 bg-red-50 p-4">
1548
- <div className="flex items-start gap-2.5">
1549
- <Icon
1550
- icon={IconProp.Alert}
1551
- className="mt-0.5 h-4 w-4 shrink-0 text-red-600"
1552
- />
1553
- <div className="min-w-0 flex-1">
1554
- <p className="text-sm font-semibold text-red-900">
1555
- Unable to load license details
1556
- </p>
1557
- <p className="mt-1 text-xs leading-relaxed text-red-800">
1558
- {configError}
1559
- </p>
1560
- <div className="mt-3">
1561
- <Button
1562
- title="Try again"
1563
- buttonStyle={ButtonStyleType.DANGER}
1564
- onClick={handleRetryFetch}
1565
- isLoading={isConfigLoading}
1566
- className="!mt-0 md:!ml-0"
1567
- />
1568
- </div>
1569
- </div>
1570
- </div>
1571
- </div>
1572
- )}
1573
-
1574
- {evaluationNoticeElement}
1575
-
1576
- {showLicenseDetails && (
1577
- <dl className="grid grid-cols-1 gap-px overflow-hidden rounded-xl border border-gray-200 bg-gray-200 sm:grid-cols-2">
1578
- <div className="min-w-0 bg-white px-4 py-3">
1579
- <dt className="text-[11px] font-medium uppercase tracking-wide text-gray-500">
1580
- Licensed to
1581
- </dt>
1582
- <dd
1583
- className="mt-1 truncate text-sm font-medium text-gray-900"
1584
- title={globalConfig?.enterpriseCompanyName || undefined}
1585
- >
1586
- {globalConfig?.enterpriseCompanyName || "Not specified"}
1587
- </dd>
1588
- </div>
1589
- <div className="bg-white px-4 py-3">
1590
- <dt className="text-[11px] font-medium uppercase tracking-wide text-gray-500">
1591
- Expires
1592
- </dt>
1593
- <dd className="mt-1 text-sm font-medium tabular-nums text-gray-900">
1594
- {licenseExpiresAtText || "—"}
1595
- </dd>
1596
- </div>
1597
- </dl>
1598
- )}
1599
-
1600
- {versionCardElement}
1601
-
1602
- {showLicenseDetails && (
1603
- <section
1604
- aria-labelledby="edition-seats-heading"
1605
- className="rounded-xl border border-gray-200 bg-white p-5"
1606
- >
1607
- <div className="flex items-start justify-between gap-4">
1608
- <div className="min-w-0">
1609
- <h4
1610
- id="edition-seats-heading"
1611
- className="text-sm font-semibold text-gray-900"
1612
- >
1613
- Licensed seats
1614
- </h4>
1615
- <p className="mt-0.5 text-xs text-gray-500">
1616
- Unique users across every instance on this license.
1617
- </p>
1618
- </div>
1619
- {seatTone !== "healthy" && (
1620
- <span
1621
- className={`inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-xs font-medium ${
1622
- seatTone === "breached"
1623
- ? "bg-red-100 text-red-700"
1624
- : "bg-amber-100 text-amber-800"
1625
- }`}
1626
- >
1627
- {seatTone === "breached"
1628
- ? "Limit exceeded"
1629
- : "Nearly full"}
1630
- </span>
1631
- )}
1632
- </div>
1633
-
1634
- <div className="mt-4 flex items-end justify-between gap-4">
1635
- <p className="flex items-baseline gap-1.5">
1636
- <span
1637
- className={`text-3xl font-semibold leading-none tabular-nums ${
1638
- typeof effectiveUserCount !== "number"
1639
- ? "text-gray-300"
1640
- : seatTone === "breached"
1641
- ? "text-red-700"
1642
- : "text-gray-900"
1643
- }`}
1644
- >
1645
- {typeof effectiveUserCount === "number"
1646
- ? effectiveUserCount.toLocaleString()
1647
- : "—"}
1648
- </span>
1649
- <span className="text-sm tabular-nums text-gray-500">
1650
- {" / "}
1651
- {typeof userLimit === "number" && userLimit > 0
1652
- ? `${userLimit.toLocaleString()} seats`
1653
- : "unlimited"}
1654
- </span>
1655
- </p>
1656
- {typeof seatsRemaining === "number" && (
1657
- <p
1658
- className={`text-xs font-medium tabular-nums ${
1659
- seatTone === "breached"
1660
- ? "text-red-700"
1661
- : seatTone === "approaching"
1662
- ? "text-amber-700"
1663
- : "text-gray-500"
1664
- }`}
1665
- >
1666
- {seatsRemainingText}
1667
- </p>
1668
- )}
1669
- </div>
1670
-
1671
- {typeof seatUsageDisplayPercent === "number" && (
1672
- <div className="mt-2.5">
1673
- <div
1674
- className="h-2.5 w-full overflow-hidden rounded-full bg-gray-100"
1675
- role="progressbar"
1676
- aria-label="Licensed seat usage"
1677
- aria-valuenow={seatUsageBarPercent}
1678
- aria-valuemin={0}
1679
- aria-valuemax={100}
1680
- aria-valuetext={seatUsageAriaValueText}
1681
- >
1682
- <div
1683
- className={`h-full rounded-full transition-all duration-300 ease-out ${
1684
- seatTone === "breached"
1685
- ? "bg-red-500"
1686
- : seatTone === "approaching"
1687
- ? "bg-amber-500"
1688
- : "bg-emerald-500"
1689
- }`}
1690
- style={{ width: `${seatUsageBarPercent}%` }}
1691
- />
1692
- </div>
1693
- <p className="mt-1.5 text-xs tabular-nums text-gray-500">
1694
- {seatUsageDisplayPercent}% of licensed seats in use
1695
- </p>
1696
- </div>
1697
- )}
1698
-
1699
- {seatTone !== "healthy" && canSeeLicenseAdmin && (
1700
- <div
1701
- role={seatTone === "breached" ? "alert" : "status"}
1702
- className={`mt-4 rounded-lg border p-3.5 ${
1703
- seatTone === "breached"
1704
- ? "border-red-200 bg-red-50"
1705
- : "border-amber-200 bg-amber-50"
1706
- }`}
1707
- >
1708
- <div className="flex items-start gap-2.5">
1709
- {/*
1710
- * Colour comes from className only. Never add a type
1711
- * prop here: Icon emits both the type colour and
1712
- * className into one class attribute, so two colour
1713
- * classes would land on one svg and Tailwind's
1714
- * emission order would pick the winner.
1715
- */}
1716
- <Icon
1717
- icon={
1718
- seatTone === "breached"
1719
- ? IconProp.Alert
1720
- : IconProp.ExclaimationCircle
1721
- }
1722
- className={`mt-0.5 h-4 w-4 shrink-0 ${
1723
- seatTone === "breached"
1724
- ? "text-red-600"
1725
- : "text-amber-600"
1726
- }`}
1727
- />
1728
- <div className="min-w-0 flex-1">
1729
- <h5
1730
- className={`text-sm font-semibold ${
1731
- seatTone === "breached"
1732
- ? "text-red-900"
1733
- : "text-amber-900"
1734
- }`}
1735
- >
1736
- {seatAdvisoryTitle}
1737
- </h5>
1738
- <p
1739
- className={`mt-1 text-xs leading-relaxed ${
1740
- seatTone === "breached"
1741
- ? "text-red-800"
1742
- : "text-amber-800"
1743
- }`}
1744
- >
1745
- {seatAdvisoryBody}
1746
- </p>
1747
- <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2">
1748
- <Button
1749
- title={
1750
- seatTone === "breached"
1751
- ? "Expand your license"
1752
- : "Request more seats"
1753
- }
1754
- icon={IconProp.Email}
1755
- buttonStyle={
1756
- seatTone === "breached"
1757
- ? ButtonStyleType.DANGER
1758
- : ButtonStyleType.PRIMARY
1759
- }
1760
- onClick={handleRequestMoreSeats}
1761
- className="!mt-0 md:!ml-0"
1762
- />
1763
- <span
1764
- className={`text-[11px] ${
1765
- seatTone === "breached"
1766
- ? "text-red-700"
1767
- : "text-amber-700"
1768
- }`}
1769
- >
1770
- Or email{" "}
1771
- <a
1772
- href={SALES_MAILTO_URL}
1773
- className={`font-medium underline ${
1774
- seatTone === "breached"
1775
- ? "text-red-800 hover:text-red-900"
1776
- : "text-amber-900 hover:text-amber-950"
1777
- }`}
1778
- >
1779
- {SALES_EMAIL}
1780
- </a>{" "}
1781
- directly.
1782
- </span>
1783
- </div>
1784
- </div>
1785
- </div>
1786
- </div>
1787
- )}
1788
-
1789
- <div className="mt-4 border-t border-gray-100 pt-3">
1790
- <p className="text-xs text-gray-500">
1791
- {userCountUpdatedAtText
1792
- ? `Last reported to OneUptime on ${userCountUpdatedAtText}.`
1793
- : "User count has not been reported to OneUptime yet. The first report will be sent within 24 hours."}
1794
- {isSeatLimitEnforced && (
1795
- <>
1796
- {" "}
1797
- This installation enforces the seat limit: users
1798
- above it cannot be invited, signed up or
1799
- provisioned.
1800
- </>
1801
- )}
1802
- {/*
1803
- * Only pointed at somebody who can actually press it.
1804
- * The refresh button is master-admin only, so telling
1805
- * everyone else to use it would be an instruction they
1806
- * cannot follow.
1807
- */}
1808
- {isSeatLimitEnforced && canManageLicense && (
1809
- <>
1810
- {" "}
1811
- Bought more seats? Use &quot;Refresh license&quot;
1812
- below to apply the new limit without waiting for the
1813
- next daily report.
1814
- </>
1815
- )}
1816
- </p>
1817
- </div>
1818
- </section>
1819
- )}
1820
-
1821
- {showLicenseDetails && licenseInstances.length > 0 && (
1822
- <section
1823
- aria-labelledby="edition-instances-heading"
1824
- className="rounded-xl border border-gray-200 bg-white p-5"
1825
- >
1826
- <div className="flex items-center justify-between gap-3">
1827
- <h4
1828
- id="edition-instances-heading"
1829
- className="text-sm font-semibold text-gray-900"
1830
- >
1831
- Instances on this license
1832
- </h4>
1833
- <span className="shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium tabular-nums text-gray-600">
1834
- {licenseInstances.length}{" "}
1835
- {licenseInstances.length === 1
1836
- ? "instance"
1837
- : "instances"}
1838
- </span>
1839
- </div>
1840
- <p className="mt-1 text-xs leading-relaxed text-gray-500">
1841
- Use the same license key on every instance you deploy
1842
- (staging, production, and so on). Each instance reports
1843
- its usage and the version it runs once a day.
1844
- </p>
1845
- {licenseInstances.length > 1 && (
1846
- <p className="mt-1.5 text-xs leading-relaxed text-gray-500">
1847
- {instanceOverlapText}
1848
- </p>
1849
- )}
1850
- <ul className="mt-3 divide-y divide-gray-100 overflow-hidden rounded-lg border border-gray-200">
1851
- {licenseInstances.map(
1852
- (
1853
- instance: EnterpriseLicenseInstanceSummary,
1854
- index: number,
1855
- ) => {
1856
- const isThisInstance: boolean =
1857
- Boolean(thisInstanceId) &&
1858
- instance.instanceId === thisInstanceId;
1859
-
1860
- /*
1861
- * The list is a snapshot of what each instance last
1862
- * reported, refreshed at most once a day. For the
1863
- * instance serving this page the server just told us
1864
- * what it is running right now, so prefer that —
1865
- * otherwise this row contradicts the "This
1866
- * installation" card above it for up to a day after
1867
- * an upgrade. hasComparableVersion keeps a dev build
1868
- * from rendering "vunknown".
1869
- */
1870
- const displayVersion: string | null =
1871
- isThisInstance && hasComparableVersion
1872
- ? currentVersion
1873
- : instance.version;
1874
-
1875
- /*
1876
- * Per-instance, so an admin can see which of their
1877
- * deployments are lagging rather than only the one
1878
- * they happen to be signed in to.
1879
- */
1880
- const isInstanceOutdated: boolean =
1881
- VersionUtil.isUpdateAvailable({
1882
- currentVersion: displayVersion,
1883
- latestVersion: latestVersion,
1884
- });
1885
-
1886
- return (
1887
- <li
1888
- key={instance.instanceId || index}
1889
- className="flex items-center justify-between gap-4 bg-white px-3 py-2.5 transition-colors hover:bg-gray-50"
1890
- >
1891
- <div className="min-w-0 flex-1">
1892
- {/*
1893
- * Wraps rather than squeezing: both pills are
1894
- * shrink-0, so without this a long hostname on
1895
- * a narrow modal absorbs all the overflow and
1896
- * truncates to a few characters.
1897
- */}
1898
- <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
1899
- <span
1900
- className="min-w-0 max-w-full truncate text-sm font-medium text-gray-900"
1901
- title={instance.host || "Unknown host"}
1902
- >
1903
- {instance.host || "Unknown host"}
1904
- </span>
1905
- {isThisInstance && (
1906
- <span className="shrink-0 rounded-full bg-indigo-50 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-indigo-700">
1907
- This instance
1908
- </span>
1909
- )}
1910
- {displayVersion && (
1911
- <span
1912
- className={`shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-medium tabular-nums ${
1913
- isInstanceOutdated
1914
- ? "bg-amber-50 text-amber-800"
1915
- : "bg-gray-100 text-gray-600"
1916
- }`}
1917
- title={
1918
- isInstanceOutdated
1919
- ? `Running v${displayVersion}. OneUptime v${latestVersion} is available.`
1920
- : `Running v${displayVersion}.`
1921
- }
1922
- >
1923
- v{displayVersion}
1924
- </span>
1925
- )}
1926
- </div>
1927
- <p className="mt-0.5 truncate text-xs text-gray-500">
1928
- {formatInstanceReportedAt(
1929
- instance.lastReportedAt,
1930
- )}
1931
- {isInstanceOutdated
1932
- ? " Update available."
1933
- : ""}
1934
- </p>
1935
- </div>
1936
- <div className="flex shrink-0 items-baseline justify-end gap-1">
1937
- {typeof instance.userCount === "number" ? (
1938
- <>
1939
- <span className="text-sm font-medium tabular-nums text-gray-900">
1940
- {instance.userCount.toLocaleString()}
1941
- </span>
1942
- <span className="text-xs text-gray-500">
1943
- {instance.userCount === 1
1944
- ? "user"
1945
- : "users"}
1946
- </span>
1947
- </>
1948
- ) : (
1949
- <span className="text-sm text-gray-400">
1950
- —
1951
- </span>
1952
- )}
1953
- </div>
1954
- </li>
1955
- );
1956
- },
1957
- )}
1958
- </ul>
1959
- </section>
1960
- )}
1961
-
1962
- {!configError &&
1963
- !isConfigLoading &&
1964
- !licenseValid &&
1965
- globalConfig?.enterpriseLicenseKey && (
1966
- <div className="rounded-xl border border-red-200 bg-red-50 p-4">
1967
- <p className="text-sm font-semibold text-red-900">
1968
- License validation required
1969
- </p>
1970
- <p className="mt-1 text-xs leading-relaxed text-red-800">
1971
- The stored license information could not be verified.
1972
- Please validate the license key again.
1973
- </p>
1974
- </div>
1975
- )}
1976
-
1977
- {showLicenseAdminRequiredNotice && (
1978
- <section className="rounded-xl border border-amber-200 bg-amber-50 p-5">
1979
- <h4 className="text-sm font-semibold text-amber-900">
1980
- A master admin has to activate this license
1981
- </h4>
1982
- <p className="mt-1 text-xs leading-relaxed text-amber-800">
1983
- This installation does not have a valid enterprise
1984
- license. Ask a master admin of this OneUptime installation
1985
- to enter or refresh the license key from this dialog.
1986
- </p>
1987
- </section>
1988
- )}
1989
-
1990
- {!configError && showLicenseKeyInput && (
1991
- <section className="rounded-xl border border-gray-200 bg-white p-5">
1992
- <div className="flex items-start justify-between gap-4">
1993
- <div className="min-w-0">
1994
- <label
1995
- htmlFor="enterprise-license-key"
1996
- className="text-sm font-semibold text-gray-900"
1997
- >
1998
- {isChangingLicense
1999
- ? "New license key"
2000
- : "License key"}
2001
- </label>
2002
- <p className="mt-0.5 text-xs leading-relaxed text-gray-500">
2003
- {licenseKeyHelperText}
2004
- {!isChangingLicense && (
2005
- <>
2006
- {" "}
2007
- <a
2008
- href={SALES_MAILTO_URL}
2009
- className="font-medium text-indigo-600 hover:text-indigo-700"
2010
- >
2011
- {SALES_EMAIL}
2012
- </a>
2013
- .
2014
- </>
2015
- )}
2016
- </p>
2017
- </div>
2018
- {isChangingLicense && (
2019
- <Button
2020
- title="Cancel"
2021
- buttonStyle={ButtonStyleType.NORMAL}
2022
- onClick={handleCancelChangingLicense}
2023
- className="!mt-0 shrink-0 md:!ml-0"
2024
- />
2025
- )}
2026
- </div>
2027
- <div className="mt-3">
2028
- <Input
2029
- id="enterprise-license-key"
2030
- value={licenseKeyInput}
2031
- onChange={(value: string) => {
2032
- setLicenseKeyInput(value);
2033
- licenseInputEditedRef.current = true;
2034
- }}
2035
- placeholder="Enter your enterprise license key"
2036
- disableSpellCheck={true}
2037
- />
2038
- </div>
2039
- {validationError && (
2040
- <div className="mt-3">
2041
- <Alert
2042
- type={AlertType.DANGER}
2043
- title={validationError}
2044
- />
2045
- </div>
2046
- )}
2047
- </section>
2048
- )}
2049
-
2050
- {showEnterpriseFeatureList && (
2051
- <section
2052
- aria-labelledby="edition-features-heading"
2053
- className="rounded-xl border border-indigo-100 bg-gradient-to-br from-indigo-50 via-white to-white p-5"
2054
- >
2055
- <h4
2056
- id="edition-features-heading"
2057
- className="text-sm font-semibold text-indigo-900"
2058
- >
2059
- What your license unlocks
2060
- </h4>
2061
- <p className="mt-0.5 text-xs text-indigo-700">
2062
- {canManageLicense
2063
- ? "Validate your key above to turn these on immediately."
2064
- : "A master admin can activate the license to turn these on."}
2065
- </p>
2066
- <ul className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2">
2067
- {enterpriseFeatures.map(
2068
- (feature: string, index: number) => {
2069
- return (
2070
- <li
2071
- key={index}
2072
- className="flex items-start gap-2 rounded-lg border border-gray-100 bg-white px-3 py-2.5"
2073
- >
2074
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-indigo-50">
2075
- <Icon
2076
- icon={IconProp.Check}
2077
- className="h-3 w-3 text-indigo-600"
2078
- />
2079
- </span>
2080
- <span className="text-xs leading-snug text-gray-700">
2081
- {feature}
2082
- </span>
2083
- </li>
2084
- );
2085
- },
2086
- )}
2087
- </ul>
2088
- </section>
2089
- )}
2090
- </>
2091
- ) : (
2092
- <>
2093
- {versionCardElement}
2094
- <p>
2095
- You are running the Community Edition of OneUptime. Here is a
2096
- quick comparison to help you decide if Enterprise is the right
2097
- fit for your team.
2098
- </p>
2099
- <div className="grid gap-4 md:grid-cols-2">
2100
- <div className="rounded-xl border border-gray-200 bg-white p-4">
2101
- <h4 className="text-sm font-semibold text-gray-900">
2102
- Community Edition
2103
- </h4>
2104
- <ul className="mt-3 space-y-2 text-sm text-gray-600">
2105
- {communityFeatures.map(
2106
- (feature: string, index: number) => {
2107
- return (
2108
- <li key={index} className="flex items-start gap-2">
2109
- <Icon
2110
- icon={IconProp.Check}
2111
- className="mt-0.5 h-3 w-3 shrink-0 text-gray-400"
2112
- />
2113
- <span className="leading-snug">{feature}</span>
2114
- </li>
2115
- );
2116
- },
2117
- )}
2118
- </ul>
2119
- <p className="mt-3 text-xs text-gray-500">
2120
- Best for small teams experimenting with reliability
2121
- workflows.
2122
- </p>
2123
- </div>
2124
- <div className="rounded-xl border border-indigo-100 bg-gradient-to-br from-indigo-50 via-white to-white p-4">
2125
- <h4 className="text-sm font-semibold text-indigo-900">
2126
- Enterprise Edition
2127
- </h4>
2128
- <ul className="mt-3 space-y-2 text-sm text-indigo-900">
2129
- {enterpriseFeatures.map(
2130
- (feature: string, index: number) => {
2131
- return (
2132
- <li key={index} className="flex items-start gap-2">
2133
- <Icon
2134
- icon={IconProp.Check}
2135
- className="mt-0.5 h-3 w-3 shrink-0 text-indigo-600"
2136
- />
2137
- <span className="leading-snug">{feature}</span>
2138
- </li>
2139
- );
2140
- },
2141
- )}
2142
- </ul>
2143
- <p className="mt-3 text-xs text-indigo-700">
2144
- Everything in Community plus white-glove onboarding,
2145
- enterprise SLAs, and a partner dedicated to your
2146
- reliability goals.
2147
- </p>
2148
- </div>
2149
- </div>
2150
- <p className="text-xs text-gray-500">
2151
- Ready to unlock enterprise capabilities? Click &quot;Talk to
2152
- Sales&quot; to start the conversation.
2153
- </p>
2154
- </>
2155
- )}
2156
- </div>
2157
- </Modal>
1761
+ {LicenseManager ? (
1762
+ <LicenseManager
1763
+ license={licenseSnapshot}
1764
+ isDialogOpen={isDialogOpen}
1765
+ reloadLicense={fetchGlobalConfig}
1766
+ applySeatEnforcement={applySeatEnforcementPayload}
1767
+ renderDialog={renderDialog}
1768
+ />
1769
+ ) : (
1770
+ renderDialog({})
2158
1771
  )}
2159
1772
  </>
2160
1773
  );