@oneuptime/common 13.0.8 → 14.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Index.ts +4 -0
  4. package/Models/DatabaseModels/Monitor.ts +3 -2
  5. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  6. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
  7. package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
  8. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
  9. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
  10. package/Scripts/generate-service-worker.js +65 -4
  11. package/Server/API/BaseAPI.ts +26 -5
  12. package/Server/API/GlobalConfigAPI.ts +228 -462
  13. package/Server/API/ProjectOIDC.ts +25 -16
  14. package/Server/API/ProjectSSO.ts +24 -16
  15. package/Server/API/StatusPageAPI.ts +100 -42
  16. package/Server/API/TeamMemberAPI.ts +10 -1
  17. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  18. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  19. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  20. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  21. package/Server/EnvironmentConfig.ts +147 -2
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  26. package/Server/Middleware/UserAuthorization.ts +38 -16
  27. package/Server/Services/AuditLogService.ts +84 -1079
  28. package/Server/Services/DatabaseService.ts +10 -0
  29. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  30. package/Server/Services/GlobalConfigService.ts +80 -1
  31. package/Server/Services/Index.ts +4 -0
  32. package/Server/Services/MonitorProbeService.ts +116 -20
  33. package/Server/Services/MonitorService.ts +59 -6
  34. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  35. package/Server/Services/MonitorTemplateService.ts +26 -0
  36. package/Server/Services/ProjectService.ts +284 -0
  37. package/Server/Services/RumSessionPinService.ts +25 -6
  38. package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
  39. package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
  40. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
  41. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
  42. package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
  43. package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
  44. package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
  45. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  46. package/Server/Services/StatusPageService.ts +78 -1
  47. package/Server/Services/TeamMemberService.ts +11 -0
  48. package/Server/Services/TeamService.ts +10 -0
  49. package/Server/Services/UserService.ts +10 -17
  50. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  51. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  52. package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
  53. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  54. package/Server/Types/Markdown.ts +41 -0
  55. package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
  56. package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
  57. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  58. package/Server/Utils/EditionEnforcement.ts +233 -0
  59. package/Server/Utils/FrontendEnvironment.ts +41 -2
  60. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  61. package/Server/Utils/Monitor/AffectedResourceList.ts +148 -0
  62. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  63. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  64. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
  65. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +720 -465
  66. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  67. package/Server/Utils/Monitor/RootCauseList.ts +139 -0
  68. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
  69. package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
  70. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  71. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  72. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  73. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  74. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  75. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  76. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  77. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  78. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  79. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  80. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  81. package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
  82. package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
  83. package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
  84. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  85. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
  86. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
  87. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  88. package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
  89. package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
  90. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
  91. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
  92. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
  93. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
  94. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  95. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  96. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
  97. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  98. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  99. package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
  100. package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
  101. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
  102. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
  103. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
  104. package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
  105. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  106. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  107. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  108. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  109. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
  110. package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
  111. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
  112. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  113. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  114. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  115. package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
  116. package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
  117. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  118. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  119. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  120. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  121. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  122. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  123. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  124. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  125. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  126. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  127. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  128. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  129. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  130. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  131. package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
  132. package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
  133. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
  134. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  135. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  136. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  137. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  138. package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
  139. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  140. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  141. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  142. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  143. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  144. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  145. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  146. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
  147. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
  148. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  149. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
  150. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
  151. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
  152. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
  153. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
  154. package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
  155. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
  156. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  157. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  158. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
  159. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  160. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  161. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  162. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
  163. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  164. package/Tests/Server/Types/Markdown.test.ts +183 -1
  165. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
  166. package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
  167. package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
  168. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  169. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  170. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  171. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  172. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  173. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  174. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
  175. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +1339 -96
  176. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
  177. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
  178. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  179. package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
  180. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
  181. package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
  182. package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
  183. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
  184. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  185. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  186. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  187. package/Tests/Types/API/Route.test.ts +56 -0
  188. package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
  189. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  190. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  191. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  192. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
  193. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  194. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  195. package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
  196. package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
  197. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  198. package/Tests/UI/Components/Icon.test.tsx +26 -0
  199. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  200. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  201. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  202. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  203. package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
  204. package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
  205. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  206. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  207. package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
  208. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  209. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  210. package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
  211. package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
  212. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
  213. package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
  214. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  215. package/Types/API/Hostname.ts +63 -16
  216. package/Types/API/Route.ts +19 -2
  217. package/Types/API/URL.ts +26 -2
  218. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  219. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  220. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  221. package/Types/Database/CanonicalModelInstance.ts +102 -0
  222. package/Types/Database/TableColumn.ts +20 -6
  223. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  224. package/Types/IP/IPv6.ts +6 -1
  225. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
  226. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
  227. package/Types/Monitor/MonitorStep.ts +6 -1
  228. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  229. package/Types/Permission.ts +90 -0
  230. package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
  231. package/Types/Rules/RuleRun.ts +4 -0
  232. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  233. package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
  234. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  235. package/UI/Components/Feed/FeedOptions.ts +427 -0
  236. package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
  237. package/UI/Components/Feed/useFeedItems.ts +28 -9
  238. package/UI/Components/Feed/useFeedOptions.ts +225 -0
  239. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  240. package/UI/Components/Icon/Icon.tsx +1 -1
  241. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  242. package/UI/Components/ModelList/ModelList.tsx +11 -1
  243. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  244. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  245. package/UI/esbuild-enterprise.js +263 -0
  246. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  247. package/Utils/HostAddressUtil.ts +94 -0
  248. package/Utils/LabelRuleImportExport.ts +27 -9
  249. package/Utils/Monitor/MetricValueFormatter.ts +2 -2
  250. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  251. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  252. package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
  253. package/Utils/Slo/SloDuration.ts +22 -6
  254. package/Utils/Slo/SloHealth.ts +8 -7
  255. package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
  256. package/Utils/Slo/SloOverviewText.ts +33 -0
  257. package/Utils/Slo/SloProjection.ts +4 -3
  258. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  259. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  260. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  261. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  262. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  263. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  264. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  265. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  266. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  267. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  268. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  269. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
  270. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
  271. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
  272. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
  273. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
  274. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
  275. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
  276. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
  277. package/build/dist/Server/API/BaseAPI.js +26 -5
  278. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  279. package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
  280. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  281. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  282. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  283. package/build/dist/Server/API/ProjectSSO.js +24 -16
  284. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  285. package/build/dist/Server/API/StatusPageAPI.js +93 -43
  286. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  287. package/build/dist/Server/API/TeamMemberAPI.js +8 -1
  288. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  289. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  290. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  291. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  292. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  293. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  294. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  295. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  296. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  297. package/build/dist/Server/EnvironmentConfig.js +84 -2
  298. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  299. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  300. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  301. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
  302. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
  303. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
  304. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
  305. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  306. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  307. package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
  308. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  309. package/build/dist/Server/Services/AuditLogService.js +71 -740
  310. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  311. package/build/dist/Server/Services/DatabaseService.js +7 -0
  312. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  313. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  314. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  315. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  316. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  317. package/build/dist/Server/Services/Index.js +4 -0
  318. package/build/dist/Server/Services/Index.js.map +1 -1
  319. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  320. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  321. package/build/dist/Server/Services/MonitorService.js +48 -6
  322. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  323. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  324. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  325. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  326. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  327. package/build/dist/Server/Services/ProjectService.js +214 -0
  328. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  329. package/build/dist/Server/Services/RumSessionPinService.js +24 -6
  330. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  331. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
  332. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
  333. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
  334. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
  335. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
  336. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  337. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
  338. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
  339. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
  340. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
  341. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
  342. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
  343. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
  344. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  345. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  346. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  347. package/build/dist/Server/Services/StatusPageService.js +71 -0
  348. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  349. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  350. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  351. package/build/dist/Server/Services/TeamService.js +9 -0
  352. package/build/dist/Server/Services/TeamService.js.map +1 -1
  353. package/build/dist/Server/Services/UserService.js +10 -16
  354. package/build/dist/Server/Services/UserService.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  356. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  358. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
  360. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  361. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  362. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  363. package/build/dist/Server/Types/Markdown.js +31 -0
  364. package/build/dist/Server/Types/Markdown.js.map +1 -1
  365. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
  366. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
  368. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  369. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  370. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  371. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  372. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  373. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  374. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  375. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  376. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  377. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +67 -0
  378. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  379. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  380. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  381. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  382. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
  383. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +585 -367
  384. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  385. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  386. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  387. package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
  388. package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
  389. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
  390. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  391. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
  392. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
  393. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  394. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  395. package/build/dist/Types/API/Hostname.js +59 -11
  396. package/build/dist/Types/API/Hostname.js.map +1 -1
  397. package/build/dist/Types/API/Route.js +18 -2
  398. package/build/dist/Types/API/Route.js.map +1 -1
  399. package/build/dist/Types/API/URL.js +23 -2
  400. package/build/dist/Types/API/URL.js.map +1 -1
  401. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  402. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  403. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  404. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  405. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  406. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  407. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  408. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  409. package/build/dist/Types/Database/TableColumn.js +19 -6
  410. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  411. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  412. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  413. package/build/dist/Types/IP/IPv6.js +6 -1
  414. package/build/dist/Types/IP/IPv6.js.map +1 -1
  415. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  416. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  417. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  418. package/build/dist/Types/Permission.js +84 -0
  419. package/build/dist/Types/Permission.js.map +1 -1
  420. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
  421. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  422. package/build/dist/Types/Rules/RuleRun.js +4 -0
  423. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  424. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  425. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  426. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
  427. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  428. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  429. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  430. package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
  431. package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
  432. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
  433. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
  434. package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
  435. package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
  436. package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
  437. package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
  438. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  439. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  440. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  441. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  442. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  443. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  444. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  445. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  446. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  447. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  448. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  449. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  450. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  451. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  452. package/build/dist/Utils/HostAddressUtil.js +80 -0
  453. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  454. package/build/dist/Utils/LabelRuleImportExport.js +22 -6
  455. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  456. package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
  457. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  458. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  459. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  460. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  461. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
  462. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
  463. package/build/dist/Utils/Slo/SloDuration.js +13 -5
  464. package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
  465. package/build/dist/Utils/Slo/SloHealth.js +8 -7
  466. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  467. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
  468. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
  469. package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
  470. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
  471. package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
  472. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  473. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  474. package/jest.config.json +3 -1
  475. package/package.json +1 -1
  476. package/test-setup.sh +103 -34
  477. package/tsconfig.json +13 -0
  478. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  479. package/Server/API/TeamComplianceAPI.ts +0 -158
  480. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  481. package/Server/Services/TeamComplianceService.ts +0 -733
  482. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  483. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  484. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  485. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  486. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
  487. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  488. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  489. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
  490. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  491. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  492. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  493. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  494. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  495. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  496. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  497. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  498. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  499. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  500. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  501. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  502. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  503. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  504. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  505. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  506. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  507. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  508. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  509. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  510. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  511. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  512. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  513. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  514. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -0,0 +1,692 @@
