@oneuptime/common 13.0.8 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/AuditLog.ts +23 -15
- package/Models/DatabaseModels/GlobalConfig.ts +24 -0
- package/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/Monitor.ts +3 -2
- package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Scripts/generate-service-worker.js +65 -4
- package/Server/API/BaseAPI.ts +26 -5
- package/Server/API/GlobalConfigAPI.ts +228 -462
- package/Server/API/ProjectOIDC.ts +25 -16
- package/Server/API/ProjectSSO.ts +24 -16
- package/Server/API/StatusPageAPI.ts +100 -42
- package/Server/API/TeamMemberAPI.ts +10 -1
- package/Server/Enterprise/EnterpriseEdition.ts +649 -0
- package/Server/Enterprise/EnterpriseFeature.ts +48 -0
- package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
- package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
- package/Server/EnvironmentConfig.ts +147 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/UserAuthorization.ts +38 -16
- package/Server/Services/AuditLogService.ts +84 -1079
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/EnterpriseLicenseService.ts +38 -0
- package/Server/Services/GlobalConfigService.ts +80 -1
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorProbeService.ts +116 -20
- package/Server/Services/MonitorService.ts +59 -6
- package/Server/Services/MonitorStatusTimelineService.ts +284 -0
- package/Server/Services/MonitorTemplateService.ts +26 -0
- package/Server/Services/ProjectService.ts +284 -0
- package/Server/Services/RumSessionPinService.ts +25 -6
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Services/StatusPagePrivateUserService.ts +7 -3
- package/Server/Services/StatusPageService.ts +78 -1
- package/Server/Services/TeamMemberService.ts +11 -0
- package/Server/Services/TeamService.ts +10 -0
- package/Server/Services/UserService.ts +10 -17
- package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
- package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
- package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
- package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
- package/Server/Types/Markdown.ts +41 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
- package/Server/Utils/EditionEnforcement.ts +233 -0
- package/Server/Utils/FrontendEnvironment.ts +41 -2
- package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
- package/Server/Utils/Monitor/AffectedResourceList.ts +148 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +720 -465
- package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
- package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
- package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
- package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
- package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
- package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
- package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
- package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
- package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
- package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
- package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
- package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
- package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
- package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
- package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
- package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
- package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
- package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
- package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
- package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
- package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
- package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
- package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
- package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
- package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
- package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
- package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
- package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
- package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
- package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
- package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
- package/Tests/Server/Services/AuditLogService.test.ts +460 -990
- package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
- package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
- package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
- package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
- package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
- package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
- package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
- package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
- package/Tests/Server/Types/Markdown.test.ts +183 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
- package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
- package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
- package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
- package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +1339 -96
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
- package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
- package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
- package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
- package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
- package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
- package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
- package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
- package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
- package/Tests/Utils/HostAddressUtil.test.ts +138 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
- package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
- package/Types/API/Hostname.ts +63 -16
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
- package/Types/Database/CanonicalModelInstance.ts +102 -0
- package/Types/Database/TableColumn.ts +20 -6
- package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
- package/Types/IP/IPv6.ts +6 -1
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Monitor/MonitorStep.ts +6 -1
- package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
- package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/ModelList/ModelList.tsx +11 -1
- package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
- package/UI/esbuild-enterprise.js +263 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
- package/Utils/HostAddressUtil.ts +94 -0
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
- package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
- package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
- package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/API/BaseAPI.js +26 -5
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectOIDC.js +24 -16
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
- package/build/dist/Server/API/ProjectSSO.js +24 -16
- package/build/dist/Server/API/ProjectSSO.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +93 -43
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +8 -1
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
- package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +84 -2
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AuditLogService.js +71 -740
- package/build/dist/Server/Services/AuditLogService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/GlobalConfigService.js +63 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +90 -17
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +48 -6
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +214 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +24 -6
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +71 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +10 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +9 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -16
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +31 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
- package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
- package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
- package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
- package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +67 -0
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +585 -367
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +59 -11
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
- package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
- package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
- package/build/dist/Types/Database/TableColumn.js +19 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
- package/build/dist/Types/IP/IPv6.js +6 -1
- package/build/dist/Types/IP/IPv6.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +5 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
- package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
- package/build/dist/Utils/HostAddressUtil.js +80 -0
- package/build/dist/Utils/HostAddressUtil.js.map +1 -0
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
- package/jest.config.json +3 -1
- package/package.json +1 -1
- package/test-setup.sh +103 -34
- package/tsconfig.json +13 -0
- package/Server/API/EnterpriseLicenseAPI.ts +0 -925
- package/Server/API/TeamComplianceAPI.ts +0 -158
- package/Server/Middleware/SCIMAuthorization.ts +0 -141
- package/Server/Services/TeamComplianceService.ts +0 -733
- package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
- package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
- package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
- package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
- package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
- package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
- package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
- package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
- package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
- package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
- package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
- package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
- package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
- package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
- package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
- package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
- package/build/dist/Server/Services/TeamComplianceService.js +0 -537
- package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
- 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
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
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
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
if (!item || typeof item !== "object") {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
106
|
+
type LicenseStatus = "valid" | "grace" | "expired" | "missing" | "invalid";
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
type ParseLicenseStatusFunction = (value: unknown) => LicenseStatus | null;
|
|
83
109
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
123
|
+
return null;
|
|
113
124
|
};
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
return
|
|
203
|
-
}
|
|
208
|
+
const isMasterAdmin: boolean = useMemo(() => {
|
|
209
|
+
return Boolean(UserUtil.isMasterAdmin());
|
|
210
|
+
}, []);
|
|
204
211
|
|
|
205
212
|
/*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
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);
|
|
236
|
+
|
|
237
|
+
/*
|
|
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.
|
|
@@ -296,24 +340,36 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
296
340
|
] as number;
|
|
297
341
|
}
|
|
298
342
|
|
|
299
|
-
if (payload["userCountUpdatedAt"]) {
|
|
300
|
-
configModel.enterpriseLicenseUserCountUpdatedAt =
|
|
301
|
-
OneUptimeDate.fromString(payload["userCountUpdatedAt"] as string);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
343
|
setGlobalConfig(configModel);
|
|
305
|
-
|
|
306
|
-
setThisInstanceId(
|
|
307
|
-
typeof payload["instanceId"] === "string"
|
|
308
|
-
? payload["instanceId"]
|
|
309
|
-
: "",
|
|
310
|
-
);
|
|
344
|
+
setLicensePayload(payload);
|
|
311
345
|
setServerLicenseValid(
|
|
312
346
|
typeof payload["licenseValid"] === "boolean"
|
|
313
347
|
? payload["licenseValid"]
|
|
314
348
|
: null,
|
|
315
349
|
);
|
|
316
|
-
setIsEvaluationLicense(
|
|
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
|
+
);
|
|
317
373
|
|
|
318
374
|
setCurrentVersion(
|
|
319
375
|
typeof payload["currentVersion"] === "string"
|
|
@@ -338,15 +394,16 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
338
394
|
setIsUpdateAvailable(payload["isUpdateAvailable"] === true);
|
|
339
395
|
setIsUpdateCheckDisabled(payload["isUpdateCheckDisabled"] === true);
|
|
340
396
|
applySeatEnforcementPayload(payload);
|
|
341
|
-
|
|
342
|
-
if (!licenseInputEditedRef.current) {
|
|
343
|
-
setLicenseKeyInput(configModel.enterpriseLicenseKey || "");
|
|
344
|
-
}
|
|
345
397
|
} catch (err) {
|
|
346
398
|
setGlobalConfig(null);
|
|
347
|
-
|
|
399
|
+
setLicensePayload(null);
|
|
348
400
|
setServerLicenseValid(null);
|
|
349
401
|
setIsEvaluationLicense(false);
|
|
402
|
+
setLicenseStatus(null);
|
|
403
|
+
setLicenseVerification("");
|
|
404
|
+
setGraceReason("");
|
|
405
|
+
setGraceEndsAt("");
|
|
406
|
+
setLicenseMessage("");
|
|
350
407
|
setCurrentVersion("");
|
|
351
408
|
setLatestVersion("");
|
|
352
409
|
setLatestVersionPublishedAt("");
|
|
@@ -369,7 +426,7 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
369
426
|
* opened rather than adding a request to every page load.
|
|
370
427
|
*/
|
|
371
428
|
useEffect(() => {
|
|
372
|
-
if (!IS_ENTERPRISE_EDITION) {
|
|
429
|
+
if (BILLING_ENABLED || !IS_ENTERPRISE_EDITION) {
|
|
373
430
|
return;
|
|
374
431
|
}
|
|
375
432
|
|
|
@@ -404,6 +461,88 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
404
461
|
serverLicenseValid,
|
|
405
462
|
]);
|
|
406
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
|
+
|
|
407
546
|
const licenseExpiresAtText: string | null = useMemo(() => {
|
|
408
547
|
if (!globalConfig?.enterpriseLicenseExpiresAt) {
|
|
409
548
|
return null;
|
|
@@ -550,22 +689,6 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
550
689
|
return currentUserCount;
|
|
551
690
|
}, [isSeatLimitEnforced, enforcedSeatsInUse, currentUserCount]);
|
|
552
691
|
|
|
553
|
-
const userCountUpdatedAtText: string | null = useMemo(() => {
|
|
554
|
-
if (!globalConfig?.enterpriseLicenseUserCountUpdatedAt) {
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const reportedAt: Date = OneUptimeDate.fromString(
|
|
559
|
-
globalConfig.enterpriseLicenseUserCountUpdatedAt,
|
|
560
|
-
);
|
|
561
|
-
|
|
562
|
-
if (Number.isNaN(reportedAt.getTime())) {
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
return reportedAt.toLocaleString();
|
|
567
|
-
}, [globalConfig?.enterpriseLicenseUserCountUpdatedAt]);
|
|
568
|
-
|
|
569
692
|
const isUserLimitBreached: boolean = useMemo(() => {
|
|
570
693
|
if (!licenseValid) {
|
|
571
694
|
return false;
|
|
@@ -619,52 +742,6 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
619
742
|
return rounded;
|
|
620
743
|
}, [rawUserUsagePercent]);
|
|
621
744
|
|
|
622
|
-
/*
|
|
623
|
-
* The displayed percent clamped to 0-100. Drives the bar width and
|
|
624
|
-
* aria-valuenow, so the fill can never overflow its track and aria-valuenow
|
|
625
|
-
* can never exceed aria-valuemax. Derived from the displayed percent rather
|
|
626
|
-
* than the raw ratio so the fill always agrees with the caption beneath it:
|
|
627
|
-
* 599/600 reads 99% and fills to 99%, not to a visually full 100%. The true
|
|
628
|
-
* figure for a breach goes in aria-valuetext.
|
|
629
|
-
*/
|
|
630
|
-
const seatUsageBarPercent: number = useMemo(() => {
|
|
631
|
-
if (seatUsageDisplayPercent === null) {
|
|
632
|
-
return 0;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return Math.min(100, Math.max(0, seatUsageDisplayPercent));
|
|
636
|
-
}, [seatUsageDisplayPercent]);
|
|
637
|
-
|
|
638
|
-
const seatsRemaining: number | null = useMemo(() => {
|
|
639
|
-
if (typeof userLimit !== "number" || userLimit <= 0) {
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
if (typeof effectiveUserCount !== "number") {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
return userLimit - effectiveUserCount;
|
|
648
|
-
}, [userLimit, effectiveUserCount]);
|
|
649
|
-
|
|
650
|
-
const seatsRemainingText: string = useMemo(() => {
|
|
651
|
-
if (typeof seatsRemaining !== "number") {
|
|
652
|
-
return "";
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (seatsRemaining > 0) {
|
|
656
|
-
return `${seatsRemaining.toLocaleString()} ${
|
|
657
|
-
seatsRemaining === 1 ? "seat" : "seats"
|
|
658
|
-
} remaining`;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
if (seatsRemaining === 0) {
|
|
662
|
-
return "No seats remaining";
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
return `${Math.abs(seatsRemaining).toLocaleString()} over limit`;
|
|
666
|
-
}, [seatsRemaining]);
|
|
667
|
-
|
|
668
745
|
/*
|
|
669
746
|
* Total and mutually exclusive. isUserLimitBreached is tested first, so
|
|
670
747
|
* reaching the >= 90 test structurally guarantees the limit is not exceeded
|
|
@@ -708,107 +785,47 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
708
785
|
seatUsageDisplayPercent,
|
|
709
786
|
]);
|
|
710
787
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
*/
|
|
715
|
-
const seatAdvisoryTitle: string = useMemo(() => {
|
|
716
|
-
if (seatTone === "breached") {
|
|
717
|
-
const over: number = Math.abs(seatsRemaining || 0);
|
|
718
|
-
|
|
719
|
-
/*
|
|
720
|
-
* Reachable now that "every seat taken" is a breach in its own right:
|
|
721
|
-
* an installation sitting exactly on its limit is not "0 users over".
|
|
722
|
-
*/
|
|
723
|
-
if (over === 0) {
|
|
724
|
-
return "Every licensed seat is in use";
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
return over === 1
|
|
728
|
-
? "1 user over your licensed seats"
|
|
729
|
-
: `${over.toLocaleString()} users over your licensed seats`;
|
|
788
|
+
const editionName: string = useMemo(() => {
|
|
789
|
+
if (!IS_ENTERPRISE_EDITION) {
|
|
790
|
+
return "Community Edition";
|
|
730
791
|
}
|
|
731
792
|
|
|
732
|
-
if (
|
|
733
|
-
|
|
734
|
-
return "Every licensed seat is in use";
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
return seatsRemaining === 1
|
|
738
|
-
? "Only 1 seat left on your license"
|
|
739
|
-
: `Only ${(seatsRemaining || 0).toLocaleString()} seats left on your license`;
|
|
793
|
+
if (isConfigLoading) {
|
|
794
|
+
return "Enterprise Edition (Checking...)";
|
|
740
795
|
}
|
|
741
796
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const limitText: string = (userLimit || 0).toLocaleString();
|
|
747
|
-
const inUse: string = `${(effectiveUserCount || 0).toLocaleString()} of ${limitText}`;
|
|
748
|
-
|
|
749
|
-
if (seatTone === "breached") {
|
|
750
|
-
/*
|
|
751
|
-
* When the limit is being enforced this is no longer advice, it is a
|
|
752
|
-
* description of what the installation is already doing to invitations
|
|
753
|
-
* and signups - so it says so plainly rather than suggesting an upgrade
|
|
754
|
-
* as though adding people were still an option.
|
|
755
|
-
*/
|
|
756
|
-
if (isSeatLimitEnforced && !canAddMoreUsers) {
|
|
757
|
-
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.`;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
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)";
|
|
761
801
|
}
|
|
762
802
|
|
|
763
|
-
if (
|
|
764
|
-
return
|
|
803
|
+
if (isExpiredGrace) {
|
|
804
|
+
return "Enterprise Edition (License Expired, Grace Period)";
|
|
765
805
|
}
|
|
766
806
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
seatTone,
|
|
770
|
-
effectiveUserCount,
|
|
771
|
-
userLimit,
|
|
772
|
-
isSeatLimitEnforced,
|
|
773
|
-
canAddMoreUsers,
|
|
774
|
-
]);
|
|
775
|
-
|
|
776
|
-
const seatUsageAriaValueText: string = useMemo(() => {
|
|
777
|
-
if (typeof seatUsageDisplayPercent !== "number") {
|
|
778
|
-
return "";
|
|
807
|
+
if (licenseValid) {
|
|
808
|
+
return "Enterprise Edition";
|
|
779
809
|
}
|
|
780
810
|
|
|
781
|
-
if (
|
|
782
|
-
return
|
|
811
|
+
if (licenseStatus === "expired") {
|
|
812
|
+
return "Enterprise Edition (License Expired)";
|
|
783
813
|
}
|
|
784
814
|
|
|
785
|
-
if (
|
|
786
|
-
return
|
|
815
|
+
if (licenseStatus === "invalid") {
|
|
816
|
+
return "Enterprise Edition (License Invalid)";
|
|
787
817
|
}
|
|
788
818
|
|
|
789
|
-
return
|
|
819
|
+
return "Enterprise Edition (License Required)";
|
|
790
820
|
}, [
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
821
|
+
isConfigLoading,
|
|
822
|
+
licenseValid,
|
|
823
|
+
licenseStatus,
|
|
824
|
+
isUnlicensedTrial,
|
|
825
|
+
isExpiredGrace,
|
|
826
|
+
graceDaysLeftText,
|
|
796
827
|
]);
|
|
797
828
|
|
|
798
|
-
const editionName: string = useMemo(() => {
|
|
799
|
-
if (!IS_ENTERPRISE_EDITION) {
|
|
800
|
-
return "Community Edition";
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
if (isConfigLoading) {
|
|
804
|
-
return "Enterprise Edition (Checking...)";
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return licenseValid
|
|
808
|
-
? "Enterprise Edition"
|
|
809
|
-
: "Enterprise Edition (License Required)";
|
|
810
|
-
}, [isConfigLoading, licenseValid]);
|
|
811
|
-
|
|
812
829
|
const indicatorColor: string = useMemo(() => {
|
|
813
830
|
if (!IS_ENTERPRISE_EDITION) {
|
|
814
831
|
return "bg-indigo-400";
|
|
@@ -826,23 +843,39 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
826
843
|
return "bg-red-500";
|
|
827
844
|
}
|
|
828
845
|
|
|
829
|
-
if (seatTone === "approaching") {
|
|
846
|
+
if (seatTone === "approaching" || licenseStatus === "grace") {
|
|
830
847
|
return "bg-amber-500";
|
|
831
848
|
}
|
|
832
849
|
|
|
833
850
|
return "bg-emerald-500";
|
|
834
|
-
}, [isConfigLoading, licenseValid, seatTone]);
|
|
851
|
+
}, [isConfigLoading, licenseValid, seatTone, licenseStatus]);
|
|
835
852
|
|
|
836
853
|
const ctaLabel: string = useMemo(() => {
|
|
837
854
|
if (!IS_ENTERPRISE_EDITION) {
|
|
838
|
-
return "Learn more";
|
|
855
|
+
return showEditionMismatchNotice ? "Action needed" : "Learn more";
|
|
839
856
|
}
|
|
840
857
|
|
|
841
858
|
if (isConfigLoading) {
|
|
842
859
|
return "Checking";
|
|
843
860
|
}
|
|
844
861
|
|
|
862
|
+
if (isUnlicensedTrial) {
|
|
863
|
+
return "Add license";
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (isExpiredGrace) {
|
|
867
|
+
return "Renew license";
|
|
868
|
+
}
|
|
869
|
+
|
|
845
870
|
if (!licenseValid) {
|
|
871
|
+
if (licenseStatus === "expired") {
|
|
872
|
+
return "Renew license";
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (licenseStatus === "invalid") {
|
|
876
|
+
return "Fix license";
|
|
877
|
+
}
|
|
878
|
+
|
|
846
879
|
return "Validate license";
|
|
847
880
|
}
|
|
848
881
|
|
|
@@ -855,21 +888,36 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
855
888
|
}
|
|
856
889
|
|
|
857
890
|
return "View details";
|
|
858
|
-
}, [
|
|
891
|
+
}, [
|
|
892
|
+
isConfigLoading,
|
|
893
|
+
licenseValid,
|
|
894
|
+
licenseStatus,
|
|
895
|
+
seatTone,
|
|
896
|
+
isUnlicensedTrial,
|
|
897
|
+
isExpiredGrace,
|
|
898
|
+
showEditionMismatchNotice,
|
|
899
|
+
]);
|
|
859
900
|
|
|
860
901
|
const pillTone: PillTone = useMemo(() => {
|
|
861
|
-
if (!IS_ENTERPRISE_EDITION
|
|
902
|
+
if (!IS_ENTERPRISE_EDITION) {
|
|
903
|
+
return showEditionMismatchNotice ? "warning" : "normal";
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (isConfigLoading) {
|
|
862
907
|
return "normal";
|
|
863
908
|
}
|
|
864
909
|
|
|
865
910
|
if (
|
|
866
911
|
seatTone === "breached" ||
|
|
867
|
-
(!licenseValid &&
|
|
912
|
+
(!licenseValid &&
|
|
913
|
+
(Boolean(globalConfig?.enterpriseLicenseKey) ||
|
|
914
|
+
licenseStatus === "expired" ||
|
|
915
|
+
licenseStatus === "invalid"))
|
|
868
916
|
) {
|
|
869
917
|
return "alerted";
|
|
870
918
|
}
|
|
871
919
|
|
|
872
|
-
if (seatTone === "approaching") {
|
|
920
|
+
if (seatTone === "approaching" || licenseStatus === "grace") {
|
|
873
921
|
return "warning";
|
|
874
922
|
}
|
|
875
923
|
|
|
@@ -877,8 +925,10 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
877
925
|
}, [
|
|
878
926
|
isConfigLoading,
|
|
879
927
|
licenseValid,
|
|
928
|
+
licenseStatus,
|
|
880
929
|
seatTone,
|
|
881
930
|
globalConfig?.enterpriseLicenseKey,
|
|
931
|
+
showEditionMismatchNotice,
|
|
882
932
|
]);
|
|
883
933
|
|
|
884
934
|
const modalDescription: string = useMemo(() => {
|
|
@@ -890,41 +940,20 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
890
940
|
return "Checking your license with OneUptime...";
|
|
891
941
|
}
|
|
892
942
|
|
|
893
|
-
if (
|
|
894
|
-
return "
|
|
943
|
+
if (isUnlicensedTrial) {
|
|
944
|
+
return "No license is installed yet. Enterprise features are available during the trial.";
|
|
895
945
|
}
|
|
896
946
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
/*
|
|
901
|
-
* The per-instance counts only exceed the unique total when someone actually
|
|
902
|
-
* appears on more than one instance, and the component cannot know whether
|
|
903
|
-
* they do — it receives per-instance counts and the unique total separately.
|
|
904
|
-
* So the summation is stated as a possibility, and is dropped entirely when
|
|
905
|
-
* there is no unique total to compare against. It names the unique user count
|
|
906
|
-
* rather than "the total above", which would be ambiguous next to a hero
|
|
907
|
-
* reading "119 / 120 seats".
|
|
908
|
-
*/
|
|
909
|
-
const instanceOverlapText: string = useMemo(() => {
|
|
910
|
-
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.`;
|
|
911
|
-
|
|
912
|
-
if (typeof currentUserCount !== "number") {
|
|
913
|
-
return countingRule;
|
|
947
|
+
if (isExpiredGrace) {
|
|
948
|
+
return "Your license has expired. Renew it before the grace period ends.";
|
|
914
949
|
}
|
|
915
950
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
} counted above.`;
|
|
919
|
-
}, [licenseInstances.length, currentUserCount]);
|
|
920
|
-
|
|
921
|
-
const licenseKeyHelperText: string = useMemo(() => {
|
|
922
|
-
if (isChangingLicense) {
|
|
923
|
-
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.";
|
|
924
953
|
}
|
|
925
954
|
|
|
926
|
-
return "
|
|
927
|
-
}, [
|
|
955
|
+
return "License, version, seat usage, and the instances covered by this key.";
|
|
956
|
+
}, [isConfigLoading, licenseValid, isUnlicensedTrial, isExpiredGrace]);
|
|
928
957
|
|
|
929
958
|
const communityFeatures: Array<string> = useMemo(() => {
|
|
930
959
|
return [
|
|
@@ -947,34 +976,18 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
947
976
|
];
|
|
948
977
|
}, []);
|
|
949
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
|
+
*/
|
|
950
983
|
const openDialog: () => void = () => {
|
|
951
984
|
setIsDialogOpen(true);
|
|
952
|
-
setValidationError("");
|
|
953
|
-
setSuccessMessage("");
|
|
954
985
|
|
|
955
986
|
void fetchGlobalConfig();
|
|
956
987
|
};
|
|
957
988
|
|
|
958
989
|
const closeDialog: () => void = () => {
|
|
959
990
|
setIsDialogOpen(false);
|
|
960
|
-
setValidationError("");
|
|
961
|
-
setSuccessMessage("");
|
|
962
|
-
setIsChangingLicense(false);
|
|
963
|
-
};
|
|
964
|
-
|
|
965
|
-
const handleStartChangingLicense: () => void = () => {
|
|
966
|
-
setIsChangingLicense(true);
|
|
967
|
-
setValidationError("");
|
|
968
|
-
setSuccessMessage("");
|
|
969
|
-
setLicenseKeyInput("");
|
|
970
|
-
licenseInputEditedRef.current = true;
|
|
971
|
-
};
|
|
972
|
-
|
|
973
|
-
const handleCancelChangingLicense: () => void = () => {
|
|
974
|
-
setIsChangingLicense(false);
|
|
975
|
-
setValidationError("");
|
|
976
|
-
licenseInputEditedRef.current = false;
|
|
977
|
-
setLicenseKeyInput(globalConfig?.enterpriseLicenseKey || "");
|
|
978
991
|
};
|
|
979
992
|
|
|
980
993
|
const handlePrimaryAction: () => void = () => {
|
|
@@ -985,181 +998,12 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
985
998
|
closeDialog();
|
|
986
999
|
};
|
|
987
1000
|
|
|
988
|
-
type BuildSeatExpansionMailtoFunction = () => string;
|
|
989
|
-
|
|
990
|
-
const buildSeatExpansionMailto: BuildSeatExpansionMailtoFunction =
|
|
991
|
-
(): string => {
|
|
992
|
-
const subject: string =
|
|
993
|
-
seatTone === "breached"
|
|
994
|
-
? "Expand OneUptime license - over seat limit"
|
|
995
|
-
: "Expand OneUptime license - nearly out of seats";
|
|
996
|
-
|
|
997
|
-
const body: string = [
|
|
998
|
-
"Hi OneUptime,",
|
|
999
|
-
"",
|
|
1000
|
-
"We would like to expand the seat count on our enterprise license.",
|
|
1001
|
-
"",
|
|
1002
|
-
`Company: ${globalConfig?.enterpriseCompanyName || "Not specified"}`,
|
|
1003
|
-
`Seats in use: ${(effectiveUserCount || 0).toLocaleString()} of ${(
|
|
1004
|
-
userLimit || 0
|
|
1005
|
-
).toLocaleString()}`,
|
|
1006
|
-
`Instances on this license: ${licenseInstances.length}`,
|
|
1007
|
-
].join("\n");
|
|
1008
|
-
|
|
1009
|
-
return `${SALES_MAILTO_URL}?subject=${encodeURIComponent(
|
|
1010
|
-
subject,
|
|
1011
|
-
)}&body=${encodeURIComponent(body)}`;
|
|
1012
|
-
};
|
|
1013
|
-
|
|
1014
|
-
const handleRequestMoreSeats: () => void = () => {
|
|
1015
|
-
if (typeof window !== "undefined") {
|
|
1016
|
-
window.location.href = buildSeatExpansionMailto();
|
|
1017
|
-
}
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
const runLicenseValidation: (
|
|
1021
|
-
key: string,
|
|
1022
|
-
setLoading: React.Dispatch<React.SetStateAction<boolean>>,
|
|
1023
|
-
) => Promise<void> = useCallback(
|
|
1024
|
-
async (
|
|
1025
|
-
key: string,
|
|
1026
|
-
setLoading: React.Dispatch<React.SetStateAction<boolean>>,
|
|
1027
|
-
): Promise<void> => {
|
|
1028
|
-
const trimmedKey: string = key.trim();
|
|
1029
|
-
|
|
1030
|
-
if (!trimmedKey) {
|
|
1031
|
-
setValidationError("Please enter a license key before validating.");
|
|
1032
|
-
setSuccessMessage("");
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
setValidationError("");
|
|
1037
|
-
setSuccessMessage("");
|
|
1038
|
-
setLoading(true);
|
|
1039
|
-
|
|
1040
|
-
try {
|
|
1041
|
-
const validationUrl: URL = URL.fromURL(APP_API_URL).addRoute(
|
|
1042
|
-
new Route("/global-config/license"),
|
|
1043
|
-
);
|
|
1044
|
-
|
|
1045
|
-
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
|
1046
|
-
await API.fetch<JSONObject>({
|
|
1047
|
-
method: HTTPMethod.POST,
|
|
1048
|
-
url: validationUrl,
|
|
1049
|
-
data: {
|
|
1050
|
-
licenseKey: trimmedKey,
|
|
1051
|
-
},
|
|
1052
|
-
});
|
|
1053
|
-
|
|
1054
|
-
if (!response.isSuccess()) {
|
|
1055
|
-
throw response;
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
const payload: JSONObject = response.data as JSONObject;
|
|
1059
|
-
|
|
1060
|
-
licenseInputEditedRef.current = false;
|
|
1061
|
-
setLicenseKeyInput((payload["licenseKey"] as string) || trimmedKey);
|
|
1062
|
-
setSuccessMessage("License validated successfully.");
|
|
1063
|
-
setIsChangingLicense(false);
|
|
1064
|
-
|
|
1065
|
-
await fetchGlobalConfig();
|
|
1066
|
-
} catch (err) {
|
|
1067
|
-
setValidationError(API.getFriendlyMessage(err));
|
|
1068
|
-
} finally {
|
|
1069
|
-
setLoading(false);
|
|
1070
|
-
}
|
|
1071
|
-
},
|
|
1072
|
-
[fetchGlobalConfig],
|
|
1073
|
-
);
|
|
1074
|
-
|
|
1075
|
-
const handleValidateClick: () => void = () => {
|
|
1076
|
-
if (isValidating) {
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
void runLicenseValidation(licenseKeyInput, setIsValidating);
|
|
1081
|
-
};
|
|
1082
|
-
|
|
1083
|
-
/*
|
|
1084
|
-
* Re-ask oneuptime.com about the license this installation already holds.
|
|
1085
|
-
*
|
|
1086
|
-
* The seat limit and the expiry are changed on oneuptime.com, and the daily
|
|
1087
|
-
* report job is what normally carries them here - so without this, an
|
|
1088
|
-
* administrator who has just bought seats waits up to a day before the
|
|
1089
|
-
* installation stops refusing invitations. Deliberately sends no key: the
|
|
1090
|
-
* server refreshes the stored one.
|
|
1091
|
-
*/
|
|
1092
|
-
const handleRefreshLicense: () => void = useCallback((): void => {
|
|
1093
|
-
if (isRefreshingLicense) {
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
const refresh: () => Promise<void> = async (): Promise<void> => {
|
|
1098
|
-
setValidationError("");
|
|
1099
|
-
setSuccessMessage("");
|
|
1100
|
-
setIsRefreshingLicense(true);
|
|
1101
|
-
|
|
1102
|
-
try {
|
|
1103
|
-
const refreshUrl: URL = URL.fromURL(APP_API_URL).addRoute(
|
|
1104
|
-
new Route("/global-config/license/refresh"),
|
|
1105
|
-
);
|
|
1106
|
-
|
|
1107
|
-
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
|
1108
|
-
await API.fetch<JSONObject>({
|
|
1109
|
-
method: HTTPMethod.POST,
|
|
1110
|
-
url: refreshUrl,
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
if (!response.isSuccess()) {
|
|
1114
|
-
throw response;
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
applySeatEnforcementPayload(response.data as JSONObject);
|
|
1118
|
-
|
|
1119
|
-
setSuccessMessage("License refreshed from OneUptime.");
|
|
1120
|
-
|
|
1121
|
-
/*
|
|
1122
|
-
* The POST already returned the new terms, but the modal renders from
|
|
1123
|
-
* what the GET reports - re-reading keeps one source of truth for the
|
|
1124
|
-
* whole dialog instead of two that can disagree.
|
|
1125
|
-
*/
|
|
1126
|
-
await fetchGlobalConfig();
|
|
1127
|
-
} catch (err) {
|
|
1128
|
-
setValidationError(API.getFriendlyMessage(err));
|
|
1129
|
-
} finally {
|
|
1130
|
-
setIsRefreshingLicense(false);
|
|
1131
|
-
}
|
|
1132
|
-
};
|
|
1133
|
-
|
|
1134
|
-
void refresh();
|
|
1135
|
-
}, [isRefreshingLicense, fetchGlobalConfig, applySeatEnforcementPayload]);
|
|
1136
|
-
|
|
1137
1001
|
const handleRetryFetch: () => void = () => {
|
|
1138
1002
|
if (!isConfigLoading) {
|
|
1139
1003
|
void fetchGlobalConfig();
|
|
1140
1004
|
}
|
|
1141
1005
|
};
|
|
1142
1006
|
|
|
1143
|
-
/*
|
|
1144
|
-
* Activating, replacing and refreshing the license are master-admin actions:
|
|
1145
|
-
* the license is instance-wide, and its seat limit is what UserService
|
|
1146
|
-
* enforces against, so being able to rewrite it is being able to raise the
|
|
1147
|
-
* ceiling on the whole installation. The server enforces this
|
|
1148
|
-
* (MasterAdminAuthorization on both POSTs); hiding the controls here just
|
|
1149
|
-
* stops everyone else being offered a button that can only fail.
|
|
1150
|
-
*
|
|
1151
|
-
* False for a signed-out visitor, which is what keeps the license key input
|
|
1152
|
-
* off the login page where this same component renders the edition pill.
|
|
1153
|
-
*/
|
|
1154
|
-
const canManageLicense: boolean = useMemo(() => {
|
|
1155
|
-
return Boolean(UserUtil.isMasterAdmin());
|
|
1156
|
-
}, []);
|
|
1157
|
-
|
|
1158
|
-
const showLicenseKeyInput: boolean =
|
|
1159
|
-
IS_ENTERPRISE_EDITION &&
|
|
1160
|
-
canManageLicense &&
|
|
1161
|
-
(!licenseValid || isChangingLicense);
|
|
1162
|
-
|
|
1163
1007
|
/*
|
|
1164
1008
|
* Someone who cannot fix the license still needs to be told why the
|
|
1165
1009
|
* installation is behaving like an unlicensed one, and who can fix it.
|
|
@@ -1171,14 +1015,21 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
1171
1015
|
!licenseValid &&
|
|
1172
1016
|
!canManageLicense;
|
|
1173
1017
|
|
|
1174
|
-
const shouldShowEnterpriseValidationButton: boolean = showLicenseKeyInput;
|
|
1175
|
-
|
|
1176
1018
|
/*
|
|
1177
1019
|
* Collapses the !configError && !isConfigLoading && licenseValid chain that
|
|
1178
|
-
* gates the stat strip, the seat card
|
|
1020
|
+
* gates the stat strip here, and the license manager's seat card, instances
|
|
1021
|
+
* card and footer actions.
|
|
1179
1022
|
*/
|
|
1180
1023
|
const showLicenseDetails: boolean =
|
|
1181
|
-
IS_ENTERPRISE_EDITION &&
|
|
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;
|
|
1182
1033
|
|
|
1183
1034
|
/*
|
|
1184
1035
|
* The evaluation notice rides along with the license details: it is only
|
|
@@ -1196,7 +1047,10 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
1196
1047
|
IS_ENTERPRISE_EDITION && licenseValid && isEvaluationLicense;
|
|
1197
1048
|
|
|
1198
1049
|
const showEnterpriseFeatureList: boolean =
|
|
1199
|
-
IS_ENTERPRISE_EDITION &&
|
|
1050
|
+
IS_ENTERPRISE_EDITION &&
|
|
1051
|
+
!configError &&
|
|
1052
|
+
!isConfigLoading &&
|
|
1053
|
+
(!licenseValid || isUnlicensedTrial);
|
|
1200
1054
|
|
|
1201
1055
|
/*
|
|
1202
1056
|
* Gated on neither edition nor licenseValid: which build you run, and
|
|
@@ -1208,40 +1062,6 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
1208
1062
|
const showVersionCard: boolean =
|
|
1209
1063
|
!configError && !isConfigLoading && Boolean(currentVersion);
|
|
1210
1064
|
|
|
1211
|
-
/*
|
|
1212
|
-
* GlobalConfigAPI returns userLimit and currentUserCount to anonymous callers
|
|
1213
|
-
* because the same route serves the signed-out login page, and gates only
|
|
1214
|
-
* licenseKey, token, instances and instanceId on isAuthenticatedUser. Gate
|
|
1215
|
-
* the sales ask on a field the server does redact, so a signed-out visitor is
|
|
1216
|
-
* never shown an administrator-targeted CTA.
|
|
1217
|
-
*/
|
|
1218
|
-
const canSeeLicenseAdmin: boolean = Boolean(
|
|
1219
|
-
globalConfig?.enterpriseLicenseKey,
|
|
1220
|
-
);
|
|
1221
|
-
|
|
1222
|
-
const modalSubmitButtonText: string | undefined = IS_ENTERPRISE_EDITION
|
|
1223
|
-
? shouldShowEnterpriseValidationButton
|
|
1224
|
-
? "Validate License"
|
|
1225
|
-
: undefined
|
|
1226
|
-
: "Talk to Sales";
|
|
1227
|
-
|
|
1228
|
-
const modalOnSubmit: (() => void) | undefined = IS_ENTERPRISE_EDITION
|
|
1229
|
-
? shouldShowEnterpriseValidationButton
|
|
1230
|
-
? handleValidateClick
|
|
1231
|
-
: undefined
|
|
1232
|
-
: handlePrimaryAction;
|
|
1233
|
-
|
|
1234
|
-
const modalIsLoading: boolean =
|
|
1235
|
-
IS_ENTERPRISE_EDITION && shouldShowEnterpriseValidationButton
|
|
1236
|
-
? isValidating
|
|
1237
|
-
: false;
|
|
1238
|
-
|
|
1239
|
-
const modalDisableSubmitButton: boolean | undefined = IS_ENTERPRISE_EDITION
|
|
1240
|
-
? shouldShowEnterpriseValidationButton
|
|
1241
|
-
? !licenseKeyInput.trim() || isValidating || isConfigLoading
|
|
1242
|
-
: undefined
|
|
1243
|
-
: false;
|
|
1244
|
-
|
|
1245
1065
|
const modalRightElement: ReactElement | undefined = showLicenseDetails ? (
|
|
1246
1066
|
<span
|
|
1247
1067
|
className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium ${
|
|
@@ -1265,41 +1085,12 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
1265
1085
|
? "Seat limit exceeded"
|
|
1266
1086
|
: seatTone === "approaching"
|
|
1267
1087
|
? "Seats nearly full"
|
|
1268
|
-
:
|
|
1088
|
+
: isExpiredGrace
|
|
1089
|
+
? "Grace period"
|
|
1090
|
+
: "License active"}
|
|
1269
1091
|
</span>
|
|
1270
1092
|
) : undefined;
|
|
1271
1093
|
|
|
1272
|
-
const modalLeftFooterElement: ReactElement | undefined =
|
|
1273
|
-
showLicenseDetails && !isChangingLicense && canManageLicense ? (
|
|
1274
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
1275
|
-
{/*
|
|
1276
|
-
* The seat limit and the expiry are changed on oneuptime.com and reach
|
|
1277
|
-
* this installation through the daily report job. This is the button
|
|
1278
|
-
* for the administrator who has just bought seats and would rather not
|
|
1279
|
-
* wait a day for the installation to stop refusing invitations.
|
|
1280
|
-
*/}
|
|
1281
|
-
<Button
|
|
1282
|
-
title="Refresh license"
|
|
1283
|
-
icon={IconProp.Refresh}
|
|
1284
|
-
buttonStyle={ButtonStyleType.NORMAL}
|
|
1285
|
-
onClick={handleRefreshLicense}
|
|
1286
|
-
isLoading={isRefreshingLicense}
|
|
1287
|
-
disabled={isRefreshingLicense || isConfigLoading}
|
|
1288
|
-
tooltip="Fetch the latest seat limit and expiry for this license from OneUptime."
|
|
1289
|
-
dataTestId="refresh-enterprise-license"
|
|
1290
|
-
className="!mt-0 md:!ml-0"
|
|
1291
|
-
/>
|
|
1292
|
-
<Button
|
|
1293
|
-
title="Change license key"
|
|
1294
|
-
icon={IconProp.Edit}
|
|
1295
|
-
buttonStyle={ButtonStyleType.NORMAL}
|
|
1296
|
-
onClick={handleStartChangingLicense}
|
|
1297
|
-
disabled={isRefreshingLicense}
|
|
1298
|
-
className="!mt-0 md:!ml-0"
|
|
1299
|
-
/>
|
|
1300
|
-
</div>
|
|
1301
|
-
) : undefined;
|
|
1302
|
-
|
|
1303
1094
|
const pillClassName: string =
|
|
1304
1095
|
pillTone === "alerted"
|
|
1305
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"
|
|
@@ -1484,6 +1275,450 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
1484
1275
|
</section>
|
|
1485
1276
|
) : null;
|
|
1486
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 "Talk to
|
|
1705
|
+
Sales" 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
|
+
|
|
1487
1722
|
return (
|
|
1488
1723
|
<>
|
|
1489
1724
|
<button
|
|
@@ -1523,650 +1758,16 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
1523
1758
|
<span className={pillCtaTextClassName}>{ctaLabel}</span>
|
|
1524
1759
|
</button>
|
|
1525
1760
|
|
|
1526
|
-
{
|
|
1527
|
-
<
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
isLoading={modalIsLoading}
|
|
1537
|
-
disableSubmitButton={modalDisableSubmitButton}
|
|
1538
|
-
isBodyLoading={IS_ENTERPRISE_EDITION ? isConfigLoading : false}
|
|
1539
|
-
leftFooterElement={modalLeftFooterElement}
|
|
1540
|
-
>
|
|
1541
|
-
<div className="space-y-4 text-sm text-gray-600">
|
|
1542
|
-
{IS_ENTERPRISE_EDITION ? (
|
|
1543
|
-
<>
|
|
1544
|
-
{!configError && successMessage && (
|
|
1545
|
-
<Alert type={AlertType.SUCCESS} title={successMessage} />
|
|
1546
|
-
)}
|
|
1547
|
-
|
|
1548
|
-
{/*
|
|
1549
|
-
* A failed refresh has nowhere else to go: the license key
|
|
1550
|
-
* section carries its own copy of this alert, but that
|
|
1551
|
-
* section is not rendered at all while the license is valid,
|
|
1552
|
-
* which is exactly when refreshing is worth doing.
|
|
1553
|
-
*/}
|
|
1554
|
-
{!configError && validationError && !showLicenseKeyInput && (
|
|
1555
|
-
<Alert type={AlertType.DANGER} title={validationError} />
|
|
1556
|
-
)}
|
|
1557
|
-
|
|
1558
|
-
{configError && (
|
|
1559
|
-
<div className="rounded-xl border border-red-200 bg-red-50 p-4">
|
|
1560
|
-
<div className="flex items-start gap-2.5">
|
|
1561
|
-
<Icon
|
|
1562
|
-
icon={IconProp.Alert}
|
|
1563
|
-
className="mt-0.5 h-4 w-4 shrink-0 text-red-600"
|
|
1564
|
-
/>
|
|
1565
|
-
<div className="min-w-0 flex-1">
|
|
1566
|
-
<p className="text-sm font-semibold text-red-900">
|
|
1567
|
-
Unable to load license details
|
|
1568
|
-
</p>
|
|
1569
|
-
<p className="mt-1 text-xs leading-relaxed text-red-800">
|
|
1570
|
-
{configError}
|
|
1571
|
-
</p>
|
|
1572
|
-
<div className="mt-3">
|
|
1573
|
-
<Button
|
|
1574
|
-
title="Try again"
|
|
1575
|
-
buttonStyle={ButtonStyleType.DANGER}
|
|
1576
|
-
onClick={handleRetryFetch}
|
|
1577
|
-
isLoading={isConfigLoading}
|
|
1578
|
-
className="!mt-0 md:!ml-0"
|
|
1579
|
-
/>
|
|
1580
|
-
</div>
|
|
1581
|
-
</div>
|
|
1582
|
-
</div>
|
|
1583
|
-
</div>
|
|
1584
|
-
)}
|
|
1585
|
-
|
|
1586
|
-
{evaluationNoticeElement}
|
|
1587
|
-
|
|
1588
|
-
{showLicenseDetails && (
|
|
1589
|
-
<dl className="grid grid-cols-1 gap-px overflow-hidden rounded-xl border border-gray-200 bg-gray-200 sm:grid-cols-2">
|
|
1590
|
-
<div className="min-w-0 bg-white px-4 py-3">
|
|
1591
|
-
<dt className="text-[11px] font-medium uppercase tracking-wide text-gray-500">
|
|
1592
|
-
Licensed to
|
|
1593
|
-
</dt>
|
|
1594
|
-
<dd
|
|
1595
|
-
className="mt-1 truncate text-sm font-medium text-gray-900"
|
|
1596
|
-
title={globalConfig?.enterpriseCompanyName || undefined}
|
|
1597
|
-
>
|
|
1598
|
-
{globalConfig?.enterpriseCompanyName || "Not specified"}
|
|
1599
|
-
</dd>
|
|
1600
|
-
</div>
|
|
1601
|
-
<div className="bg-white px-4 py-3">
|
|
1602
|
-
<dt className="text-[11px] font-medium uppercase tracking-wide text-gray-500">
|
|
1603
|
-
Expires
|
|
1604
|
-
</dt>
|
|
1605
|
-
<dd className="mt-1 text-sm font-medium tabular-nums text-gray-900">
|
|
1606
|
-
{licenseExpiresAtText || "—"}
|
|
1607
|
-
</dd>
|
|
1608
|
-
</div>
|
|
1609
|
-
</dl>
|
|
1610
|
-
)}
|
|
1611
|
-
|
|
1612
|
-
{versionCardElement}
|
|
1613
|
-
|
|
1614
|
-
{showLicenseDetails && (
|
|
1615
|
-
<section
|
|
1616
|
-
aria-labelledby="edition-seats-heading"
|
|
1617
|
-
className="rounded-xl border border-gray-200 bg-white p-5"
|
|
1618
|
-
>
|
|
1619
|
-
<div className="flex items-start justify-between gap-4">
|
|
1620
|
-
<div className="min-w-0">
|
|
1621
|
-
<h4
|
|
1622
|
-
id="edition-seats-heading"
|
|
1623
|
-
className="text-sm font-semibold text-gray-900"
|
|
1624
|
-
>
|
|
1625
|
-
Licensed seats
|
|
1626
|
-
</h4>
|
|
1627
|
-
<p className="mt-0.5 text-xs text-gray-500">
|
|
1628
|
-
Unique users across every instance on this license.
|
|
1629
|
-
</p>
|
|
1630
|
-
</div>
|
|
1631
|
-
{seatTone !== "healthy" && (
|
|
1632
|
-
<span
|
|
1633
|
-
className={`inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-xs font-medium ${
|
|
1634
|
-
seatTone === "breached"
|
|
1635
|
-
? "bg-red-100 text-red-700"
|
|
1636
|
-
: "bg-amber-100 text-amber-800"
|
|
1637
|
-
}`}
|
|
1638
|
-
>
|
|
1639
|
-
{seatTone === "breached"
|
|
1640
|
-
? "Limit exceeded"
|
|
1641
|
-
: "Nearly full"}
|
|
1642
|
-
</span>
|
|
1643
|
-
)}
|
|
1644
|
-
</div>
|
|
1645
|
-
|
|
1646
|
-
<div className="mt-4 flex items-end justify-between gap-4">
|
|
1647
|
-
<p className="flex items-baseline gap-1.5">
|
|
1648
|
-
<span
|
|
1649
|
-
className={`text-3xl font-semibold leading-none tabular-nums ${
|
|
1650
|
-
typeof effectiveUserCount !== "number"
|
|
1651
|
-
? "text-gray-300"
|
|
1652
|
-
: seatTone === "breached"
|
|
1653
|
-
? "text-red-700"
|
|
1654
|
-
: "text-gray-900"
|
|
1655
|
-
}`}
|
|
1656
|
-
>
|
|
1657
|
-
{typeof effectiveUserCount === "number"
|
|
1658
|
-
? effectiveUserCount.toLocaleString()
|
|
1659
|
-
: "—"}
|
|
1660
|
-
</span>
|
|
1661
|
-
<span className="text-sm tabular-nums text-gray-500">
|
|
1662
|
-
{" / "}
|
|
1663
|
-
{typeof userLimit === "number" && userLimit > 0
|
|
1664
|
-
? `${userLimit.toLocaleString()} seats`
|
|
1665
|
-
: "unlimited"}
|
|
1666
|
-
</span>
|
|
1667
|
-
</p>
|
|
1668
|
-
{typeof seatsRemaining === "number" && (
|
|
1669
|
-
<p
|
|
1670
|
-
className={`text-xs font-medium tabular-nums ${
|
|
1671
|
-
seatTone === "breached"
|
|
1672
|
-
? "text-red-700"
|
|
1673
|
-
: seatTone === "approaching"
|
|
1674
|
-
? "text-amber-700"
|
|
1675
|
-
: "text-gray-500"
|
|
1676
|
-
}`}
|
|
1677
|
-
>
|
|
1678
|
-
{seatsRemainingText}
|
|
1679
|
-
</p>
|
|
1680
|
-
)}
|
|
1681
|
-
</div>
|
|
1682
|
-
|
|
1683
|
-
{typeof seatUsageDisplayPercent === "number" && (
|
|
1684
|
-
<div className="mt-2.5">
|
|
1685
|
-
<div
|
|
1686
|
-
className="h-2.5 w-full overflow-hidden rounded-full bg-gray-100"
|
|
1687
|
-
role="progressbar"
|
|
1688
|
-
aria-label="Licensed seat usage"
|
|
1689
|
-
aria-valuenow={seatUsageBarPercent}
|
|
1690
|
-
aria-valuemin={0}
|
|
1691
|
-
aria-valuemax={100}
|
|
1692
|
-
aria-valuetext={seatUsageAriaValueText}
|
|
1693
|
-
>
|
|
1694
|
-
<div
|
|
1695
|
-
className={`h-full rounded-full transition-all duration-300 ease-out ${
|
|
1696
|
-
seatTone === "breached"
|
|
1697
|
-
? "bg-red-500"
|
|
1698
|
-
: seatTone === "approaching"
|
|
1699
|
-
? "bg-amber-500"
|
|
1700
|
-
: "bg-emerald-500"
|
|
1701
|
-
}`}
|
|
1702
|
-
style={{ width: `${seatUsageBarPercent}%` }}
|
|
1703
|
-
/>
|
|
1704
|
-
</div>
|
|
1705
|
-
<p className="mt-1.5 text-xs tabular-nums text-gray-500">
|
|
1706
|
-
{seatUsageDisplayPercent}% of licensed seats in use
|
|
1707
|
-
</p>
|
|
1708
|
-
</div>
|
|
1709
|
-
)}
|
|
1710
|
-
|
|
1711
|
-
{seatTone !== "healthy" && canSeeLicenseAdmin && (
|
|
1712
|
-
<div
|
|
1713
|
-
role={seatTone === "breached" ? "alert" : "status"}
|
|
1714
|
-
className={`mt-4 rounded-lg border p-3.5 ${
|
|
1715
|
-
seatTone === "breached"
|
|
1716
|
-
? "border-red-200 bg-red-50"
|
|
1717
|
-
: "border-amber-200 bg-amber-50"
|
|
1718
|
-
}`}
|
|
1719
|
-
>
|
|
1720
|
-
<div className="flex items-start gap-2.5">
|
|
1721
|
-
{/*
|
|
1722
|
-
* Colour comes from className only. Never add a type
|
|
1723
|
-
* prop here: Icon emits both the type colour and
|
|
1724
|
-
* className into one class attribute, so two colour
|
|
1725
|
-
* classes would land on one svg and Tailwind's
|
|
1726
|
-
* emission order would pick the winner.
|
|
1727
|
-
*/}
|
|
1728
|
-
<Icon
|
|
1729
|
-
icon={
|
|
1730
|
-
seatTone === "breached"
|
|
1731
|
-
? IconProp.Alert
|
|
1732
|
-
: IconProp.ExclaimationCircle
|
|
1733
|
-
}
|
|
1734
|
-
className={`mt-0.5 h-4 w-4 shrink-0 ${
|
|
1735
|
-
seatTone === "breached"
|
|
1736
|
-
? "text-red-600"
|
|
1737
|
-
: "text-amber-600"
|
|
1738
|
-
}`}
|
|
1739
|
-
/>
|
|
1740
|
-
<div className="min-w-0 flex-1">
|
|
1741
|
-
<h5
|
|
1742
|
-
className={`text-sm font-semibold ${
|
|
1743
|
-
seatTone === "breached"
|
|
1744
|
-
? "text-red-900"
|
|
1745
|
-
: "text-amber-900"
|
|
1746
|
-
}`}
|
|
1747
|
-
>
|
|
1748
|
-
{seatAdvisoryTitle}
|
|
1749
|
-
</h5>
|
|
1750
|
-
<p
|
|
1751
|
-
className={`mt-1 text-xs leading-relaxed ${
|
|
1752
|
-
seatTone === "breached"
|
|
1753
|
-
? "text-red-800"
|
|
1754
|
-
: "text-amber-800"
|
|
1755
|
-
}`}
|
|
1756
|
-
>
|
|
1757
|
-
{seatAdvisoryBody}
|
|
1758
|
-
</p>
|
|
1759
|
-
<div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
1760
|
-
<Button
|
|
1761
|
-
title={
|
|
1762
|
-
seatTone === "breached"
|
|
1763
|
-
? "Expand your license"
|
|
1764
|
-
: "Request more seats"
|
|
1765
|
-
}
|
|
1766
|
-
icon={IconProp.Email}
|
|
1767
|
-
buttonStyle={
|
|
1768
|
-
seatTone === "breached"
|
|
1769
|
-
? ButtonStyleType.DANGER
|
|
1770
|
-
: ButtonStyleType.PRIMARY
|
|
1771
|
-
}
|
|
1772
|
-
onClick={handleRequestMoreSeats}
|
|
1773
|
-
className="!mt-0 md:!ml-0"
|
|
1774
|
-
/>
|
|
1775
|
-
<span
|
|
1776
|
-
className={`text-[11px] ${
|
|
1777
|
-
seatTone === "breached"
|
|
1778
|
-
? "text-red-700"
|
|
1779
|
-
: "text-amber-700"
|
|
1780
|
-
}`}
|
|
1781
|
-
>
|
|
1782
|
-
Or email{" "}
|
|
1783
|
-
<a
|
|
1784
|
-
href={SALES_MAILTO_URL}
|
|
1785
|
-
className={`font-medium underline ${
|
|
1786
|
-
seatTone === "breached"
|
|
1787
|
-
? "text-red-800 hover:text-red-900"
|
|
1788
|
-
: "text-amber-900 hover:text-amber-950"
|
|
1789
|
-
}`}
|
|
1790
|
-
>
|
|
1791
|
-
{SALES_EMAIL}
|
|
1792
|
-
</a>{" "}
|
|
1793
|
-
directly.
|
|
1794
|
-
</span>
|
|
1795
|
-
</div>
|
|
1796
|
-
</div>
|
|
1797
|
-
</div>
|
|
1798
|
-
</div>
|
|
1799
|
-
)}
|
|
1800
|
-
|
|
1801
|
-
<div className="mt-4 border-t border-gray-100 pt-3">
|
|
1802
|
-
<p className="text-xs text-gray-500">
|
|
1803
|
-
{userCountUpdatedAtText
|
|
1804
|
-
? `Last reported to OneUptime on ${userCountUpdatedAtText}.`
|
|
1805
|
-
: "User count has not been reported to OneUptime yet. The first report will be sent within 24 hours."}
|
|
1806
|
-
{isSeatLimitEnforced && (
|
|
1807
|
-
<>
|
|
1808
|
-
{" "}
|
|
1809
|
-
This installation enforces the seat limit: users
|
|
1810
|
-
above it cannot be invited, signed up or
|
|
1811
|
-
provisioned.
|
|
1812
|
-
</>
|
|
1813
|
-
)}
|
|
1814
|
-
{/*
|
|
1815
|
-
* Only pointed at somebody who can actually press it.
|
|
1816
|
-
* The refresh button is master-admin only, so telling
|
|
1817
|
-
* everyone else to use it would be an instruction they
|
|
1818
|
-
* cannot follow.
|
|
1819
|
-
*/}
|
|
1820
|
-
{isSeatLimitEnforced && canManageLicense && (
|
|
1821
|
-
<>
|
|
1822
|
-
{" "}
|
|
1823
|
-
Bought more seats? Use "Refresh license"
|
|
1824
|
-
below to apply the new limit without waiting for the
|
|
1825
|
-
next daily report.
|
|
1826
|
-
</>
|
|
1827
|
-
)}
|
|
1828
|
-
</p>
|
|
1829
|
-
</div>
|
|
1830
|
-
</section>
|
|
1831
|
-
)}
|
|
1832
|
-
|
|
1833
|
-
{showLicenseDetails && licenseInstances.length > 0 && (
|
|
1834
|
-
<section
|
|
1835
|
-
aria-labelledby="edition-instances-heading"
|
|
1836
|
-
className="rounded-xl border border-gray-200 bg-white p-5"
|
|
1837
|
-
>
|
|
1838
|
-
<div className="flex items-center justify-between gap-3">
|
|
1839
|
-
<h4
|
|
1840
|
-
id="edition-instances-heading"
|
|
1841
|
-
className="text-sm font-semibold text-gray-900"
|
|
1842
|
-
>
|
|
1843
|
-
Instances on this license
|
|
1844
|
-
</h4>
|
|
1845
|
-
<span className="shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium tabular-nums text-gray-600">
|
|
1846
|
-
{licenseInstances.length}{" "}
|
|
1847
|
-
{licenseInstances.length === 1
|
|
1848
|
-
? "instance"
|
|
1849
|
-
: "instances"}
|
|
1850
|
-
</span>
|
|
1851
|
-
</div>
|
|
1852
|
-
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
|
1853
|
-
Use the same license key on every instance you deploy
|
|
1854
|
-
(staging, production, and so on). Each instance reports
|
|
1855
|
-
its usage and the version it runs once a day.
|
|
1856
|
-
</p>
|
|
1857
|
-
{licenseInstances.length > 1 && (
|
|
1858
|
-
<p className="mt-1.5 text-xs leading-relaxed text-gray-500">
|
|
1859
|
-
{instanceOverlapText}
|
|
1860
|
-
</p>
|
|
1861
|
-
)}
|
|
1862
|
-
<ul className="mt-3 divide-y divide-gray-100 overflow-hidden rounded-lg border border-gray-200">
|
|
1863
|
-
{licenseInstances.map(
|
|
1864
|
-
(
|
|
1865
|
-
instance: EnterpriseLicenseInstanceSummary,
|
|
1866
|
-
index: number,
|
|
1867
|
-
) => {
|
|
1868
|
-
const isThisInstance: boolean =
|
|
1869
|
-
Boolean(thisInstanceId) &&
|
|
1870
|
-
instance.instanceId === thisInstanceId;
|
|
1871
|
-
|
|
1872
|
-
/*
|
|
1873
|
-
* The list is a snapshot of what each instance last
|
|
1874
|
-
* reported, refreshed at most once a day. For the
|
|
1875
|
-
* instance serving this page the server just told us
|
|
1876
|
-
* what it is running right now, so prefer that —
|
|
1877
|
-
* otherwise this row contradicts the "This
|
|
1878
|
-
* installation" card above it for up to a day after
|
|
1879
|
-
* an upgrade. hasComparableVersion keeps a dev build
|
|
1880
|
-
* from rendering "vunknown".
|
|
1881
|
-
*/
|
|
1882
|
-
const displayVersion: string | null =
|
|
1883
|
-
isThisInstance && hasComparableVersion
|
|
1884
|
-
? currentVersion
|
|
1885
|
-
: instance.version;
|
|
1886
|
-
|
|
1887
|
-
/*
|
|
1888
|
-
* Per-instance, so an admin can see which of their
|
|
1889
|
-
* deployments are lagging rather than only the one
|
|
1890
|
-
* they happen to be signed in to.
|
|
1891
|
-
*/
|
|
1892
|
-
const isInstanceOutdated: boolean =
|
|
1893
|
-
VersionUtil.isUpdateAvailable({
|
|
1894
|
-
currentVersion: displayVersion,
|
|
1895
|
-
latestVersion: latestVersion,
|
|
1896
|
-
});
|
|
1897
|
-
|
|
1898
|
-
return (
|
|
1899
|
-
<li
|
|
1900
|
-
key={instance.instanceId || index}
|
|
1901
|
-
className="flex items-center justify-between gap-4 bg-white px-3 py-2.5 transition-colors hover:bg-gray-50"
|
|
1902
|
-
>
|
|
1903
|
-
<div className="min-w-0 flex-1">
|
|
1904
|
-
{/*
|
|
1905
|
-
* Wraps rather than squeezing: both pills are
|
|
1906
|
-
* shrink-0, so without this a long hostname on
|
|
1907
|
-
* a narrow modal absorbs all the overflow and
|
|
1908
|
-
* truncates to a few characters.
|
|
1909
|
-
*/}
|
|
1910
|
-
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
|
1911
|
-
<span
|
|
1912
|
-
className="min-w-0 max-w-full truncate text-sm font-medium text-gray-900"
|
|
1913
|
-
title={instance.host || "Unknown host"}
|
|
1914
|
-
>
|
|
1915
|
-
{instance.host || "Unknown host"}
|
|
1916
|
-
</span>
|
|
1917
|
-
{isThisInstance && (
|
|
1918
|
-
<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">
|
|
1919
|
-
This instance
|
|
1920
|
-
</span>
|
|
1921
|
-
)}
|
|
1922
|
-
{displayVersion && (
|
|
1923
|
-
<span
|
|
1924
|
-
className={`shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-medium tabular-nums ${
|
|
1925
|
-
isInstanceOutdated
|
|
1926
|
-
? "bg-amber-50 text-amber-800"
|
|
1927
|
-
: "bg-gray-100 text-gray-600"
|
|
1928
|
-
}`}
|
|
1929
|
-
title={
|
|
1930
|
-
isInstanceOutdated
|
|
1931
|
-
? `Running v${displayVersion}. OneUptime v${latestVersion} is available.`
|
|
1932
|
-
: `Running v${displayVersion}.`
|
|
1933
|
-
}
|
|
1934
|
-
>
|
|
1935
|
-
v{displayVersion}
|
|
1936
|
-
</span>
|
|
1937
|
-
)}
|
|
1938
|
-
</div>
|
|
1939
|
-
<p className="mt-0.5 truncate text-xs text-gray-500">
|
|
1940
|
-
{formatInstanceReportedAt(
|
|
1941
|
-
instance.lastReportedAt,
|
|
1942
|
-
)}
|
|
1943
|
-
{isInstanceOutdated
|
|
1944
|
-
? " Update available."
|
|
1945
|
-
: ""}
|
|
1946
|
-
</p>
|
|
1947
|
-
</div>
|
|
1948
|
-
<div className="flex shrink-0 items-baseline justify-end gap-1">
|
|
1949
|
-
{typeof instance.userCount === "number" ? (
|
|
1950
|
-
<>
|
|
1951
|
-
<span className="text-sm font-medium tabular-nums text-gray-900">
|
|
1952
|
-
{instance.userCount.toLocaleString()}
|
|
1953
|
-
</span>
|
|
1954
|
-
<span className="text-xs text-gray-500">
|
|
1955
|
-
{instance.userCount === 1
|
|
1956
|
-
? "user"
|
|
1957
|
-
: "users"}
|
|
1958
|
-
</span>
|
|
1959
|
-
</>
|
|
1960
|
-
) : (
|
|
1961
|
-
<span className="text-sm text-gray-400">
|
|
1962
|
-
—
|
|
1963
|
-
</span>
|
|
1964
|
-
)}
|
|
1965
|
-
</div>
|
|
1966
|
-
</li>
|
|
1967
|
-
);
|
|
1968
|
-
},
|
|
1969
|
-
)}
|
|
1970
|
-
</ul>
|
|
1971
|
-
</section>
|
|
1972
|
-
)}
|
|
1973
|
-
|
|
1974
|
-
{!configError &&
|
|
1975
|
-
!isConfigLoading &&
|
|
1976
|
-
!licenseValid &&
|
|
1977
|
-
globalConfig?.enterpriseLicenseKey && (
|
|
1978
|
-
<div className="rounded-xl border border-red-200 bg-red-50 p-4">
|
|
1979
|
-
<p className="text-sm font-semibold text-red-900">
|
|
1980
|
-
License validation required
|
|
1981
|
-
</p>
|
|
1982
|
-
<p className="mt-1 text-xs leading-relaxed text-red-800">
|
|
1983
|
-
The stored license information could not be verified.
|
|
1984
|
-
Please validate the license key again.
|
|
1985
|
-
</p>
|
|
1986
|
-
</div>
|
|
1987
|
-
)}
|
|
1988
|
-
|
|
1989
|
-
{showLicenseAdminRequiredNotice && (
|
|
1990
|
-
<section className="rounded-xl border border-amber-200 bg-amber-50 p-5">
|
|
1991
|
-
<h4 className="text-sm font-semibold text-amber-900">
|
|
1992
|
-
A master admin has to activate this license
|
|
1993
|
-
</h4>
|
|
1994
|
-
<p className="mt-1 text-xs leading-relaxed text-amber-800">
|
|
1995
|
-
This installation does not have a valid enterprise
|
|
1996
|
-
license. Ask a master admin of this OneUptime installation
|
|
1997
|
-
to enter or refresh the license key from this dialog.
|
|
1998
|
-
</p>
|
|
1999
|
-
</section>
|
|
2000
|
-
)}
|
|
2001
|
-
|
|
2002
|
-
{!configError && showLicenseKeyInput && (
|
|
2003
|
-
<section className="rounded-xl border border-gray-200 bg-white p-5">
|
|
2004
|
-
<div className="flex items-start justify-between gap-4">
|
|
2005
|
-
<div className="min-w-0">
|
|
2006
|
-
<label
|
|
2007
|
-
htmlFor="enterprise-license-key"
|
|
2008
|
-
className="text-sm font-semibold text-gray-900"
|
|
2009
|
-
>
|
|
2010
|
-
{isChangingLicense
|
|
2011
|
-
? "New license key"
|
|
2012
|
-
: "License key"}
|
|
2013
|
-
</label>
|
|
2014
|
-
<p className="mt-0.5 text-xs leading-relaxed text-gray-500">
|
|
2015
|
-
{licenseKeyHelperText}
|
|
2016
|
-
{!isChangingLicense && (
|
|
2017
|
-
<>
|
|
2018
|
-
{" "}
|
|
2019
|
-
<a
|
|
2020
|
-
href={SALES_MAILTO_URL}
|
|
2021
|
-
className="font-medium text-indigo-600 hover:text-indigo-700"
|
|
2022
|
-
>
|
|
2023
|
-
{SALES_EMAIL}
|
|
2024
|
-
</a>
|
|
2025
|
-
.
|
|
2026
|
-
</>
|
|
2027
|
-
)}
|
|
2028
|
-
</p>
|
|
2029
|
-
</div>
|
|
2030
|
-
{isChangingLicense && (
|
|
2031
|
-
<Button
|
|
2032
|
-
title="Cancel"
|
|
2033
|
-
buttonStyle={ButtonStyleType.NORMAL}
|
|
2034
|
-
onClick={handleCancelChangingLicense}
|
|
2035
|
-
className="!mt-0 shrink-0 md:!ml-0"
|
|
2036
|
-
/>
|
|
2037
|
-
)}
|
|
2038
|
-
</div>
|
|
2039
|
-
<div className="mt-3">
|
|
2040
|
-
<Input
|
|
2041
|
-
id="enterprise-license-key"
|
|
2042
|
-
value={licenseKeyInput}
|
|
2043
|
-
onChange={(value: string) => {
|
|
2044
|
-
setLicenseKeyInput(value);
|
|
2045
|
-
licenseInputEditedRef.current = true;
|
|
2046
|
-
}}
|
|
2047
|
-
placeholder="Enter your enterprise license key"
|
|
2048
|
-
disableSpellCheck={true}
|
|
2049
|
-
/>
|
|
2050
|
-
</div>
|
|
2051
|
-
{validationError && (
|
|
2052
|
-
<div className="mt-3">
|
|
2053
|
-
<Alert
|
|
2054
|
-
type={AlertType.DANGER}
|
|
2055
|
-
title={validationError}
|
|
2056
|
-
/>
|
|
2057
|
-
</div>
|
|
2058
|
-
)}
|
|
2059
|
-
</section>
|
|
2060
|
-
)}
|
|
2061
|
-
|
|
2062
|
-
{showEnterpriseFeatureList && (
|
|
2063
|
-
<section
|
|
2064
|
-
aria-labelledby="edition-features-heading"
|
|
2065
|
-
className="rounded-xl border border-indigo-100 bg-gradient-to-br from-indigo-50 via-white to-white p-5"
|
|
2066
|
-
>
|
|
2067
|
-
<h4
|
|
2068
|
-
id="edition-features-heading"
|
|
2069
|
-
className="text-sm font-semibold text-indigo-900"
|
|
2070
|
-
>
|
|
2071
|
-
What your license unlocks
|
|
2072
|
-
</h4>
|
|
2073
|
-
<p className="mt-0.5 text-xs text-indigo-700">
|
|
2074
|
-
{canManageLicense
|
|
2075
|
-
? "Validate your key above to turn these on immediately."
|
|
2076
|
-
: "A master admin can activate the license to turn these on."}
|
|
2077
|
-
</p>
|
|
2078
|
-
<ul className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
2079
|
-
{enterpriseFeatures.map(
|
|
2080
|
-
(feature: string, index: number) => {
|
|
2081
|
-
return (
|
|
2082
|
-
<li
|
|
2083
|
-
key={index}
|
|
2084
|
-
className="flex items-start gap-2 rounded-lg border border-gray-100 bg-white px-3 py-2.5"
|
|
2085
|
-
>
|
|
2086
|
-
<span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-indigo-50">
|
|
2087
|
-
<Icon
|
|
2088
|
-
icon={IconProp.Check}
|
|
2089
|
-
className="h-3 w-3 text-indigo-600"
|
|
2090
|
-
/>
|
|
2091
|
-
</span>
|
|
2092
|
-
<span className="text-xs leading-snug text-gray-700">
|
|
2093
|
-
{feature}
|
|
2094
|
-
</span>
|
|
2095
|
-
</li>
|
|
2096
|
-
);
|
|
2097
|
-
},
|
|
2098
|
-
)}
|
|
2099
|
-
</ul>
|
|
2100
|
-
</section>
|
|
2101
|
-
)}
|
|
2102
|
-
</>
|
|
2103
|
-
) : (
|
|
2104
|
-
<>
|
|
2105
|
-
{versionCardElement}
|
|
2106
|
-
<p>
|
|
2107
|
-
You are running the Community Edition of OneUptime. Here is a
|
|
2108
|
-
quick comparison to help you decide if Enterprise is the right
|
|
2109
|
-
fit for your team.
|
|
2110
|
-
</p>
|
|
2111
|
-
<div className="grid gap-4 md:grid-cols-2">
|
|
2112
|
-
<div className="rounded-xl border border-gray-200 bg-white p-4">
|
|
2113
|
-
<h4 className="text-sm font-semibold text-gray-900">
|
|
2114
|
-
Community Edition
|
|
2115
|
-
</h4>
|
|
2116
|
-
<ul className="mt-3 space-y-2 text-sm text-gray-600">
|
|
2117
|
-
{communityFeatures.map(
|
|
2118
|
-
(feature: string, index: number) => {
|
|
2119
|
-
return (
|
|
2120
|
-
<li key={index} className="flex items-start gap-2">
|
|
2121
|
-
<Icon
|
|
2122
|
-
icon={IconProp.Check}
|
|
2123
|
-
className="mt-0.5 h-3 w-3 shrink-0 text-gray-400"
|
|
2124
|
-
/>
|
|
2125
|
-
<span className="leading-snug">{feature}</span>
|
|
2126
|
-
</li>
|
|
2127
|
-
);
|
|
2128
|
-
},
|
|
2129
|
-
)}
|
|
2130
|
-
</ul>
|
|
2131
|
-
<p className="mt-3 text-xs text-gray-500">
|
|
2132
|
-
Best for small teams experimenting with reliability
|
|
2133
|
-
workflows.
|
|
2134
|
-
</p>
|
|
2135
|
-
</div>
|
|
2136
|
-
<div className="rounded-xl border border-indigo-100 bg-gradient-to-br from-indigo-50 via-white to-white p-4">
|
|
2137
|
-
<h4 className="text-sm font-semibold text-indigo-900">
|
|
2138
|
-
Enterprise Edition
|
|
2139
|
-
</h4>
|
|
2140
|
-
<ul className="mt-3 space-y-2 text-sm text-indigo-900">
|
|
2141
|
-
{enterpriseFeatures.map(
|
|
2142
|
-
(feature: string, index: number) => {
|
|
2143
|
-
return (
|
|
2144
|
-
<li key={index} className="flex items-start gap-2">
|
|
2145
|
-
<Icon
|
|
2146
|
-
icon={IconProp.Check}
|
|
2147
|
-
className="mt-0.5 h-3 w-3 shrink-0 text-indigo-600"
|
|
2148
|
-
/>
|
|
2149
|
-
<span className="leading-snug">{feature}</span>
|
|
2150
|
-
</li>
|
|
2151
|
-
);
|
|
2152
|
-
},
|
|
2153
|
-
)}
|
|
2154
|
-
</ul>
|
|
2155
|
-
<p className="mt-3 text-xs text-indigo-700">
|
|
2156
|
-
Everything in Community plus white-glove onboarding,
|
|
2157
|
-
enterprise SLAs, and a partner dedicated to your
|
|
2158
|
-
reliability goals.
|
|
2159
|
-
</p>
|
|
2160
|
-
</div>
|
|
2161
|
-
</div>
|
|
2162
|
-
<p className="text-xs text-gray-500">
|
|
2163
|
-
Ready to unlock enterprise capabilities? Click "Talk to
|
|
2164
|
-
Sales" to start the conversation.
|
|
2165
|
-
</p>
|
|
2166
|
-
</>
|
|
2167
|
-
)}
|
|
2168
|
-
</div>
|
|
2169
|
-
</Modal>
|
|
1761
|
+
{LicenseManager ? (
|
|
1762
|
+
<LicenseManager
|
|
1763
|
+
license={licenseSnapshot}
|
|
1764
|
+
isDialogOpen={isDialogOpen}
|
|
1765
|
+
reloadLicense={fetchGlobalConfig}
|
|
1766
|
+
applySeatEnforcement={applySeatEnforcementPayload}
|
|
1767
|
+
renderDialog={renderDialog}
|
|
1768
|
+
/>
|
|
1769
|
+
) : (
|
|
1770
|
+
renderDialog({})
|
|
2170
1771
|
)}
|
|
2171
1772
|
</>
|
|
2172
1773
|
);
|