1
+ import React, {
2
+ FunctionComponent,
3
+ ReactElement,
4
+ useCallback,
5
+ useEffect,
6
+ useId,
7
+ useLayoutEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from "react";
12
+ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
13
+ import IconProp from "../../../Types/Icon/IconProp";
14
+ import useTranslateValue from "../../Utils/Translation";
15
+ import Icon from "../Icon/Icon";
16
+ import {
17
+ DEFAULT_FEED_SORT_ORDER,
18
+ FEED_OPTIONS_TEXT,
19
+ FEED_SORT_ORDER_OPTIONS,
20
+ FeedEventTypeOption,
21
+ FeedOptions,
22
+ FeedSortOrderOption,
23
+ getFeedOptionsSummary,
24
+ isDefaultFeedOptions,
25
+ translateFeedOptionsText,
26
+ } from "./FeedOptions";
27
+
28
+ export interface ComponentProps {
29
+ value: FeedOptions;
30
+ onChange: (options: FeedOptions) => void;
31
+ eventTypeOptions: Array<FeedEventTypeOption>;
32
+ }
33
+
34
+ // A short checklist is scanned by eye; a longer one gets a search box.
35
+ export const FEED_EVENT_TYPE_SEARCH_THRESHOLD: number = 8;
36
+
37
+ export const FEED_OPTIONS_PANEL_WIDTH_PX: number = 288;
38
+
39
+ // The panel never comes closer than this to either edge of the viewport.
40
+ export const FEED_OPTIONS_VIEWPORT_GUTTER_PX: number = 16;
41
+
42
+ export interface FeedOptionsPanelPlacement {
43
+ // Offset from the trigger's left edge, in px.
44
+ left: number;
45
+ width: number;
46
+ }
47
+
48
+ type GetFeedOptionsPanelPlacement = (data: {
49
+ triggerLeft: number;
50
+ triggerRight: number;
51
+ viewportWidth: number;
52
+ }) => FeedOptionsPanelPlacement;
53
+
54
+ /*
55
+ * Where the panel goes. It lines its right edge up with the trigger's, like
56
+ * every other header menu, and slides back inside the viewport when that
57
+ * would cross an edge. That happens on phones and tablets: the card header
58
+ * stacks there and puts this button, the first of the header's controls, at
59
+ * the left of the row, so a right-aligned panel would open off the screen.
60
+ */
61
+ export const getFeedOptionsPanelPlacement: GetFeedOptionsPanelPlacement =
62
+ (data: {
63
+ triggerLeft: number;
64
+ triggerRight: number;
65
+ viewportWidth: number;
66
+ }): FeedOptionsPanelPlacement => {
67
+ const width: number = Math.max(
68
+ Math.min(
69
+ FEED_OPTIONS_PANEL_WIDTH_PX,
70
+ data.viewportWidth - 2 * FEED_OPTIONS_VIEWPORT_GUTTER_PX,
71
+ ),
72
+ 0,
73
+ );
74
+ const lowestLeft: number = FEED_OPTIONS_VIEWPORT_GUTTER_PX;
75
+ const highestLeft: number = Math.max(
76
+ data.viewportWidth - FEED_OPTIONS_VIEWPORT_GUTTER_PX - width,
77
+ lowestLeft,
78
+ );
79
+ const left: number = Math.min(
80
+ Math.max(data.triggerRight - width, lowestLeft),
81
+ highestLeft,
82
+ );
83
+
84
+ return {
85
+ left: left - data.triggerLeft,
86
+ width,
87
+ };
88
+ };
89
+
90
+ /*
91
+ * The one control in a feed's header for everything about what the feed
92
+ * shows: the time order and the event type filter. It opens a small panel
93
+ * rather than a menu, because the reader ticks several boxes in one visit and
94
+ * a menu closes on every pick. Changes apply as they are made - there is no
95
+ * Apply button to forget - and the trigger is tinted, with a count of the
96
+ * chosen event types, whenever the feed is not showing its default view.
97
+ */
98
+ const FeedOptionsButton: FunctionComponent<ComponentProps> = (
99
+ props: ComponentProps,
100
+ ): ReactElement => {
101
+ const { translateString } = useTranslateValue();
102
+ const uniqueId: string = useId();
103
+ const buttonId: string = `feed-options-button-${uniqueId}`;
104
+ const summaryId: string = `feed-options-summary-${uniqueId}`;
105
+ const panelId: string = `feed-options-panel-${uniqueId}`;
106
+ const sortHeadingId: string = `feed-options-sort-${uniqueId}`;
107
+ const eventTypesHeadingId: string = `feed-options-event-types-${uniqueId}`;
108
+
109
+ const [isOpen, setIsOpen] = useState<boolean>(false);
110
+ const [searchText, setSearchText] = useState<string>("");
111
+ const [placement, setPlacement] = useState<FeedOptionsPanelPlacement | null>(
112
+ null,
113
+ );
114
+
115
+ const containerRef: React.MutableRefObject<HTMLDivElement | null> =
116
+ useRef<HTMLDivElement | null>(null);
117
+ const triggerRef: React.MutableRefObject<HTMLButtonElement | null> =
118
+ useRef<HTMLButtonElement | null>(null);
119
+ const sortRadioRefs: React.MutableRefObject<Array<HTMLButtonElement | null>> =
120
+ useRef<Array<HTMLButtonElement | null>>([]);
121
+ const eventTypeListRef: React.MutableRefObject<HTMLUListElement | null> =
122
+ useRef<HTMLUListElement | null>(null);
123
+
124
+ const selectedEventTypes: Set<string> = useMemo(() => {
125
+ return new Set<string>(props.value.eventTypes);
126
+ }, [props.value.eventTypes]);
127
+
128
+ const selectedCount: number = props.value.eventTypes.length;
129
+ const isCustomized: boolean = !isDefaultFeedOptions(props.value);
130
+ const isOldestFirst: boolean =
131
+ props.value.sortOrder !== DEFAULT_FEED_SORT_ORDER;
132
+ const translate: (
133
+ text: string,
134
+ values?: Record<string, string | number>,
135
+ ) => string = (
136
+ text: string,
137
+ values?: Record<string, string | number>,
138
+ ): string => {
139
+ return translateFeedOptionsText({
140
+ text,
141
+ values,
142
+ translate: translateString,
143
+ });
144
+ };
145
+
146
+ const summary: string = getFeedOptionsSummary({
147
+ options: props.value,
148
+ eventTypeCount: props.eventTypeOptions.length,
149
+ translate: translateString,
150
+ });
151
+
152
+ const showSearch: boolean =
153
+ props.eventTypeOptions.length > FEED_EVENT_TYPE_SEARCH_THRESHOLD;
154
+
155
+ /*
156
+ * Labels are shown - and searched and ordered - in the reader's language.
157
+ * A label with no translation yet stays in English.
158
+ */
159
+ const translatedEventTypeOptions: Array<FeedEventTypeOption> =
160
+ props.eventTypeOptions
161
+ .map((option: FeedEventTypeOption): FeedEventTypeOption => {
162
+ return {
163
+ ...option,
164
+ label: translate(option.label),
165
+ };
166
+ })
167
+ .sort((a: FeedEventTypeOption, b: FeedEventTypeOption): number => {
168
+ return a.label.localeCompare(b.label);
169
+ });
170
+
171
+ const searchTerm: string = searchText.trim().toLowerCase();
172
+ const visibleEventTypeOptions: Array<FeedEventTypeOption> = searchTerm
173
+ ? translatedEventTypeOptions.filter((option: FeedEventTypeOption) => {
174
+ return option.label.toLowerCase().includes(searchTerm);
175
+ })
176
+ : translatedEventTypeOptions;
177
+
178
+ const close: (shouldReturnFocus: boolean) => void = (
179
+ shouldReturnFocus: boolean,
180
+ ): void => {
181
+ setIsOpen(false);
182
+ setSearchText("");
183
+
184
+ if (shouldReturnFocus) {
185
+ triggerRef.current?.focus();
186
+ }
187
+ };
188
+
189
+ /*
190
+ * Dismiss on a press outside the trigger and panel. The listener sits on
191
+ * the container that holds both, so pressing the trigger to close the panel
192
+ * is a toggle, not a dismissal followed by a re-open.
193
+ */
194
+ useEffect(() => {
195
+ if (!isOpen) {
196
+ return undefined;
197
+ }
198
+
199
+ const onPointerDown: (event: MouseEvent) => void = (
200
+ event: MouseEvent,
201
+ ): void => {
202
+ const target: EventTarget | null = event.target;
203
+
204
+ if (
205
+ containerRef.current &&
206
+ target instanceof Node &&
207
+ !containerRef.current.contains(target)
208
+ ) {
209
+ setIsOpen(false);
210
+ setSearchText("");
211
+ }
212
+ };
213
+
214
+ document.addEventListener("mousedown", onPointerDown);
215
+
216
+ return () => {
217
+ document.removeEventListener("mousedown", onPointerDown);
218
+ };
219
+ }, [isOpen]);
220
+
221
+ const placePanel: () => void = useCallback((): void => {
222
+ const container: HTMLDivElement | null = containerRef.current;
223
+
224
+ if (!container) {
225
+ return;
226
+ }
227
+
228
+ const rect: DOMRect = container.getBoundingClientRect();
229
+
230
+ setPlacement(
231
+ getFeedOptionsPanelPlacement({
232
+ triggerLeft: rect.left,
233
+ triggerRight: rect.right,
234
+ viewportWidth:
235
+ document.documentElement.clientWidth || window.innerWidth,
236
+ }),
237
+ );
238
+ }, []);
239
+
240
+ // Placed before paint, so the panel never flashes off-screen first.
241
+ useLayoutEffect(() => {
242
+ if (!isOpen) {
243
+ setPlacement(null);
244
+ return undefined;
245
+ }
246
+
247
+ placePanel();
248
+ window.addEventListener("resize", placePanel);
249
+
250
+ return () => {
251
+ window.removeEventListener("resize", placePanel);
252
+ };
253
+ }, [isOpen, placePanel]);
254
+
255
+ // Opening moves focus onto the chosen sort order, the panel's first control.
256
+ useEffect(() => {
257
+ if (!isOpen) {
258
+ return;
259
+ }
260
+
261
+ const selectedIndex: number = FEED_SORT_ORDER_OPTIONS.findIndex(
262
+ (option: FeedSortOrderOption) => {
263
+ return option.value === props.value.sortOrder;
264
+ },
265
+ );
266
+
267
+ sortRadioRefs.current[Math.max(selectedIndex, 0)]?.focus();
268
+ // Only on open: re-focusing on every sort change would fight the keyboard.
269
+ }, [isOpen]);
270
+
271
+ const setSortOrder: (sortOrder: SortOrder) => void = (
272
+ sortOrder: SortOrder,
273
+ ): void => {
274
+ if (sortOrder === props.value.sortOrder) {
275
+ return;
276
+ }
277
+
278
+ props.onChange({
279
+ ...props.value,
280
+ sortOrder,
281
+ });
282
+ };
283
+
284
+ const toggleEventType: (eventType: string) => void = (
285
+ eventType: string,
286
+ ): void => {
287
+ const nextSelected: Set<string> = new Set<string>(selectedEventTypes);
288
+
289
+ if (nextSelected.has(eventType)) {
290
+ nextSelected.delete(eventType);
291
+ } else {
292
+ nextSelected.add(eventType);
293
+ }
294
+
295
+ // Keep the checklist's order, not the order the boxes were ticked in.
296
+ props.onChange({
297
+ ...props.value,
298
+ eventTypes: props.eventTypeOptions
299
+ .map((option: FeedEventTypeOption) => {
300
+ return option.value;
301
+ })
302
+ .filter((value: string) => {
303
+ return nextSelected.has(value);
304
+ }),
305
+ });
306
+ };
307
+
308
+ /*
309
+ * "Show all" and "Reset to default" hide themselves once they have done
310
+ * their job. Focus on a button that unmounts falls to the page body, out of
311
+ * the panel, where Escape no longer closes it - so both hand focus to the
312
+ * control the reader is most likely to reach for next.
313
+ */
314
+ const focusSortOrder: (sortOrder: SortOrder) => void = (
315
+ sortOrder: SortOrder,
316
+ ): void => {
317
+ const index: number = FEED_SORT_ORDER_OPTIONS.findIndex(
318
+ (option: FeedSortOrderOption) => {
319
+ return option.value === sortOrder;
320
+ },
321
+ );
322
+
323
+ sortRadioRefs.current[Math.max(index, 0)]?.focus();
324
+ };
325
+
326
+ const clearEventTypes: () => void = (): void => {
327
+ props.onChange({
328
+ ...props.value,
329
+ eventTypes: [],
330
+ });
331
+
332
+ const firstCheckbox: HTMLInputElement | null =
333
+ eventTypeListRef.current?.querySelector<HTMLInputElement>(
334
+ 'input[type="checkbox"]',
335
+ ) || null;
336
+
337
+ if (firstCheckbox) {
338
+ firstCheckbox.focus();
339
+ } else {
340
+ focusSortOrder(props.value.sortOrder);
341
+ }
342
+ };
343
+
344
+ const resetOptions: () => void = (): void => {
345
+ props.onChange({
346
+ sortOrder: DEFAULT_FEED_SORT_ORDER,
347
+ eventTypes: [],
348
+ });
349
+ focusSortOrder(DEFAULT_FEED_SORT_ORDER);
350
+ };
351
+
352
+ const onContainerKeyDown: (event: React.KeyboardEvent) => void = (
353
+ event: React.KeyboardEvent,
354
+ ): void => {
355
+ if (isOpen && event.key === "Escape") {
356
+ event.preventDefault();
357
+ event.stopPropagation();
358
+ close(true);
359
+ }
360
+ };
361
+
362
+ /*
363
+ * Tabbing out of the panel closes it. Only focus that lands on something
364
+ * outside counts: a press on the panel's own padding moves focus to the
365
+ * panel itself (it is focusable for exactly this reason), which keeps
366
+ * Escape working.
367
+ */
368
+ const onContainerBlur: (event: React.FocusEvent) => void = (
369
+ event: React.FocusEvent,
370
+ ): void => {
371
+ const nextFocus: EventTarget | null = event.relatedTarget;
372
+
373
+ if (
374
+ isOpen &&
375
+ nextFocus instanceof Node &&
376
+ containerRef.current &&
377
+ !containerRef.current.contains(nextFocus)
378
+ ) {
379
+ setIsOpen(false);
380
+ setSearchText("");
381
+ }
382
+ };
383
+
384
+ // Arrow keys move between the two sort orders, as in any radio group.
385
+ const onSortKeyDown: (event: React.KeyboardEvent, index: number) => void = (
386
+ event: React.KeyboardEvent,
387
+ index: number,
388
+ ): void => {
389
+ let nextIndex: number | null = null;
390
+
391
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
392
+ nextIndex = (index + 1) % FEED_SORT_ORDER_OPTIONS.length;
393
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
394
+ nextIndex =
395
+ (index - 1 + FEED_SORT_ORDER_OPTIONS.length) %
396
+ FEED_SORT_ORDER_OPTIONS.length;
397
+ }
398
+
399
+ if (nextIndex === null) {
400
+ return;
401
+ }
402
+
403
+ event.preventDefault();
404
+ const nextOption: FeedSortOrderOption | undefined =
405
+ FEED_SORT_ORDER_OPTIONS[nextIndex];
406
+
407
+ if (nextOption) {
408
+ setSortOrder(nextOption.value);
409
+ sortRadioRefs.current[nextIndex]?.focus();
410
+ }
411
+ };
412
+
413
+ /*
414
+ * The visible label is the button's name, and the state behind it - order
415
+ * and filter - its description, so a screen reader says each once. The
416
+ * label is only drawn from xl up: beside the feed's other header controls
417
+ * it would squeeze the card title into a sliver on tablets. Oldest first
418
+ * swaps the funnel for the sort glyph, so a reversed feed does not rely on
419
+ * the tint alone to say so.
420
+ */
421
+ const getTrigger: () => ReactElement = (): ReactElement => {
422
+ return (
423
+ <button
424
+ ref={triggerRef}
425
+ id={buttonId}
426
+ type="button"
427
+ data-testid="feed-options-button"
428
+ className={`inline-flex min-h-[2.375rem] items-center gap-2 rounded-lg border px-3.5 py-2 text-sm font-medium shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-150 cursor-pointer select-none ${
429
+ isCustomized
430
+ ? "border-indigo-300 bg-indigo-50 text-indigo-700 hover:bg-indigo-100 hover:border-indigo-400"
431
+ : "border-gray-300 bg-white text-gray-700 hover:bg-gray-50 hover:border-gray-400 hover:text-gray-900"
432
+ }`}
433
+ title={summary}
434
+ aria-describedby={summaryId}
435
+ aria-haspopup="dialog"
436
+ aria-expanded={isOpen}
437
+ aria-controls={isOpen ? panelId : undefined}
438
+ onClick={() => {
439
+ if (isOpen) {
440
+ close(false);
441
+ } else {
442
+ setIsOpen(true);
443
+ }
444
+ }}
445
+ >
446
+ <Icon
447
+ icon={isOldestFirst ? IconProp.BarsArrowUp : IconProp.Filter}
448
+ className={`h-4 w-4 ${isCustomized ? "text-indigo-500" : "text-gray-500"}`}
449
+ />
450
+ <span
451
+ className="sr-only xl:not-sr-only"
452
+ data-testid="feed-options-label"
453
+ >
454
+ {translate(FEED_OPTIONS_TEXT.triggerLabel)}
455
+ </span>
456
+ {selectedCount > 0 && (
457
+ <span
458
+ data-testid="feed-options-count"
459
+ className="inline-flex min-w-[1.25rem] justify-center rounded-full bg-indigo-600 px-1.5 text-xs font-semibold leading-5 text-white"
460
+ aria-hidden="true"
461
+ >
462
+ {selectedCount}
463
+ </span>
464
+ )}
465
+ <Icon
466
+ icon={IconProp.ChevronDown}
467
+ className={`h-3.5 w-3.5 ml-0.5 ${isCustomized ? "text-indigo-400" : "text-gray-400"}`}
468
+ />
469
+ </button>
470
+ );
471
+ };
472
+
473
+ const getSortSection: () => ReactElement = (): ReactElement => {
474
+ return (
475
+ <div className="border-b border-gray-100 p-3">
476
+ <div
477
+ id={sortHeadingId}
478
+ className="text-xs font-semibold uppercase tracking-wide text-gray-500"
479
+ >
480
+ {translate(FEED_OPTIONS_TEXT.sortHeading)}
481
+ </div>
482
+ <div
483
+ role="radiogroup"
484
+ aria-labelledby={sortHeadingId}
485
+ className="mt-2 grid grid-cols-2 gap-1 rounded-lg bg-gray-100 p-1"
486
+ >
487
+ {FEED_SORT_ORDER_OPTIONS.map(
488
+ (option: FeedSortOrderOption, index: number) => {
489
+ const isSelected: boolean =
490
+ option.value === props.value.sortOrder;
491
+
492
+ return (
493
+ <button
494
+ key={option.value}
495
+ ref={(element: HTMLButtonElement | null) => {
496
+ sortRadioRefs.current[index] = element;
497
+ }}
498
+ type="button"
499
+ role="radio"
500
+ aria-checked={isSelected}
501
+ tabIndex={isSelected ? 0 : -1}
502
+ data-testid={`feed-options-sort-${option.value}`}
503
+ className={`inline-flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-sm transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${
504
+ isSelected
505
+ ? "bg-white font-semibold text-gray-900 shadow-sm ring-1 ring-indigo-400"
506
+ : "font-normal text-gray-600 hover:text-gray-900"
507
+ }`}
508
+ onClick={() => {
509
+ setSortOrder(option.value);
510
+ }}
511
+ onKeyDown={(event: React.KeyboardEvent) => {
512
+ onSortKeyDown(event, index);
513
+ }}
514
+ >
515
+ <Icon
516
+ icon={option.icon}
517
+ className={`h-4 w-4 ${isSelected ? "text-indigo-500" : "text-gray-400"}`}
518
+ />
519
+ {translate(option.label)}
520
+ </button>
521
+ );
522
+ },
523
+ )}
524
+ </div>
525
+ </div>
526
+ );
527
+ };
528
+
529
+ const getEventTypeSection: () => ReactElement = (): ReactElement => {
530
+ return (
531
+ <div className="p-3">
532
+ <div className="flex items-center justify-between">
533
+ <div
534
+ id={eventTypesHeadingId}
535
+ className="text-xs font-semibold uppercase tracking-wide text-gray-500"
536
+ >
537
+ {translate(FEED_OPTIONS_TEXT.eventTypesHeading)}
538
+ </div>
539
+ {selectedCount > 0 && (
540
+ <button
541
+ type="button"
542
+ data-testid="feed-options-clear-event-types"
543
+ className="-my-1 rounded px-1.5 py-1 text-xs font-medium text-indigo-600 hover:text-indigo-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
544
+ onClick={clearEventTypes}
545
+ >
546
+ {translate(FEED_OPTIONS_TEXT.showAll)}
547
+ </button>
548
+ )}
549
+ </div>
550
+ <p
551
+ className="mt-1 text-xs text-gray-500"
552
+ data-testid="feed-options-event-type-summary"
553
+ aria-live="polite"
554
+ >
555
+ {selectedCount === 0
556
+ ? translate(FEED_OPTIONS_TEXT.showingEveryEventType)
557
+ : translate(FEED_OPTIONS_TEXT.showingSomeEventTypes, {
558
+ selected: selectedCount,
559
+ total: props.eventTypeOptions.length,
560
+ })}
561
+ </p>
562
+ {showSearch && (
563
+ <div className="relative mt-2">
564
+ <Icon
565
+ icon={IconProp.Search}
566
+ className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"
567
+ />
568
+ <input
569
+ type="text"
570
+ value={searchText}
571
+ aria-label={translate(FEED_OPTIONS_TEXT.searchPlaceholder)}
572
+ placeholder={translate(FEED_OPTIONS_TEXT.searchPlaceholder)}
573
+ data-testid="feed-options-search"
574
+ className="block w-full rounded-md border border-gray-300 bg-white py-1.5 pl-8 pr-2 text-sm text-gray-900 placeholder-gray-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
575
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
576
+ setSearchText(event.target.value);
577
+ }}
578
+ />
579
+ <span
580
+ className="sr-only"
581
+ aria-live="polite"
582
+ data-testid="feed-options-search-results"
583
+ >
584
+ {searchTerm
585
+ ? translate(FEED_OPTIONS_TEXT.searchResults, {
586
+ count: visibleEventTypeOptions.length,
587
+ })
588
+ : ""}
589
+ </span>
590
+ </div>
591
+ )}
592
+ <div role="group" aria-labelledby={eventTypesHeadingId}>
593
+ <ul
594
+ ref={eventTypeListRef}
595
+ role="list"
596
+ className="-mx-1 mt-2 max-h-64 overflow-y-auto"
597
+ data-testid="feed-options-event-types"
598
+ >
599
+ {visibleEventTypeOptions.map((option: FeedEventTypeOption) => {
600
+ const isChecked: boolean = selectedEventTypes.has(option.value);
601
+
602
+ return (
603
+ <li key={option.value}>
604
+ <label className="flex cursor-pointer items-center gap-2.5 rounded-md px-1.5 py-1.5 text-sm text-gray-700 hover:bg-gray-50">
605
+ <input
606
+ type="checkbox"
607
+ checked={isChecked}
608
+ data-testid={`feed-options-event-type-${option.value}`}
609
+ className="h-4 w-4 shrink-0 cursor-pointer rounded border-gray-300 accent-indigo-600 text-indigo-600 focus:ring-indigo-500"
610
+ onChange={() => {
611
+ toggleEventType(option.value);
612
+ }}
613
+ />
614
+ {option.icon && (
615
+ <Icon
616
+ icon={option.icon}
617
+ className="h-4 w-4 shrink-0 text-gray-400"
618
+ />
619
+ )}
620
+ <span className="truncate">{option.label}</span>
621
+ </label>
622
+ </li>
623
+ );
624
+ })}
625
+ {visibleEventTypeOptions.length === 0 && (
626
+ <li className="px-1.5 py-2 text-sm text-gray-500">
627
+ {props.eventTypeOptions.length === 0
628
+ ? translate(FEED_OPTIONS_TEXT.noEventTypes)
629
+ : translate(FEED_OPTIONS_TEXT.noSearchMatches, {
630
+ search: searchText.trim(),
631
+ })}
632
+ </li>
633
+ )}
634
+ </ul>
635
+ </div>
636
+ </div>
637
+ );
638
+ };
639
+
640
+ return (
641
+ <div
642
+ ref={containerRef}
643
+ className="relative inline-block text-left"
644
+ onKeyDown={onContainerKeyDown}
645
+ onBlur={onContainerBlur}
646
+ >
647
+ {getTrigger()}
648
+ {/* Outside the button, or its text would join the button's name. */}
649
+ <span
650
+ id={summaryId}
651
+ className="sr-only"
652
+ data-testid="feed-options-summary"
653
+ >
654
+ {summary}
655
+ </span>
656
+ {isOpen && (
657
+ <div
658
+ id={panelId}
659
+ role="dialog"
660
+ tabIndex={-1}
661
+ aria-label={translate(FEED_OPTIONS_TEXT.panelLabel)}
662
+ data-testid="feed-options-panel"
663
+ style={
664
+ placement
665
+ ? { left: `${placement.left}px`, width: `${placement.width}px` }
666
+ : undefined
667
+ }
668
+ className={`absolute z-50 mt-2 origin-top-right rounded-lg bg-white shadow-xl ring-1 ring-gray-200 focus:outline-none ${
669
+ placement ? "" : "right-0 w-72"
670
+ }`}
671
+ >
672
+ {getSortSection()}
673
+ {getEventTypeSection()}
674
+ {isCustomized && (
675
+ <div className="flex justify-end border-t border-gray-100 px-3 py-2">
676
+ <button
677
+ type="button"
678
+ data-testid="feed-options-reset"
679
+ className="-my-1 rounded px-1.5 py-1 text-xs font-medium text-gray-600 hover:text-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
680
+ onClick={resetOptions}
681
+ >
682
+ {translate(FEED_OPTIONS_TEXT.reset)}
683
+ </button>
684
+ </div>
685
+ )}
686
+ </div>
687
+ )}
688
+ </div>
689
+ );
690
+ };
691
+
692
+ export default FeedOptionsButton;