@oneuptime/common 14.0.9 → 14.0.11

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 (1146) hide show
  1. package/Models/DatabaseModels/AIConversation.ts +127 -1
  2. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  3. package/Models/DatabaseModels/CephCluster.ts +225 -0
  4. package/Models/DatabaseModels/CloudResource.ts +12 -0
  5. package/Models/DatabaseModels/DatabaseServer.ts +225 -0
  6. package/Models/DatabaseModels/DockerHost.ts +225 -0
  7. package/Models/DatabaseModels/DockerSwarmCluster.ts +225 -0
  8. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  9. package/Models/DatabaseModels/GlobalOidc.ts +0 -4
  10. package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
  11. package/Models/DatabaseModels/GlobalSso.ts +0 -4
  12. package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
  13. package/Models/DatabaseModels/Host.ts +225 -0
  14. package/Models/DatabaseModels/IncidentForm.ts +594 -0
  15. package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
  16. package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
  17. package/Models/DatabaseModels/Index.ts +16 -0
  18. package/Models/DatabaseModels/IoTFleet.ts +12 -0
  19. package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
  20. package/Models/DatabaseModels/MessageQueue.ts +1102 -0
  21. package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
  22. package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
  23. package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
  24. package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
  25. package/Models/DatabaseModels/PodmanHost.ts +225 -0
  26. package/Models/DatabaseModels/Project.ts +59 -0
  27. package/Models/DatabaseModels/ProjectOidc.ts +0 -4
  28. package/Models/DatabaseModels/ProjectSso.ts +0 -4
  29. package/Models/DatabaseModels/ProxmoxCluster.ts +225 -0
  30. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  31. package/Models/DatabaseModels/RumApplication.ts +12 -0
  32. package/Models/DatabaseModels/RunnerJob.ts +144 -0
  33. package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
  34. package/Models/DatabaseModels/Service.ts +12 -0
  35. package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
  36. package/Models/DatabaseModels/StatusPageSso.ts +0 -4
  37. package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
  38. package/Models/DatabaseModels/VMwareVCenter.ts +225 -0
  39. package/Server/API/AIChatAPI.ts +13 -114
  40. package/Server/API/AIInvestigationAPI.ts +3 -3
  41. package/Server/API/AIInvestigationConversationAPI.ts +266 -0
  42. package/Server/API/AutoRemediationAPI.ts +392 -1
  43. package/Server/API/IncidentFormAPI.ts +376 -0
  44. package/Server/API/ProjectOIDC.ts +16 -25
  45. package/Server/API/ProjectSSO.ts +16 -24
  46. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  47. package/Server/API/StatusPageAPI.ts +42 -77
  48. package/Server/Enterprise/EnterpriseEdition.ts +93 -28
  49. package/Server/Enterprise/EnterpriseFeature.ts +25 -3
  50. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
  51. package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
  52. package/Server/EnvironmentConfig.ts +1 -1
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  54. package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
  55. package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
  56. package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
  57. package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
  58. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  59. package/Server/Infrastructure/Semaphore.ts +22 -0
  60. package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
  61. package/Server/Middleware/TelemetryIngest.ts +15 -5
  62. package/Server/Middleware/UserAuthorization.ts +16 -38
  63. package/Server/Services/AIService.ts +10 -13
  64. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  65. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  66. package/Server/Services/CephClusterService.ts +109 -1
  67. package/Server/Services/DatabaseServerService.ts +116 -1
  68. package/Server/Services/DockerHostService.ts +109 -1
  69. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  70. package/Server/Services/HostService.ts +126 -4
  71. package/Server/Services/IncidentCustomFieldService.ts +112 -2
  72. package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
  73. package/Server/Services/IncidentEpisodeService.ts +10 -1
  74. package/Server/Services/IncidentFormService.ts +1566 -0
  75. package/Server/Services/IncidentFormSubmissionService.ts +97 -0
  76. package/Server/Services/IncidentGroupingEngineService.ts +45 -8
  77. package/Server/Services/IncidentService.ts +123 -5
  78. package/Server/Services/IncidentTemplateService.ts +22 -0
  79. package/Server/Services/Index.ts +14 -0
  80. package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
  81. package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
  82. package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
  83. package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
  84. package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
  85. package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
  86. package/Server/Services/MessageQueueService.ts +1463 -0
  87. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  88. package/Server/Services/OpenTelemetryIngestService.ts +37 -4
  89. package/Server/Services/PodmanHostService.ts +109 -1
  90. package/Server/Services/ProjectService.ts +0 -59
  91. package/Server/Services/ProxmoxClusterService.ts +110 -1
  92. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  93. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  94. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  95. package/Server/Services/RunnerJobService.ts +611 -4
  96. package/Server/Services/StatusPagePrivateUserService.ts +1 -4
  97. package/Server/Services/StatusPageService.ts +1 -51
  98. package/Server/Services/TeamComplianceSettingService.ts +163 -42
  99. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  100. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  101. package/Server/Services/UserNotificationRuleService.ts +8 -1
  102. package/Server/Services/VMwareVCenterService.ts +110 -1
  103. package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
  104. package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
  105. package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
  106. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
  107. package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
  108. package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
  109. package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
  110. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
  111. package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
  112. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
  113. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  114. package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
  115. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
  116. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
  117. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
  118. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
  119. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1442 -34
  120. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +968 -34
  121. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  122. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +622 -0
  123. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  124. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  125. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  126. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  127. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +882 -0
  128. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +125 -12
  129. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +99 -15
  130. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +99 -15
  131. package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
  132. package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
  133. package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
  134. package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
  135. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
  136. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  137. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  138. package/Server/Utils/EditionEnforcement.ts +19 -202
  139. package/Server/Utils/LLM/LLMService.ts +259 -63
  140. package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
  141. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  142. package/Server/Utils/SameOriginRequest.ts +203 -0
  143. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  144. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  145. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  146. package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
  147. package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
  148. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
  149. package/Server/Utils/VendorAssets.ts +3 -3
  150. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  151. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  152. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  153. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +301 -225
  154. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +38 -16
  155. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  156. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +376 -144
  157. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  158. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  159. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  160. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  161. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  162. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  163. package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
  164. package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
  165. package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
  166. package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
  167. package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
  168. package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
  169. package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
  170. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
  171. package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
  172. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
  173. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
  174. package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
  175. package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
  176. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
  177. package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
  178. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +267 -12
  179. package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
  180. package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
  181. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
  182. package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
  183. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +23 -6
  184. package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
  185. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
  186. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
  187. package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
  188. package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
  189. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
  190. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
  191. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
  192. package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
  193. package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
  194. package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
  195. package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
  196. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
  197. package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
  198. package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
  199. package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
  200. package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
  201. package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
  202. package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
  203. package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
  204. package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
  205. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
  206. package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
  207. package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
  208. package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
  209. package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
  210. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  211. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
  212. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
  213. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
  214. package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
  215. package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
  216. package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
  217. package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
  218. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
  219. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
  220. package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
  221. package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
  222. package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
  223. package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
  224. package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
  225. package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
  226. package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
  227. package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
  228. package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
  229. package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
  230. package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
  231. package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
  232. package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
  233. package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
  234. package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
  235. package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
  236. package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
  237. package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
  238. package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
  239. package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
  240. package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
  241. package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
  242. package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
  243. package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
  244. package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
  245. package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
  246. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
  247. package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
  248. package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
  249. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
  250. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
  251. package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
  252. package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
  253. package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
  254. package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
  255. package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
  256. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  257. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  258. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  259. package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
  260. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  261. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +2010 -0
  262. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1455 -0
  263. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  264. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  265. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
  266. package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
  267. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
  268. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
  269. package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
  270. package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
  271. package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
  272. package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
  273. package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
  274. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  275. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
  276. package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
  277. package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
  278. package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
  279. package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
  280. package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
  281. package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
  282. package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
  283. package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
  284. package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
  285. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
  286. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +61 -10
  287. package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
  288. package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
  289. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  290. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  291. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  292. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
  293. package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
  294. package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
  295. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  296. package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
  297. package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
  298. package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
  299. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
  300. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  301. package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
  302. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
  303. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
  304. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
  305. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
  306. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
  307. package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
  308. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  309. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  310. package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
  311. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  312. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
  313. package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
  314. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  315. package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
  316. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
  317. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
  318. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
  319. package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
  320. package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
  321. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
  322. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  323. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  324. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  325. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  326. package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
  327. package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
  328. package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
  329. package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
  330. package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
  331. package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
  332. package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
  333. package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
  334. package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
  335. package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
  336. package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
  337. package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
  338. package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
  339. package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
  340. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
  341. package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
  342. package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
  343. package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
  344. package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
  345. package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
  346. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  347. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
  348. package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
  349. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
  350. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
  351. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
  352. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  353. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  354. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  355. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  356. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  357. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  358. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  359. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  360. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  361. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  362. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
  363. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
  364. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
  365. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  366. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  367. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  368. package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
  369. package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
  370. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  371. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
  372. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
  373. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
  374. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  375. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  376. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
  377. package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
  378. package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
  379. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
  380. package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
  381. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
  382. package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
  383. package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
  384. package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
  385. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
  386. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
  387. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
  388. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
  389. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +694 -0
  390. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  391. package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
  392. package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
  393. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
  394. package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
  395. package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
  396. package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
  397. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  398. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  399. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
  400. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1251 -0
  401. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  402. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  403. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +1004 -0
  404. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  405. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  406. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  407. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +834 -0
  408. package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
  409. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
  410. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
  411. package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
  412. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  413. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  414. package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
  415. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  416. package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
  417. package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
  418. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  419. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  420. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
  421. package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
  422. package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
  423. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
  424. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
  425. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  426. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  427. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  428. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  429. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  430. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  431. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  432. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  433. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  434. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  435. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  436. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  437. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  438. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  439. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  440. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  441. package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
  442. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  443. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  444. package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
  445. package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
  446. package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
  447. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
  448. package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
  449. package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
  450. package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
  451. package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
  452. package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
  453. package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
  454. package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
  455. package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
  456. package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
  457. package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
  458. package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
  459. package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
  460. package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
  461. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  462. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  463. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  464. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  465. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +519 -0
  466. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  467. package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
  468. package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
  469. package/Tests/Types/XML.test.ts +51 -0
  470. package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
  471. package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
  472. package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
  473. package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
  474. package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
  475. package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
  476. package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
  477. package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
  478. package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
  479. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
  480. package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
  481. package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
  482. package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
  483. package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
  484. package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
  485. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
  486. package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
  487. package/Tests/UI/Components/Icon.test.tsx +32 -0
  488. package/Tests/UI/Components/Input.test.tsx +31 -0
  489. package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
  490. package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
  491. package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
  492. package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
  493. package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
  494. package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
  495. package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
  496. package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
  497. package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
  498. package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
  499. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  500. package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
  501. package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
  502. package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
  503. package/Tests/UI/Components/TextArea.test.tsx +19 -0
  504. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
  505. package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
  506. package/Tests/UI/EsbuildConfig.test.ts +115 -39
  507. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
  508. package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
  509. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  510. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
  511. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
  512. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  513. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  514. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  515. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  516. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  517. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  518. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  519. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  520. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  521. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  522. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  523. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  524. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  525. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  526. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  527. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  528. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  529. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  530. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  531. package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
  532. package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
  533. package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
  534. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  535. package/Types/AI/AIAgentRunLimits.ts +146 -0
  536. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  537. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  538. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
  539. package/Types/Code/CodeType.ts +2 -0
  540. package/Types/Code/JsonSyntax.ts +923 -0
  541. package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
  542. package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
  543. package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
  544. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
  545. package/Types/Icon/IconProp.ts +2 -0
  546. package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
  547. package/Types/Incident/IncidentFormPublic.ts +1145 -0
  548. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
  549. package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
  550. package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
  551. package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
  552. package/Types/MessageQueue/MessagingSystem.ts +518 -0
  553. package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
  554. package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
  555. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  556. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  557. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  558. package/Types/Permission.ts +310 -0
  559. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  560. package/Types/ResourceAiAgent/ResourceAiAccess.ts +580 -0
  561. package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
  562. package/Types/Rules/RuleRun.ts +4 -0
  563. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  564. package/Types/Runbook/RunbookStepType.ts +29 -0
  565. package/Types/Team/ComplianceNotificationChannel.ts +4 -3
  566. package/Types/Team/ComplianceRule.ts +71 -18
  567. package/Types/Team/ComplianceRuleType.ts +2 -2
  568. package/Types/Team/TeamComplianceStatus.ts +6 -2
  569. package/Types/Telemetry/EntityType.ts +16 -0
  570. package/Types/Telemetry/LockedFilterDetail.ts +22 -0
  571. package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
  572. package/Types/XML.ts +26 -0
  573. package/UI/Components/CodeEditor/CodeEditor.css +379 -0
  574. package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
  575. package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
  576. package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
  577. package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
  578. package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
  579. package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
  580. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
  581. package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
  582. package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
  583. package/UI/Components/Forms/Fields/FormField.tsx +8 -0
  584. package/UI/Components/Forms/Types/Field.ts +8 -0
  585. package/UI/Components/Icon/Icon.tsx +18 -0
  586. package/UI/Components/Input/Input.tsx +11 -4
  587. package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
  588. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
  589. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  590. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
  591. package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
  592. package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
  593. package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
  594. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
  595. package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
  596. package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
  597. package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
  598. package/UI/Components/Table/EmptyTableMessage.ts +81 -0
  599. package/UI/Components/Table/Table.tsx +50 -21
  600. package/UI/Components/Table/TableRow.tsx +9 -1
  601. package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
  602. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
  603. package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
  604. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
  605. package/UI/Components/TextArea/TextArea.tsx +9 -2
  606. package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
  607. package/UI/Styles/Theme.css +8 -0
  608. package/UI/esbuild-config.js +2 -32
  609. package/Utils/AI/InvestigationReport.ts +121 -15
  610. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  611. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  612. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  613. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  614. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  615. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  616. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  617. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  618. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  619. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  620. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  621. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  622. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  623. package/Utils/LabelRuleImportExport.ts +2 -0
  624. package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
  625. package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
  626. package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
  627. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  628. package/Utils/Telemetry/EntityKey.ts +48 -0
  629. package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
  630. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  631. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  632. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  633. package/build/dist/Models/DatabaseModels/CephCluster.js +231 -0
  634. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  635. package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
  636. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  637. package/build/dist/Models/DatabaseModels/DatabaseServer.js +231 -0
  638. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  639. package/build/dist/Models/DatabaseModels/DockerHost.js +231 -0
  640. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  641. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +231 -0
  642. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  643. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  644. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  645. package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
  646. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  647. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
  648. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  649. package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
  650. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  651. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
  652. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  653. package/build/dist/Models/DatabaseModels/Host.js +231 -0
  654. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  655. package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
  656. package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
  657. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
  658. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
  659. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
  660. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  661. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  662. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  663. package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
  664. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  665. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
  666. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  667. package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
  668. package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
  669. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
  670. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
  671. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
  672. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
  673. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
  674. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
  675. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
  676. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
  677. package/build/dist/Models/DatabaseModels/PodmanHost.js +231 -0
  678. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  679. package/build/dist/Models/DatabaseModels/Project.js +61 -0
  680. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  681. package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
  682. package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
  683. package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
  684. package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
  685. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +231 -0
  686. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  687. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  688. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  689. package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
  690. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  691. package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
  692. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  693. package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
  694. package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
  695. package/build/dist/Models/DatabaseModels/Service.js +12 -0
  696. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  697. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
  698. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  699. package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
  700. package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
  701. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
  702. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  703. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +231 -0
  704. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  705. package/build/dist/Server/API/AIChatAPI.js +9 -103
  706. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  707. package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
  708. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  709. package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
  710. package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
  711. package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
  712. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  713. package/build/dist/Server/API/IncidentFormAPI.js +245 -0
  714. package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
  715. package/build/dist/Server/API/ProjectOIDC.js +16 -24
  716. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  717. package/build/dist/Server/API/ProjectSSO.js +16 -24
  718. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  719. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  720. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  721. package/build/dist/Server/API/StatusPageAPI.js +42 -75
  722. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  723. package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
  724. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
  725. package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
  726. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
  727. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
  728. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
  729. package/build/dist/Server/EnvironmentConfig.js +1 -1
  730. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  731. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  732. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
  733. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
  734. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
  735. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
  736. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
  737. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
  738. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
  739. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js.map +1 -0
  740. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  741. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  742. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  743. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  744. package/build/dist/Server/Middleware/IncidentFormRateLimit.js +559 -0
  745. package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
  746. package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
  747. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  748. package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
  749. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  750. package/build/dist/Server/Services/AIService.js +10 -13
  751. package/build/dist/Server/Services/AIService.js.map +1 -1
  752. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  753. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  754. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  755. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  756. package/build/dist/Server/Services/CephClusterService.js +104 -0
  757. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  758. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  759. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  760. package/build/dist/Server/Services/DockerHostService.js +104 -0
  761. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  762. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  763. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  764. package/build/dist/Server/Services/HostService.js +114 -2
  765. package/build/dist/Server/Services/HostService.js.map +1 -1
  766. package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
  767. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  768. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
  769. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  770. package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
  771. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  772. package/build/dist/Server/Services/IncidentFormService.js +1202 -0
  773. package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
  774. package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
  775. package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
  776. package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
  777. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  778. package/build/dist/Server/Services/IncidentService.js +106 -4
  779. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  780. package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
  781. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  782. package/build/dist/Server/Services/Index.js +14 -0
  783. package/build/dist/Server/Services/Index.js.map +1 -1
  784. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
  785. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
  786. package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
  787. package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
  788. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
  789. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
  790. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
  791. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
  792. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
  793. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
  794. package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
  795. package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
  796. package/build/dist/Server/Services/MessageQueueService.js +1140 -0
  797. package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
  798. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  799. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  800. package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
  801. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  802. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  803. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  804. package/build/dist/Server/Services/ProjectService.js +0 -54
  805. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  806. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  807. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  808. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  809. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  810. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  811. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  812. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  813. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  814. package/build/dist/Server/Services/RunnerJobService.js +344 -5
  815. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  816. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
  817. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  818. package/build/dist/Server/Services/StatusPageService.js +0 -46
  819. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  820. package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
  821. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  822. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  823. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  824. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  825. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  826. package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
  827. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  828. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  829. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  830. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
  831. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  832. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
  833. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  834. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
  835. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  836. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
  837. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  838. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
  839. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
  840. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  841. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
  842. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
  843. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
  844. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  845. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
  846. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
  847. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
  848. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  849. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  850. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
  851. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
  852. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
  853. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  854. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
  855. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  856. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
  857. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  858. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
  859. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  860. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1036 -32
  861. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  862. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +661 -41
  863. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  864. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  865. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  866. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +354 -0
  867. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  868. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  869. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  870. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  871. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  872. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  873. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  874. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  875. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  876. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +602 -0
  877. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  878. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +90 -14
  879. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  880. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +73 -10
  881. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  882. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +73 -10
  883. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  884. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
  885. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  886. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
  887. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
  888. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
  889. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
  890. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
  891. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  892. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
  893. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  894. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  895. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  896. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  897. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  898. package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
  899. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
  900. package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
  901. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  902. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
  903. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
  904. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  905. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  906. package/build/dist/Server/Utils/SameOriginRequest.js +151 -0
  907. package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
  908. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  909. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  910. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  911. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  912. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  913. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  914. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -1
  915. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  916. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
  917. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
  918. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
  919. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  920. package/build/dist/Server/Utils/VendorAssets.js +3 -3
  921. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  922. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  923. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  924. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  925. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  926. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  927. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +186 -210
  928. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  929. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +22 -16
  930. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  931. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  932. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  933. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +273 -111
  934. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  935. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  936. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  937. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  938. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  939. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  940. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  941. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  942. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  943. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  944. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  945. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  946. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  947. package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
  948. package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
  949. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  950. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  951. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  952. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  953. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
  954. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  955. package/build/dist/Types/Code/CodeType.js +2 -0
  956. package/build/dist/Types/Code/CodeType.js.map +1 -1
  957. package/build/dist/Types/Code/JsonSyntax.js +604 -0
  958. package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
  959. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
  960. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
  961. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
  962. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
  963. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
  964. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
  965. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
  966. package/build/dist/Types/Icon/IconProp.js +2 -0
  967. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  968. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
  969. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
  970. package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
  971. package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
  972. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
  973. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  974. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
  975. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
  976. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
  977. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
  978. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
  979. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
  980. package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
  981. package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
  982. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
  983. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
  984. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
  985. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
  986. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  987. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  988. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  989. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  990. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  991. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  992. package/build/dist/Types/Permission.js +274 -0
  993. package/build/dist/Types/Permission.js.map +1 -1
  994. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  995. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  996. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +281 -0
  997. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  998. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -0
  999. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  1000. package/build/dist/Types/Rules/RuleRun.js +4 -0
  1001. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  1002. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  1003. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  1004. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  1005. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  1006. package/build/dist/Types/Team/ComplianceNotificationChannel.js +4 -3
  1007. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
  1008. package/build/dist/Types/Team/ComplianceRule.js +48 -14
  1009. package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
  1010. package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
  1011. package/build/dist/Types/Telemetry/EntityType.js +16 -0
  1012. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  1013. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
  1014. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  1015. package/build/dist/Types/XML.js +23 -0
  1016. package/build/dist/Types/XML.js.map +1 -1
  1017. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
  1018. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  1019. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
  1020. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
  1021. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
  1022. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
  1023. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
  1024. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
  1025. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
  1026. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
  1027. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
  1028. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
  1029. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
  1030. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
  1031. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
  1032. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  1033. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
  1034. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
  1035. package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
  1036. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  1037. package/build/dist/UI/Components/Icon/Icon.js +8 -0
  1038. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  1039. package/build/dist/UI/Components/Input/Input.js +11 -3
  1040. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  1041. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
  1042. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  1043. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
  1044. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  1045. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  1046. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  1047. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
  1048. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  1049. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
  1050. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
  1051. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
  1052. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
  1053. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
  1054. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
  1055. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
  1056. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  1057. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
  1058. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
  1059. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
  1060. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1061. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
  1062. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
  1063. package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
  1064. package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
  1065. package/build/dist/UI/Components/Table/Table.js +30 -11
  1066. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  1067. package/build/dist/UI/Components/Table/TableRow.js +9 -2
  1068. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1069. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
  1070. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
  1071. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
  1072. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1073. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
  1074. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
  1075. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
  1076. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  1077. package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
  1078. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  1079. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
  1080. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
  1081. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  1082. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  1083. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  1084. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  1085. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  1086. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  1087. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  1088. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  1089. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  1090. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  1091. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  1092. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  1093. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  1094. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  1095. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  1096. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  1097. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  1098. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  1099. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  1100. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  1101. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  1102. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  1103. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  1104. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  1105. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  1106. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  1107. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  1108. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  1109. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  1110. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  1111. package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
  1112. package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
  1113. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
  1114. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
  1115. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
  1116. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
  1117. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  1118. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  1119. package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
  1120. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  1121. package/package.json +1 -3
  1122. package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
  1123. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
  1124. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
  1125. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
  1126. package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
  1127. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
  1128. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
  1129. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
  1130. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
  1131. package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
  1132. package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
  1133. package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
  1134. package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
  1135. package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
  1136. package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
  1137. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
  1138. package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
  1139. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
  1140. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
  1141. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
  1142. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
  1143. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
  1144. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
  1145. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
  1146. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
@@ -0,0 +1,1653 @@
1
+ jest.mock("../../../Server/Infrastructure/Redis", () => {
2
+ return {
3
+ __esModule: true,
4
+ default: {
5
+ getClient: jest.fn(),
6
+ isConnected: jest.fn(),
7
+ },
8
+ };
9
+ });
10
+
11
+ jest.mock("../../../Server/Utils/Logger", () => {
12
+ return {
13
+ __esModule: true,
14
+ default: {
15
+ debug: jest.fn(),
16
+ info: jest.fn(),
17
+ warn: jest.fn(),
18
+ error: jest.fn(),
19
+ },
20
+ getLogAttributesFromRequest: jest.fn().mockReturnValue({}),
21
+ };
22
+ });
23
+
24
+ import IncidentFormAPI, {
25
+ INCIDENT_FORM_FOREIGN_PAGE_MESSAGE,
26
+ INCIDENT_FORM_SUBMISSION_BODY_MESSAGE,
27
+ } from "../../../Server/API/IncidentFormAPI";
28
+ import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
29
+ import Redis from "../../../Server/Infrastructure/Redis";
30
+ import {
31
+ INCIDENT_FORM_RATE_LIMIT_UNAVAILABLE_MESSAGE,
32
+ INCIDENT_FORM_READ_RATE_LIMIT_MESSAGE,
33
+ INCIDENT_FORM_SUBMIT_RATE_LIMIT_MESSAGE,
34
+ INCIDENT_FORM_TOTAL_RATE_LIMIT_MESSAGE,
35
+ } from "../../../Server/Middleware/IncidentFormRateLimit";
36
+ import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
37
+ import AccessTokenService from "../../../Server/Services/AccessTokenService";
38
+ import IncidentCustomFieldService from "../../../Server/Services/IncidentCustomFieldService";
39
+ import IncidentFormService, {
40
+ INCIDENT_FORM_NETWORK_NOT_ALLOWED_MESSAGE,
41
+ INCIDENT_FORM_NOT_AVAILABLE_MESSAGE,
42
+ } from "../../../Server/Services/IncidentFormService";
43
+ import IncidentFormSubmissionService from "../../../Server/Services/IncidentFormSubmissionService";
44
+ import IncidentInternalNoteService from "../../../Server/Services/IncidentInternalNoteService";
45
+ import IncidentService from "../../../Server/Services/IncidentService";
46
+ import IncidentSeverityService from "../../../Server/Services/IncidentSeverityService";
47
+ import ProjectService from "../../../Server/Services/ProjectService";
48
+ import UserService from "../../../Server/Services/UserService";
49
+ import CaptchaUtil from "../../../Server/Utils/Captcha";
50
+ import CookieUtil from "../../../Server/Utils/Cookie";
51
+ import { ExpressRouter } from "../../../Server/Utils/Express";
52
+ import JSONWebToken from "../../../Server/Utils/JsonWebToken";
53
+ import SameOriginRequest from "../../../Server/Utils/SameOriginRequest";
54
+ import { expressErrorHandler } from "../../../Server/Utils/StartServer";
55
+ import Incident from "../../../Models/DatabaseModels/Incident";
56
+ import IncidentForm from "../../../Models/DatabaseModels/IncidentForm";
57
+ import Dictionary from "../../../Types/Dictionary";
58
+ import BadDataException from "../../../Types/Exception/BadDataException";
59
+ import Email from "../../../Types/Email";
60
+ import {
61
+ INCIDENT_FORM_PAGE_HEADER,
62
+ INCIDENT_FORM_PAGE_HEADER_VALUE,
63
+ IncidentFormFieldSetting,
64
+ } from "../../../Types/Incident/IncidentFormPublic";
65
+ import { JSONObject } from "../../../Types/JSON";
66
+ import Name from "../../../Types/Name";
67
+ import ObjectID from "../../../Types/ObjectID";
68
+ import {
69
+ afterAll,
70
+ afterEach,
71
+ beforeAll,
72
+ beforeEach,
73
+ describe,
74
+ expect,
75
+ it,
76
+ } from "@jest/globals";
77
+ import CookieParser from "cookie-parser";
78
+ import express from "express";
79
+ import http from "http";
80
+ import jwt from "jsonwebtoken";
81
+ import { AddressInfo } from "net";
82
+ import timers from "timers";
83
+
84
+ /*
85
+ * A handler's refusal travels as next(err), and Express leaves a router
86
+ * whose layers are exhausted with setImmediate(done, err). Common's jest
87
+ * environment is jsdom, which has no setImmediate - without this every
88
+ * refusal would hang the request instead of reaching the error handler. Lend
89
+ * jsdom Node's own, as MultipartFormData.test.ts does.
90
+ */
91
+ if (
92
+ typeof (globalThis as unknown as { setImmediate?: unknown }).setImmediate !==
93
+ "function"
94
+ ) {
95
+ (globalThis as unknown as { setImmediate: unknown }).setImmediate =
96
+ timers.setImmediate;
97
+ }
98
+
99
+ /*
100
+ * The public incident form routes, driven through a real Express app with
101
+ * cookie-parser, the JSON body parser and the production error handler -
102
+ * the real IncidentFormAPI router, the real limiter, the real user
103
+ * middleware and the real IncidentFormService. Only the data layer (and the
104
+ * captcha provider and Redis) are stubbed.
105
+ *
106
+ * It pins what the public form page has to handle - the status codes and
107
+ * the messages - and the one property the page depends on most: a visitor's
108
+ * own OneUptime session cookie, which rides along because the page is served
109
+ * on the dashboard's host, never turns into a 401. A 401 would send the
110
+ * dashboard client to the login page (losing the report being typed), or
111
+ * sign the visitor out of their real session. A cookie that no longer
112
+ * decodes, an expired one and a junk one are all simply ignored, and a
113
+ * valid one is ignored too: forms are anonymous.
114
+ */
115
+
116
+ const PROJECT_ID: ObjectID = new ObjectID(
117
+ "5d7f3c0a-4c55-4d3e-9a3e-2d4a7d1c9f01",
118
+ );
119
+ const OTHER_PROJECT_ID: string = "5d7f3c0a-4c55-4d3e-9a3e-2d4a7d1c9f02";
120
+ const FORM_ID: string = "a1b2c3d4-0000-4000-8000-0000000000f1";
121
+ const SHARE_KEY: string = "7c9e6679-7425-40de-944b-e07fc1f90ae7";
122
+ const OFF_SHARE_KEY: string = "7c9e6679-7425-40de-944b-e07fc1f90ae8";
123
+ const LOCKED_SHARE_KEY: string = "7c9e6679-7425-40de-944b-e07fc1f90ae9";
124
+ const UNKNOWN_SHARE_KEY: string = "0f8fad5b-d9cb-469f-a165-70867728950e";
125
+ const SEVERITY_ID: string = "b0000000-0000-4000-8000-0000000000f1";
126
+ const INCIDENT_ID: string = "e0000000-0000-4000-8000-0000000000f1";
127
+ const USER_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
128
+
129
+ // The client address our proxy would append, and one outside the allowlist.
130
+ const ALLOWED_IP: string = "198.51.100.23";
131
+ const OTHER_IP: string = "203.0.113.7";
132
+
133
+ // This instance, as HTTP_PROTOCOL and HOST configure it, and another site.
134
+ const INSTANCE_ORIGIN: string = "https://oneuptime.example.com";
135
+ const FOREIGN_ORIGIN: string = "https://evil.example";
136
+
137
+ const INVALID_ACCESS_TOKEN_MESSAGE: string =
138
+ "AccessToken is invalid or expired. Please refresh your token.";
139
+
140
+ type MockedFn = ReturnType<typeof jest.fn>;
141
+
142
+ const getClientMock: MockedFn = Redis.getClient as unknown as MockedFn;
143
+ const isConnectedMock: MockedFn = Redis.isConnected as unknown as MockedFn;
144
+
145
+ /*
146
+ * A counting Redis fake. `countFloor` makes every counter read at least
147
+ * that much, which is how a test puts a caller over a limit without firing
148
+ * hundreds of requests first.
149
+ */
150
+ class FakeRedisClient {
151
+ public counters: Map<string, number> = new Map();
152
+ public countFloor: number = 0;
153
+
154
+ // A pipeline counting a key this matches fails, as a broken Redis would.
155
+ public failKeysMatching: RegExp | null = null;
156
+
157
+ public pipeline(): FakePipeline {
158
+ return new FakePipeline(this);
159
+ }
160
+
161
+ public keysMatching(fragment: string): Array<string> {
162
+ return Array.from(this.counters.keys()).filter((key: string) => {
163
+ return key.includes(fragment);
164
+ });
165
+ }
166
+ }
167
+
168
+ type QueuedCommand = () => [Error | null, unknown];
169
+
170
+ class FakePipeline {
171
+ private commands: Array<QueuedCommand> = [];
172
+ private keys: Array<string> = [];
173
+
174
+ public constructor(private client: FakeRedisClient) {}
175
+
176
+ public incr(key: string): FakePipeline {
177
+ this.keys.push(key);
178
+ this.commands.push((): [Error | null, unknown] => {
179
+ const next: number = Math.max(
180
+ (this.client.counters.get(key) || 0) + 1,
181
+ this.client.countFloor,
182
+ );
183
+ this.client.counters.set(key, next);
184
+ return [null, next];
185
+ });
186
+
187
+ return this;
188
+ }
189
+
190
+ public expire(): FakePipeline {
191
+ this.commands.push((): [Error | null, unknown] => {
192
+ return [null, 1];
193
+ });
194
+
195
+ return this;
196
+ }
197
+
198
+ public async exec(): Promise<unknown> {
199
+ const failKeysMatching: RegExp | null = this.client.failKeysMatching;
200
+
201
+ if (
202
+ failKeysMatching &&
203
+ this.keys.some((key: string): boolean => {
204
+ return failKeysMatching.test(key);
205
+ })
206
+ ) {
207
+ throw new Error("connection reset");
208
+ }
209
+
210
+ return this.commands.map((command: QueuedCommand) => {
211
+ return command();
212
+ });
213
+ }
214
+ }
215
+
216
+ function buildForm(data: {
217
+ shareKey: string;
218
+ isEnabled?: boolean;
219
+ ipWhitelist?: string;
220
+ }): IncidentForm {
221
+ const form: IncidentForm = new IncidentForm();
222
+ form._id = FORM_ID;
223
+ form.projectId = PROJECT_ID;
224
+ form.name = "Report a Problem";
225
+ form.description = "Tell us what is broken.";
226
+ form.isEnabled = data.isEnabled !== false;
227
+ form.shareKey = new ObjectID(data.shareKey);
228
+ form.incidentSeverityId = new ObjectID(SEVERITY_ID);
229
+ form.allowReporterToChooseSeverity = false;
230
+ form.descriptionSetting = IncidentFormFieldSetting.Optional;
231
+ form.customFieldSettings = {};
232
+ form.isReporterDetailsRequired = true;
233
+ form.successMessage = "Thanks - we are on it.";
234
+ form.ipWhitelist = data.ipWhitelist || "";
235
+ return form;
236
+ }
237
+
238
+ const FORMS: Array<IncidentForm> = [
239
+ buildForm({ shareKey: SHARE_KEY }),
240
+ buildForm({ shareKey: OFF_SHARE_KEY, isEnabled: false }),
241
+ buildForm({ shareKey: LOCKED_SHARE_KEY, ipWhitelist: "198.51.100.0/24" }),
242
+ ];
243
+
244
+ const PUBLIC_FORM: JSONObject = {
245
+ name: "Report a Problem",
246
+ description: "Tell us what is broken.",
247
+ descriptionSetting: "Optional",
248
+ isReporterDetailsRequired: true,
249
+ customFields: [],
250
+ isCaptchaRequired: false,
251
+ };
252
+
253
+ const ANSWERS: JSONObject = {
254
+ title: "Checkout is down",
255
+ description: "Every order fails.",
256
+ reporterName: "Jane Doe",
257
+ reporterEmail: "jane@example.com",
258
+ };
259
+
260
+ const SUBMISSION_RESULT: JSONObject = {
261
+ incidentNumber: "INC-42",
262
+ successMessage: "Thanks - we are on it.",
263
+ };
264
+
265
+ // Signed with a secret this instance no longer uses: a rotation happened.
266
+ const STALE_ACCESS_TOKEN: string = jwt.sign(
267
+ {
268
+ userId: USER_ID.toString(),
269
+ email: "viewer@example.com",
270
+ name: "Viewer",
271
+ isMasterAdmin: false,
272
+ isGlobalLogin: true,
273
+ sessionId: "44444444-4444-4444-8444-444444444444",
274
+ },
275
+ `${EncryptionSecret.toString()}-before-rotation`,
276
+ { expiresIn: 15 * 60 },
277
+ );
278
+
279
+ // Signed with the right secret, but past its lifetime.
280
+ const EXPIRED_ACCESS_TOKEN: string = jwt.sign(
281
+ {
282
+ userId: USER_ID.toString(),
283
+ email: "viewer@example.com",
284
+ name: "Viewer",
285
+ isMasterAdmin: false,
286
+ isGlobalLogin: true,
287
+ sessionId: "44444444-4444-4444-8444-444444444444",
288
+ exp: Math.floor(Date.now() / 1000) - 60,
289
+ },
290
+ EncryptionSecret.toString(),
291
+ );
292
+
293
+ function validAccessToken(): string {
294
+ return JSONWebToken.signUserLoginToken({
295
+ tokenData: {
296
+ userId: USER_ID,
297
+ email: new Email("viewer@example.com"),
298
+ name: new Name("Viewer"),
299
+ timezone: null,
300
+ isMasterAdmin: false,
301
+ isGlobalLogin: true,
302
+ sessionId: ObjectID.generate(),
303
+ },
304
+ expiresInSeconds: 15 * 60,
305
+ });
306
+ }
307
+
308
+ interface HttpResult {
309
+ status: number;
310
+ headers: http.IncomingHttpHeaders;
311
+ body: JSONObject | null;
312
+ }
313
+
314
+ function send(data: {
315
+ port: number;
316
+ method: "GET" | "POST";
317
+ path: string;
318
+ clientIp?: string;
319
+ cookies?: Dictionary<string> | undefined;
320
+ headers?: http.OutgoingHttpHeaders | undefined;
321
+ /*
322
+ * Without the header the form page's API client adds to every request:
323
+ * what an <img>, a link, a frame or a no-cors fetch sends.
324
+ */
325
+ withoutPageHeader?: boolean | undefined;
326
+ body?: unknown;
327
+ // Sent as it is, with the content-type the headers give: not JSON.
328
+ rawBody?: string | undefined;
329
+ }): Promise<HttpResult> {
330
+ return new Promise<HttpResult>(
331
+ (resolve: (result: HttpResult) => void, reject: (e: Error) => void) => {
332
+ const payload: string =
333
+ data.method === "POST" && data.body !== undefined
334
+ ? JSON.stringify(data.body)
335
+ : "";
336
+
337
+ const headers: http.OutgoingHttpHeaders = {
338
+ // What the public form page's API client sends.
339
+ tenantid: "",
340
+ ...(data.withoutPageHeader
341
+ ? {}
342
+ : { [INCIDENT_FORM_PAGE_HEADER]: INCIDENT_FORM_PAGE_HEADER_VALUE }),
343
+ // What our proxy appends: the client address, the trusted hop.
344
+ "x-forwarded-for": data.clientIp || ALLOWED_IP,
345
+ ...(data.headers || {}),
346
+ };
347
+
348
+ const cookieHeader: string = Object.entries(data.cookies || {})
349
+ .map(([name, value]: [string, string]) => {
350
+ return `${name}=${value}`;
351
+ })
352
+ .join("; ");
353
+
354
+ if (cookieHeader) {
355
+ headers["cookie"] = cookieHeader;
356
+ }
357
+
358
+ if (payload) {
359
+ headers["content-type"] = "application/json";
360
+ headers["content-length"] = Buffer.byteLength(payload);
361
+ }
362
+
363
+ if (data.rawBody !== undefined) {
364
+ headers["content-length"] = Buffer.byteLength(data.rawBody);
365
+ }
366
+
367
+ const request: http.ClientRequest = http.request(
368
+ {
369
+ host: "127.0.0.1",
370
+ port: data.port,
371
+ path: data.path,
372
+ method: data.method,
373
+ headers,
374
+ },
375
+ (response: http.IncomingMessage) => {
376
+ const chunks: Array<Buffer> = [];
377
+
378
+ response.on("data", (chunk: Buffer) => {
379
+ chunks.push(chunk);
380
+ });
381
+
382
+ response.on("end", () => {
383
+ const raw: string = Buffer.concat(chunks).toString("utf8");
384
+ let body: JSONObject | null = null;
385
+
386
+ try {
387
+ body = raw ? (JSON.parse(raw) as JSONObject) : null;
388
+ } catch {
389
+ body = { raw };
390
+ }
391
+
392
+ resolve({
393
+ status: response.statusCode || 0,
394
+ headers: response.headers,
395
+ body,
396
+ });
397
+ });
398
+ },
399
+ );
400
+
401
+ request.on("error", reject);
402
+
403
+ if (payload) {
404
+ request.write(payload);
405
+ }
406
+
407
+ if (data.rawBody !== undefined) {
408
+ request.write(data.rawBody);
409
+ }
410
+
411
+ request.end();
412
+ },
413
+ );
414
+ }
415
+
416
+ /*
417
+ * A limiter refusal is written by Response.sendErrorResponse ({ message });
418
+ * a handler's refusal goes through the production error handler ({ error }).
419
+ * The page's API client reads either.
420
+ */
421
+ function errorMessageOf(result: HttpResult): unknown {
422
+ return result.body?.["message"] ?? result.body?.["error"];
423
+ }
424
+
425
+ // Only what the page would see: the status and the body.
426
+ function seen(result: HttpResult): { status: number; body: unknown } {
427
+ return { status: result.status, body: result.body };
428
+ }
429
+
430
+ describe("the public incident form routes over HTTP", () => {
431
+ let server: http.Server;
432
+ let port: number;
433
+ let client: FakeRedisClient;
434
+ let incidentCreate: MockedFn;
435
+ let getPublicForm: MockedFn;
436
+ let submitPublicForm: MockedFn;
437
+ let onPlan: MockedFn;
438
+
439
+ const readPath: (shareKey: string) => string = (shareKey: string) => {
440
+ return `/api/incident-form/public/${shareKey}`;
441
+ };
442
+
443
+ const submitPath: (shareKey: string) => string = (shareKey: string) => {
444
+ return `/api/incident-form/public/${shareKey}/submit`;
445
+ };
446
+
447
+ beforeAll(async () => {
448
+ const app: express.Express = express();
449
+ app.use(CookieParser());
450
+ app.use(express.json());
451
+ /*
452
+ * As StartServer mounts it: "data[title]=..." from a plain HTML form
453
+ * becomes the very object a JSON body gives.
454
+ */
455
+ app.use(express.urlencoded({ extended: true }));
456
+ app.use("/api", new IncidentFormAPI().getRouter());
457
+ app.use(expressErrorHandler);
458
+
459
+ server = http.createServer(app);
460
+
461
+ await new Promise<void>((resolve: () => void) => {
462
+ server.listen(0, "127.0.0.1", resolve);
463
+ });
464
+
465
+ port = (server.address() as AddressInfo).port;
466
+ });
467
+
468
+ afterAll(async () => {
469
+ await new Promise<void>((resolve: () => void) => {
470
+ server.close(() => {
471
+ resolve();
472
+ });
473
+ });
474
+ });
475
+
476
+ beforeEach(() => {
477
+ client = new FakeRedisClient();
478
+ getClientMock.mockReturnValue(client);
479
+ isConnectedMock.mockReturnValue(true);
480
+
481
+ jest
482
+ .spyOn(IncidentFormService, "findOneBy")
483
+ .mockImplementation((async (findBy: {
484
+ query: { shareKey?: ObjectID };
485
+ }): Promise<IncidentForm | null> => {
486
+ return (
487
+ FORMS.find((form: IncidentForm) => {
488
+ return (
489
+ form.shareKey?.toString() === findBy.query.shareKey?.toString()
490
+ );
491
+ }) || null
492
+ );
493
+ }) as never);
494
+
495
+ jest.spyOn(IncidentCustomFieldService, "findBy").mockResolvedValue([]);
496
+ jest.spyOn(IncidentSeverityService, "findBy").mockResolvedValue([]);
497
+
498
+ /*
499
+ * The plan check reads the project's subscription, which this suite has
500
+ * no database for. Left real, it would pass only where billing is off:
501
+ * CI's Common job runs with BILLING_ENABLED=true, the read fails, the
502
+ * check fails closed, and every form here would answer "not available".
503
+ * The check itself is pinned in IncidentFormPublicForm.test.ts; the
504
+ * off-plan test below turns it the other way.
505
+ */
506
+ onPlan = jest
507
+ .spyOn(IncidentFormService, "isProjectOnPlan")
508
+ .mockResolvedValue(true as never) as unknown as MockedFn;
509
+
510
+ // HOST is not configured in a unit run; the instance says it is this.
511
+ jest
512
+ .spyOn(SameOriginRequest, "getInstanceOrigin")
513
+ .mockReturnValue(INSTANCE_ORIGIN);
514
+
515
+ incidentCreate = jest
516
+ .spyOn(IncidentService, "create")
517
+ .mockImplementation((async (): Promise<Incident> => {
518
+ const incident: Incident = new Incident();
519
+ incident._id = INCIDENT_ID;
520
+ incident.projectId = PROJECT_ID;
521
+ incident.incidentNumber = 42;
522
+ incident.incidentNumberWithPrefix = "INC-42";
523
+ return incident;
524
+ }) as never) as unknown as MockedFn;
525
+
526
+ jest
527
+ .spyOn(IncidentFormSubmissionService, "create")
528
+ .mockImplementation((async (createBy: { data: unknown }) => {
529
+ return createBy.data;
530
+ }) as never);
531
+ jest
532
+ .spyOn(IncidentInternalNoteService, "create")
533
+ .mockImplementation((async (createBy: { data: unknown }) => {
534
+ return createBy.data;
535
+ }) as never);
536
+
537
+ jest.spyOn(CaptchaUtil, "isCaptchaEnabled").mockReturnValue(false);
538
+
539
+ /*
540
+ * What a signed-in visitor's valid session touches in the user
541
+ * middleware. Nothing here reads the database.
542
+ */
543
+ jest.spyOn(UserService, "isUserBlocked").mockResolvedValue(false);
544
+ jest.spyOn(UserService, "updateLastActive").mockResolvedValue(undefined);
545
+ jest
546
+ .spyOn(AccessTokenService, "getUserGlobalAccessPermission")
547
+ .mockResolvedValue(null);
548
+ jest.spyOn(ProjectService, "updateLastActive").mockResolvedValue(undefined);
549
+
550
+ getPublicForm = jest.spyOn(
551
+ IncidentFormService,
552
+ "getPublicForm",
553
+ ) as unknown as MockedFn;
554
+ submitPublicForm = jest.spyOn(
555
+ IncidentFormService,
556
+ "submitPublicForm",
557
+ ) as unknown as MockedFn;
558
+ });
559
+
560
+ afterEach(() => {
561
+ jest.restoreAllMocks();
562
+ });
563
+
564
+ describe("200", () => {
565
+ it("serves the form's questions, uncached", async () => {
566
+ const result: HttpResult = await send({
567
+ port,
568
+ method: "GET",
569
+ path: readPath(SHARE_KEY),
570
+ });
571
+
572
+ expect(seen(result)).toEqual({ status: 200, body: PUBLIC_FORM });
573
+ expect(result.headers["cache-control"]).toBe(
574
+ "no-store, no-cache, must-revalidate",
575
+ );
576
+ expect(result.headers["pragma"]).toBe("no-cache");
577
+
578
+ // The plan gate stays on the path; only its database read is stubbed.
579
+ expect(onPlan).toHaveBeenCalledTimes(1);
580
+ expect(String(onPlan.mock.calls[0]![0])).toBe(PROJECT_ID.toString());
581
+ });
582
+
583
+ it("declares the incident and answers with its number and the success message", async () => {
584
+ const result: HttpResult = await send({
585
+ port,
586
+ method: "POST",
587
+ path: submitPath(SHARE_KEY),
588
+ body: { data: ANSWERS },
589
+ });
590
+
591
+ expect(seen(result)).toEqual({ status: 200, body: SUBMISSION_RESULT });
592
+ expect(result.headers["cache-control"]).toBe(
593
+ "no-store, no-cache, must-revalidate",
594
+ );
595
+ expect(incidentCreate).toHaveBeenCalledTimes(1);
596
+ });
597
+
598
+ /*
599
+ * The tenant the user middleware reads from a header or the body is
600
+ * never where the incident goes: that is always the form's project.
601
+ */
602
+ it("declares in the form's project whatever tenant the request names", async () => {
603
+ await send({
604
+ port,
605
+ method: "POST",
606
+ path: submitPath(SHARE_KEY),
607
+ headers: { tenantid: OTHER_PROJECT_ID },
608
+ body: { data: ANSWERS, projectId: OTHER_PROJECT_ID },
609
+ });
610
+
611
+ const created: Incident = (
612
+ incidentCreate.mock.calls[0]![0] as { data: Incident }
613
+ ).data;
614
+
615
+ expect(created.projectId?.toString()).toBe(PROJECT_ID.toString());
616
+ });
617
+
618
+ it("takes the share key in any case", async () => {
619
+ const result: HttpResult = await send({
620
+ port,
621
+ method: "GET",
622
+ path: readPath(SHARE_KEY.toUpperCase()),
623
+ });
624
+
625
+ expect(result.status).toBe(200);
626
+ });
627
+ });
628
+
629
+ describe("400", () => {
630
+ it("names every problem with the answers, and declares nothing", async () => {
631
+ const result: HttpResult = await send({
632
+ port,
633
+ method: "POST",
634
+ path: submitPath(SHARE_KEY),
635
+ body: { data: { title: " ", reporterEmail: "jane" } },
636
+ });
637
+
638
+ expect(result.status).toBe(400);
639
+ expect(errorMessageOf(result)).toBe(
640
+ "Title is required. Your Name is required. Your Email is not a valid email address.",
641
+ );
642
+ expect(incidentCreate).not.toHaveBeenCalled();
643
+ });
644
+
645
+ it.each([
646
+ ["no body", undefined],
647
+ ["a body without answers", { title: "Down" }],
648
+ ["answers that are not an object", { data: "Down" }],
649
+ ])("refuses %s", async (_label: string, body: unknown) => {
650
+ const result: HttpResult = await send({
651
+ port,
652
+ method: "POST",
653
+ path: submitPath(SHARE_KEY),
654
+ body,
655
+ });
656
+
657
+ expect(result.status).toBe(400);
658
+ expect(errorMessageOf(result)).toBe(
659
+ INCIDENT_FORM_SUBMISSION_BODY_MESSAGE,
660
+ );
661
+ expect(submitPublicForm).not.toHaveBeenCalled();
662
+ });
663
+
664
+ /*
665
+ * The provider round trip is the one thing stubbed here: its refusal is
666
+ * a BadDataException, exactly as CaptchaUtil throws it.
667
+ */
668
+ it("refuses a submission the captcha provider rejects, when the instance has a captcha on", async () => {
669
+ jest.spyOn(CaptchaUtil, "isCaptchaEnabled").mockReturnValue(true);
670
+ const verifyCaptcha: MockedFn = jest
671
+ .spyOn(CaptchaUtil, "verifyCaptcha")
672
+ .mockRejectedValue(
673
+ new BadDataException(
674
+ "Captcha verification failed. Please try again.",
675
+ ),
676
+ ) as unknown as MockedFn;
677
+
678
+ const result: HttpResult = await send({
679
+ port,
680
+ method: "POST",
681
+ path: submitPath(SHARE_KEY),
682
+ body: { data: ANSWERS, captchaToken: "solved-token" },
683
+ });
684
+
685
+ expect(result.status).toBe(400);
686
+ expect(errorMessageOf(result)).toBe(
687
+ "Captcha verification failed. Please try again.",
688
+ );
689
+ expect(verifyCaptcha).toHaveBeenCalledWith({
690
+ token: "solved-token",
691
+ remoteIp: ALLOWED_IP,
692
+ });
693
+ expect(incidentCreate).not.toHaveBeenCalled();
694
+ });
695
+
696
+ it("declares once the captcha provider accepts the token", async () => {
697
+ jest.spyOn(CaptchaUtil, "isCaptchaEnabled").mockReturnValue(true);
698
+ jest.spyOn(CaptchaUtil, "verifyCaptcha").mockResolvedValue(undefined);
699
+
700
+ const result: HttpResult = await send({
701
+ port,
702
+ method: "POST",
703
+ path: submitPath(SHARE_KEY),
704
+ body: { data: ANSWERS, captchaToken: "solved-token" },
705
+ });
706
+
707
+ expect(seen(result)).toEqual({ status: 200, body: SUBMISSION_RESULT });
708
+ });
709
+ });
710
+
711
+ describe("403", () => {
712
+ it("refuses to show a form to a network it does not allow", async () => {
713
+ const result: HttpResult = await send({
714
+ port,
715
+ method: "GET",
716
+ path: readPath(LOCKED_SHARE_KEY),
717
+ clientIp: OTHER_IP,
718
+ });
719
+
720
+ expect(result.status).toBe(403);
721
+ expect(errorMessageOf(result)).toBe(
722
+ INCIDENT_FORM_NETWORK_NOT_ALLOWED_MESSAGE,
723
+ );
724
+ });
725
+
726
+ it("refuses a submission from a network it does not allow", async () => {
727
+ const result: HttpResult = await send({
728
+ port,
729
+ method: "POST",
730
+ path: submitPath(LOCKED_SHARE_KEY),
731
+ clientIp: OTHER_IP,
732
+ body: { data: ANSWERS },
733
+ });
734
+
735
+ expect(result.status).toBe(403);
736
+ expect(errorMessageOf(result)).toBe(
737
+ INCIDENT_FORM_NETWORK_NOT_ALLOWED_MESSAGE,
738
+ );
739
+ expect(incidentCreate).not.toHaveBeenCalled();
740
+ });
741
+
742
+ /*
743
+ * The address checked is the one our proxy appended, not one the caller
744
+ * wrote into X-Forwarded-For themselves.
745
+ */
746
+ it("cannot be talked round by a forged X-Forwarded-For entry", async () => {
747
+ const result: HttpResult = await send({
748
+ port,
749
+ method: "GET",
750
+ path: readPath(LOCKED_SHARE_KEY),
751
+ clientIp: `${ALLOWED_IP}, ${OTHER_IP}`,
752
+ });
753
+
754
+ expect(result.status).toBe(403);
755
+ });
756
+
757
+ it("serves an allowed network", async () => {
758
+ const result: HttpResult = await send({
759
+ port,
760
+ method: "GET",
761
+ path: readPath(LOCKED_SHARE_KEY),
762
+ clientIp: ALLOWED_IP,
763
+ });
764
+
765
+ expect(result.status).toBe(200);
766
+ });
767
+ });
768
+
769
+ /*
770
+ * Any website can have a visitor's browser send these requests - the API
771
+ * answers every origin, and a plain HTML form or an <img> needs no
772
+ * preflight - from the visitor's own address: inside the form's IP
773
+ * allowlist, and on the budget everybody behind that address shares. Such
774
+ * a request is refused before anything counts it or looks the link up.
775
+ * The form's own page is served, and so is a caller that is not a browser
776
+ * but sends what the page sends; a read must carry the page's header and
777
+ * a submission's body must be JSON, whoever sends them.
778
+ */
779
+ describe("a request another site's page had a browser send", () => {
780
+ // What a plain HTML form on another site posts.
781
+ const FORGED_FORM: string =
782
+ "data%5Btitle%5D=Forged+outage&data%5BreporterName%5D=CEO&data%5BreporterEmail%5D=ceo%40corp.example";
783
+
784
+ const OWN_PAGE: http.OutgoingHttpHeaders = {
785
+ origin: INSTANCE_ORIGIN,
786
+ "sec-fetch-site": "same-origin",
787
+ };
788
+
789
+ it("refuses a plain HTML form another site auto-posts from inside the allowlist, before anything counts it", async () => {
790
+ const result: HttpResult = await send({
791
+ port,
792
+ method: "POST",
793
+ path: submitPath(LOCKED_SHARE_KEY),
794
+ clientIp: ALLOWED_IP,
795
+ headers: {
796
+ origin: FOREIGN_ORIGIN,
797
+ "sec-fetch-site": "cross-site",
798
+ "content-type": "application/x-www-form-urlencoded",
799
+ },
800
+ rawBody: FORGED_FORM,
801
+ });
802
+
803
+ expect(result.status).toBe(403);
804
+ expect(errorMessageOf(result)).toBe(INCIDENT_FORM_FOREIGN_PAGE_MESSAGE);
805
+ expect(submitPublicForm).not.toHaveBeenCalled();
806
+ expect(incidentCreate).not.toHaveBeenCalled();
807
+ expect(client.counters.size).toBe(0);
808
+ });
809
+
810
+ // A preflighted JSON request carries the same Origin.
811
+ it("refuses the same request sent as JSON: the body's type is not what stops it", async () => {
812
+ const result: HttpResult = await send({
813
+ port,
814
+ method: "POST",
815
+ path: submitPath(LOCKED_SHARE_KEY),
816
+ clientIp: ALLOWED_IP,
817
+ headers: { origin: FOREIGN_ORIGIN, "sec-fetch-site": "cross-site" },
818
+ body: { data: ANSWERS },
819
+ });
820
+
821
+ expect(result.status).toBe(403);
822
+ expect(errorMessageOf(result)).toBe(INCIDENT_FORM_FOREIGN_PAGE_MESSAGE);
823
+ expect(incidentCreate).not.toHaveBeenCalled();
824
+ expect(client.counters.size).toBe(0);
825
+ });
826
+
827
+ it.each([
828
+ ["a cross-site read", { "sec-fetch-site": "cross-site" }],
829
+ ["a read from another site's page", { origin: FOREIGN_ORIGIN }],
830
+ ["a read from an opaque origin (a sandboxed frame)", { origin: "null" }],
831
+ /*
832
+ * A status page on its owner's domain, or a DNS-rebinding name pointed
833
+ * at this server: the browser calls it same-origin, but it is not
834
+ * this instance's page.
835
+ */
836
+ [
837
+ "a read from another host the browser calls same-origin",
838
+ {
839
+ origin: "https://status.customer.example",
840
+ "sec-fetch-site": "same-origin",
841
+ },
842
+ ],
843
+ ])(
844
+ "refuses %s, before anything counts it",
845
+ async (_label: string, headers: http.OutgoingHttpHeaders) => {
846
+ const result: HttpResult = await send({
847
+ port,
848
+ method: "GET",
849
+ path: readPath(SHARE_KEY),
850
+ headers: headers,
851
+ });
852
+
853
+ expect(result.status).toBe(403);
854
+ expect(errorMessageOf(result)).toBe(INCIDENT_FORM_FOREIGN_PAGE_MESSAGE);
855
+ expect(getPublicForm).not.toHaveBeenCalled();
856
+ expect(client.counters.size).toBe(0);
857
+ },
858
+ );
859
+
860
+ /*
861
+ * Thirty-one posts to a made-up link from another site's page, through
862
+ * one visitor's browser: counted, they would have used up the 30 per 15
863
+ * minutes everybody behind that address shares across every form.
864
+ */
865
+ it("cannot use up the budget of the address it sends from", async () => {
866
+ for (let attempt: number = 0; attempt < 31; attempt++) {
867
+ const refused: HttpResult = await send({
868
+ port,
869
+ method: "POST",
870
+ path: submitPath(ObjectID.generate().toString()),
871
+ clientIp: ALLOWED_IP,
872
+ headers: { origin: FOREIGN_ORIGIN, "sec-fetch-site": "cross-site" },
873
+ body: { data: ANSWERS },
874
+ });
875
+
876
+ expect(refused.status).toBe(403);
877
+ }
878
+
879
+ const colleague: HttpResult = await send({
880
+ port,
881
+ method: "POST",
882
+ path: submitPath(SHARE_KEY),
883
+ clientIp: ALLOWED_IP,
884
+ headers: OWN_PAGE,
885
+ body: { data: ANSWERS },
886
+ });
887
+
888
+ expect(seen(colleague)).toEqual({
889
+ status: 200,
890
+ body: SUBMISSION_RESULT,
891
+ });
892
+
893
+ /*
894
+ * Only the colleague's own report was counted: once by address, once
895
+ * by form and address, and once against the form's ceiling.
896
+ */
897
+ expect(
898
+ client
899
+ .keysMatching(`:submit:i:${ALLOWED_IP}:`)
900
+ .map((key: string): number => {
901
+ return client.counters.get(key)!;
902
+ }),
903
+ ).toEqual([1]);
904
+ expect(Array.from(client.counters.values())).toEqual([1, 1, 1]);
905
+ });
906
+
907
+ it("serves the form's own page: its read and its report", async () => {
908
+ const read: HttpResult = await send({
909
+ port,
910
+ method: "GET",
911
+ path: readPath(SHARE_KEY),
912
+ headers: OWN_PAGE,
913
+ });
914
+
915
+ expect(seen(read)).toEqual({ status: 200, body: PUBLIC_FORM });
916
+
917
+ const report: HttpResult = await send({
918
+ port,
919
+ method: "POST",
920
+ path: submitPath(SHARE_KEY),
921
+ headers: OWN_PAGE,
922
+ body: { data: ANSWERS },
923
+ });
924
+
925
+ expect(seen(report)).toEqual({ status: 200, body: SUBMISSION_RESULT });
926
+ expect(incidentCreate).toHaveBeenCalledTimes(1);
927
+ });
928
+
929
+ /*
930
+ * Over plain HTTP - the self-hosting default - a browser sends another
931
+ * site's <img>, link or no-cors fetch with neither Origin nor
932
+ * Sec-Fetch-Site, so the check above cannot see where it came from. It
933
+ * cannot add a header, though, and the form's page always does.
934
+ */
935
+ it("refuses a read another site's <img> sends over plain HTTP, from inside the allowlist, before anything counts it", async () => {
936
+ const result: HttpResult = await send({
937
+ port,
938
+ method: "GET",
939
+ path: readPath(LOCKED_SHARE_KEY),
940
+ clientIp: ALLOWED_IP,
941
+ withoutPageHeader: true,
942
+ });
943
+
944
+ expect(result.status).toBe(403);
945
+ expect(errorMessageOf(result)).toBe(INCIDENT_FORM_FOREIGN_PAGE_MESSAGE);
946
+ expect(result.headers["cache-control"]).toBe(
947
+ "no-store, no-cache, must-revalidate",
948
+ );
949
+ expect(getPublicForm).not.toHaveBeenCalled();
950
+ expect(client.counters.size).toBe(0);
951
+ });
952
+
953
+ /*
954
+ * Six hundred and one images with made-up links, loaded by one
955
+ * visitor's browser in a loop: counted, they would have used up the 600
956
+ * reads a minute everybody behind that address shares, and a colleague
957
+ * opening a real form would be told to wait.
958
+ */
959
+ it("cannot use up the read budget of the address it sends from", async () => {
960
+ for (let attempt: number = 0; attempt < 601; attempt++) {
961
+ const refused: HttpResult = await send({
962
+ port,
963
+ method: "GET",
964
+ path: readPath(ObjectID.generate().toString()),
965
+ clientIp: ALLOWED_IP,
966
+ withoutPageHeader: true,
967
+ });
968
+
969
+ expect(refused.status).toBe(403);
970
+ }
971
+
972
+ // The page's own read, as a browser sends it over plain HTTP.
973
+ const colleague: HttpResult = await send({
974
+ port,
975
+ method: "GET",
976
+ path: readPath(SHARE_KEY),
977
+ clientIp: ALLOWED_IP,
978
+ });
979
+
980
+ expect(seen(colleague)).toEqual({ status: 200, body: PUBLIC_FORM });
981
+
982
+ // Only the colleague's read was counted: by address, and by form and address.
983
+ expect(
984
+ client
985
+ .keysMatching(`:read:i:${ALLOWED_IP}:`)
986
+ .map((key: string): number => {
987
+ return client.counters.get(key)!;
988
+ }),
989
+ ).toEqual([1]);
990
+ expect(Array.from(client.counters.values())).toEqual([1, 1]);
991
+ }, 60_000);
992
+
993
+ /*
994
+ * What a browser really sends with the page's own read: a GET to its own
995
+ * origin carries no Origin, and over plain HTTP no Sec-Fetch-Site either
996
+ * - only the page's header says where it came from. Over HTTPS the
997
+ * browser adds Sec-Fetch-Site: same-origin.
998
+ */
999
+ it.each([
1000
+ ["over plain HTTP: the page's header and nothing else", {}],
1001
+ ["over HTTPS", { "sec-fetch-site": "same-origin" }],
1002
+ ])(
1003
+ "serves the form page's own read %s",
1004
+ async (_label: string, headers: http.OutgoingHttpHeaders) => {
1005
+ const result: HttpResult = await send({
1006
+ port,
1007
+ method: "GET",
1008
+ path: readPath(LOCKED_SHARE_KEY),
1009
+ clientIp: ALLOWED_IP,
1010
+ headers: headers,
1011
+ });
1012
+
1013
+ expect(seen(result)).toEqual({ status: 200, body: PUBLIC_FORM });
1014
+ },
1015
+ );
1016
+
1017
+ /*
1018
+ * Opening the read's own address in the address bar is a navigation:
1019
+ * the browser says the visitor made it (Sec-Fetch-Site: none), but it
1020
+ * carries no header of a page's, so it is not the form's page reading.
1021
+ */
1022
+ it("refuses the read's address opened in the address bar", async () => {
1023
+ const result: HttpResult = await send({
1024
+ port,
1025
+ method: "GET",
1026
+ path: readPath(SHARE_KEY),
1027
+ headers: { "sec-fetch-site": "none" },
1028
+ withoutPageHeader: true,
1029
+ });
1030
+
1031
+ expect(result.status).toBe(403);
1032
+ expect(errorMessageOf(result)).toBe(INCIDENT_FORM_FOREIGN_PAGE_MESSAGE);
1033
+ expect(client.counters.size).toBe(0);
1034
+ });
1035
+
1036
+ /*
1037
+ * The page's header is asked of a read only. A submission cannot come
1038
+ * from another site's page unnoticed - every POST carries its Origin,
1039
+ * and a JSON body is preflighted - so a caller that is not a browser
1040
+ * submits without it.
1041
+ */
1042
+ it("takes a submission without the page's header", async () => {
1043
+ const result: HttpResult = await send({
1044
+ port,
1045
+ method: "POST",
1046
+ path: submitPath(SHARE_KEY),
1047
+ withoutPageHeader: true,
1048
+ body: { data: ANSWERS },
1049
+ });
1050
+
1051
+ expect(seen(result)).toEqual({ status: 200, body: SUBMISSION_RESULT });
1052
+ });
1053
+
1054
+ it("takes the JSON the page's API client sends, charset and all", async () => {
1055
+ const result: HttpResult = await send({
1056
+ port,
1057
+ method: "POST",
1058
+ path: submitPath(SHARE_KEY),
1059
+ headers: {
1060
+ ...OWN_PAGE,
1061
+ "content-type": "application/json;charset=UTF-8",
1062
+ },
1063
+ rawBody: JSON.stringify({ data: ANSWERS }),
1064
+ });
1065
+
1066
+ expect(seen(result)).toEqual({ status: 200, body: SUBMISSION_RESULT });
1067
+ });
1068
+
1069
+ it.each([
1070
+ ["from the form's own page", OWN_PAGE],
1071
+ ["from a caller that is not a browser", {}],
1072
+ ])(
1073
+ "refuses a submission that is not JSON %s, before anything counts it",
1074
+ async (_label: string, headers: http.OutgoingHttpHeaders) => {
1075
+ for (const contentType of [
1076
+ "application/x-www-form-urlencoded",
1077
+ "text/plain",
1078
+ ]) {
1079
+ const result: HttpResult = await send({
1080
+ port,
1081
+ method: "POST",
1082
+ path: submitPath(SHARE_KEY),
1083
+ headers: { ...headers, "content-type": contentType },
1084
+ rawBody: FORGED_FORM,
1085
+ });
1086
+
1087
+ expect(result.status).toBe(400);
1088
+ expect(errorMessageOf(result)).toBe(
1089
+ INCIDENT_FORM_SUBMISSION_BODY_MESSAGE,
1090
+ );
1091
+ }
1092
+
1093
+ expect(submitPublicForm).not.toHaveBeenCalled();
1094
+ expect(incidentCreate).not.toHaveBeenCalled();
1095
+ expect(client.counters.size).toBe(0);
1096
+ },
1097
+ );
1098
+ });
1099
+
1100
+ describe("404", () => {
1101
+ it.each([
1102
+ ["a link no form holds", UNKNOWN_SHARE_KEY],
1103
+ ["a form that is turned off", OFF_SHARE_KEY],
1104
+ ["a link that is not a share key", "report-a-problem"],
1105
+ ])(
1106
+ "answers %s with the one not-available message",
1107
+ async (_label: string, shareKey: string) => {
1108
+ const read: HttpResult = await send({
1109
+ port,
1110
+ method: "GET",
1111
+ path: readPath(shareKey),
1112
+ });
1113
+ const submitted: HttpResult = await send({
1114
+ port,
1115
+ method: "POST",
1116
+ path: submitPath(shareKey),
1117
+ body: { data: ANSWERS },
1118
+ });
1119
+
1120
+ for (const result of [read, submitted]) {
1121
+ expect(result.status).toBe(404);
1122
+ expect(errorMessageOf(result)).toBe(
1123
+ INCIDENT_FORM_NOT_AVAILABLE_MESSAGE,
1124
+ );
1125
+ }
1126
+
1127
+ expect(incidentCreate).not.toHaveBeenCalled();
1128
+ },
1129
+ );
1130
+
1131
+ it("answers a project off plan exactly as a link no form holds", async () => {
1132
+ jest
1133
+ .spyOn(IncidentFormService, "isProjectOnPlan")
1134
+ .mockResolvedValue(false as never);
1135
+
1136
+ const offPlan: HttpResult = await send({
1137
+ port,
1138
+ method: "GET",
1139
+ path: readPath(SHARE_KEY),
1140
+ });
1141
+ const unknown: HttpResult = await send({
1142
+ port,
1143
+ method: "GET",
1144
+ path: readPath(UNKNOWN_SHARE_KEY),
1145
+ });
1146
+
1147
+ expect(seen(offPlan)).toEqual(seen(unknown));
1148
+ expect(offPlan.status).toBe(404);
1149
+ });
1150
+ });
1151
+
1152
+ describe("429", () => {
1153
+ it("refuses a caller over the read limit, saying when to come back", async () => {
1154
+ client.countFloor = 10_000;
1155
+
1156
+ const result: HttpResult = await send({
1157
+ port,
1158
+ method: "GET",
1159
+ path: readPath(SHARE_KEY),
1160
+ });
1161
+
1162
+ expect(result.status).toBe(429);
1163
+ expect(errorMessageOf(result)).toBe(
1164
+ INCIDENT_FORM_READ_RATE_LIMIT_MESSAGE,
1165
+ );
1166
+ expect(Number(result.headers["retry-after"])).toBeGreaterThan(0);
1167
+ expect(getPublicForm).not.toHaveBeenCalled();
1168
+ });
1169
+
1170
+ it("refuses a caller over the submission limit, declaring nothing", async () => {
1171
+ for (let i: number = 0; i < 10; i++) {
1172
+ expect(
1173
+ (
1174
+ await send({
1175
+ port,
1176
+ method: "POST",
1177
+ path: submitPath(SHARE_KEY),
1178
+ body: { data: ANSWERS },
1179
+ })
1180
+ ).status,
1181
+ ).toBe(200);
1182
+ }
1183
+
1184
+ const result: HttpResult = await send({
1185
+ port,
1186
+ method: "POST",
1187
+ path: submitPath(SHARE_KEY),
1188
+ body: { data: ANSWERS },
1189
+ });
1190
+
1191
+ expect(result.status).toBe(429);
1192
+ expect(errorMessageOf(result)).toBe(
1193
+ INCIDENT_FORM_SUBMIT_RATE_LIMIT_MESSAGE,
1194
+ );
1195
+ expect(Number(result.headers["retry-after"])).toBeGreaterThan(0);
1196
+ expect(incidentCreate).toHaveBeenCalledTimes(10);
1197
+ });
1198
+ });
1199
+
1200
+ /*
1201
+ * The form's hourly ceiling - sixty incidents across every address -
1202
+ * bounds the pages a leaked link can cause. Only a submission that passed
1203
+ * every check spends it: if refused requests did, anyone holding the link
1204
+ * - even from outside the IP allowlist, or without solving the captcha -
1205
+ * could use it up with requests that declare nothing, and lock the form
1206
+ * for everybody, silently, hour after hour.
1207
+ */
1208
+ describe("the form's hourly ceiling", () => {
1209
+ // Ten requests from each of six addresses: within each one's own budget.
1210
+ async function sendFromSixAddresses(data: {
1211
+ firstOctet: number;
1212
+ shareKey: string;
1213
+ body: unknown;
1214
+ }): Promise<Array<number>> {
1215
+ const statuses: Array<number> = [];
1216
+
1217
+ for (let address: number = 1; address <= 6; address++) {
1218
+ for (let attempt: number = 0; attempt < 10; attempt++) {
1219
+ statuses.push(
1220
+ (
1221
+ await send({
1222
+ port,
1223
+ method: "POST",
1224
+ path: submitPath(data.shareKey),
1225
+ clientIp: `${data.firstOctet}.0.0.${address}`,
1226
+ body: data.body,
1227
+ })
1228
+ ).status,
1229
+ );
1230
+ }
1231
+ }
1232
+
1233
+ return statuses;
1234
+ }
1235
+
1236
+ test("is never spent by requests the IP allowlist, the captcha or the answers refuse", async () => {
1237
+ // Outside the form's allowlist (198.51.100.0/24).
1238
+ expect(
1239
+ new Set(
1240
+ await sendFromSixAddresses({
1241
+ firstOctet: 11,
1242
+ shareKey: LOCKED_SHARE_KEY,
1243
+ body: { data: ANSWERS },
1244
+ }),
1245
+ ),
1246
+ ).toEqual(new Set([403]));
1247
+
1248
+ // A captcha that fails.
1249
+ jest.spyOn(CaptchaUtil, "isCaptchaEnabled").mockReturnValue(true);
1250
+ jest
1251
+ .spyOn(CaptchaUtil, "verifyCaptcha")
1252
+ .mockRejectedValue(
1253
+ new BadDataException(
1254
+ "Captcha verification failed. Please try again.",
1255
+ ),
1256
+ );
1257
+
1258
+ expect(
1259
+ new Set(
1260
+ await sendFromSixAddresses({
1261
+ firstOctet: 12,
1262
+ shareKey: LOCKED_SHARE_KEY,
1263
+ body: { data: ANSWERS },
1264
+ }),
1265
+ ),
1266
+ ).toEqual(new Set([403]));
1267
+ expect(
1268
+ new Set(
1269
+ await sendFromSixAddresses({
1270
+ firstOctet: 12,
1271
+ shareKey: SHARE_KEY,
1272
+ body: { data: ANSWERS },
1273
+ }),
1274
+ ),
1275
+ ).toEqual(new Set([400]));
1276
+
1277
+ jest.spyOn(CaptchaUtil, "isCaptchaEnabled").mockReturnValue(false);
1278
+
1279
+ // Bodies that are not a submission, and answers that do not pass.
1280
+ expect(
1281
+ new Set(
1282
+ await sendFromSixAddresses({
1283
+ firstOctet: 13,
1284
+ shareKey: SHARE_KEY,
1285
+ body: {},
1286
+ }),
1287
+ ),
1288
+ ).toEqual(new Set([400]));
1289
+ expect(
1290
+ new Set(
1291
+ await sendFromSixAddresses({
1292
+ firstOctet: 14,
1293
+ shareKey: SHARE_KEY,
1294
+ body: { data: { reporterName: "No title" } },
1295
+ }),
1296
+ ),
1297
+ ).toEqual(new Set([400]));
1298
+
1299
+ expect(incidentCreate).not.toHaveBeenCalled();
1300
+ expect(client.keysMatching(":submit:f:")).toEqual([]);
1301
+
1302
+ // The office inside the allowlist can still report.
1303
+ const insider: HttpResult = await send({
1304
+ port,
1305
+ method: "POST",
1306
+ path: submitPath(LOCKED_SHARE_KEY),
1307
+ clientIp: ALLOWED_IP,
1308
+ body: { data: ANSWERS },
1309
+ });
1310
+
1311
+ expect(seen(insider)).toEqual({ status: 200, body: SUBMISSION_RESULT });
1312
+ expect(incidentCreate).toHaveBeenCalledTimes(1);
1313
+ expect(client.keysMatching(":submit:f:")).toHaveLength(1);
1314
+ }, 60_000);
1315
+
1316
+ test("declares at most sixty incidents an hour, from however many addresses", async () => {
1317
+ for (let address: number = 1; address <= 60; address++) {
1318
+ expect(
1319
+ (
1320
+ await send({
1321
+ port,
1322
+ method: "POST",
1323
+ path: submitPath(SHARE_KEY),
1324
+ clientIp: `10.3.0.${address}`,
1325
+ body: { data: ANSWERS },
1326
+ })
1327
+ ).status,
1328
+ ).toBe(200);
1329
+ }
1330
+
1331
+ const refused: HttpResult = await send({
1332
+ port,
1333
+ method: "POST",
1334
+ path: submitPath(SHARE_KEY),
1335
+ clientIp: "10.3.1.1",
1336
+ body: { data: ANSWERS },
1337
+ });
1338
+
1339
+ expect(refused.status).toBe(429);
1340
+ expect(errorMessageOf(refused)).toBe(
1341
+ INCIDENT_FORM_TOTAL_RATE_LIMIT_MESSAGE,
1342
+ );
1343
+ expect(Number(refused.headers["retry-after"])).toBeGreaterThan(0);
1344
+ expect(Number(refused.headers["retry-after"])).toBeLessThanOrEqual(
1345
+ 60 * 60,
1346
+ );
1347
+ expect(incidentCreate).toHaveBeenCalledTimes(60);
1348
+ }, 60_000);
1349
+
1350
+ test("refuses with a 503, declaring nothing, when it cannot be counted", async () => {
1351
+ client.failKeysMatching = /:submit:f:/;
1352
+
1353
+ const result: HttpResult = await send({
1354
+ port,
1355
+ method: "POST",
1356
+ path: submitPath(SHARE_KEY),
1357
+ body: { data: ANSWERS },
1358
+ });
1359
+
1360
+ expect(result.status).toBe(503);
1361
+ expect(errorMessageOf(result)).toBe(
1362
+ INCIDENT_FORM_RATE_LIMIT_UNAVAILABLE_MESSAGE,
1363
+ );
1364
+ expect(incidentCreate).not.toHaveBeenCalled();
1365
+ });
1366
+ });
1367
+
1368
+ describe("503", () => {
1369
+ it("refuses submissions while the rate limit counter is unavailable", async () => {
1370
+ isConnectedMock.mockReturnValue(false);
1371
+
1372
+ const result: HttpResult = await send({
1373
+ port,
1374
+ method: "POST",
1375
+ path: submitPath(SHARE_KEY),
1376
+ body: { data: ANSWERS },
1377
+ });
1378
+
1379
+ expect(result.status).toBe(503);
1380
+ expect(errorMessageOf(result)).toBe(
1381
+ INCIDENT_FORM_RATE_LIMIT_UNAVAILABLE_MESSAGE,
1382
+ );
1383
+ expect(submitPublicForm).not.toHaveBeenCalled();
1384
+ expect(incidentCreate).not.toHaveBeenCalled();
1385
+ });
1386
+
1387
+ it("still shows the form while the counter is unavailable", async () => {
1388
+ isConnectedMock.mockReturnValue(false);
1389
+
1390
+ const result: HttpResult = await send({
1391
+ port,
1392
+ method: "GET",
1393
+ path: readPath(SHARE_KEY),
1394
+ });
1395
+
1396
+ expect(seen(result)).toEqual({ status: 200, body: PUBLIC_FORM });
1397
+ });
1398
+ });
1399
+
1400
+ describe("a visitor's own OneUptime session never gets in the way", () => {
1401
+ const deadSessions: Array<
1402
+ [string, Dictionary<string>, http.OutgoingHttpHeaders]
1403
+ > = [
1404
+ [
1405
+ "an access-token cookie from before a secret rotation",
1406
+ { [CookieUtil.getUserTokenKey()]: STALE_ACCESS_TOKEN },
1407
+ {},
1408
+ ],
1409
+ [
1410
+ "an expired access-token cookie",
1411
+ { [CookieUtil.getUserTokenKey()]: EXPIRED_ACCESS_TOKEN },
1412
+ {},
1413
+ ],
1414
+ [
1415
+ "a junk access-token cookie",
1416
+ { [CookieUtil.getUserTokenKey()]: "not-a-jwt" },
1417
+ {},
1418
+ ],
1419
+ ["a junk bearer token", {}, { authorization: "Bearer not-a-jwt" }],
1420
+ ];
1421
+
1422
+ it.each(deadSessions)(
1423
+ "answers a request carrying %s exactly as one carrying none",
1424
+ async (
1425
+ _label: string,
1426
+ cookies: Dictionary<string>,
1427
+ headers: http.OutgoingHttpHeaders,
1428
+ ) => {
1429
+ for (const [method, path, body] of [
1430
+ ["GET", readPath(SHARE_KEY), undefined],
1431
+ ["POST", submitPath(SHARE_KEY), { data: ANSWERS }],
1432
+ ["GET", readPath(OFF_SHARE_KEY), undefined],
1433
+ ["POST", submitPath(SHARE_KEY), { data: { title: "" } }],
1434
+ ] as Array<["GET" | "POST", string, unknown]>) {
1435
+ const withSession: HttpResult = await send({
1436
+ port,
1437
+ method,
1438
+ path,
1439
+ cookies,
1440
+ headers,
1441
+ body,
1442
+ });
1443
+ const withoutSession: HttpResult = await send({
1444
+ port,
1445
+ method,
1446
+ path,
1447
+ body,
1448
+ });
1449
+
1450
+ expect(withSession.status).not.toBe(401);
1451
+ expect(seen(withSession)).toEqual(seen(withoutSession));
1452
+ }
1453
+ },
1454
+ );
1455
+
1456
+ /*
1457
+ * Nothing on these routes touches the visitor's cookies, so nothing can
1458
+ * sign them out of the session they have on this host.
1459
+ */
1460
+ it.each(deadSessions)(
1461
+ "never sets or clears a cookie for a request carrying %s",
1462
+ async (
1463
+ _label: string,
1464
+ cookies: Dictionary<string>,
1465
+ headers: http.OutgoingHttpHeaders,
1466
+ ) => {
1467
+ const read: HttpResult = await send({
1468
+ port,
1469
+ method: "GET",
1470
+ path: readPath(SHARE_KEY),
1471
+ cookies,
1472
+ headers,
1473
+ });
1474
+ const submitted: HttpResult = await send({
1475
+ port,
1476
+ method: "POST",
1477
+ path: submitPath(SHARE_KEY),
1478
+ cookies,
1479
+ headers,
1480
+ body: { data: ANSWERS },
1481
+ });
1482
+
1483
+ expect(read.headers["set-cookie"]).toBeUndefined();
1484
+ expect(submitted.headers["set-cookie"]).toBeUndefined();
1485
+ },
1486
+ );
1487
+
1488
+ it("treats a signed-in visitor as just a visitor: the same answer, and an incident that names no user", async () => {
1489
+ const cookies: Dictionary<string> = {
1490
+ [CookieUtil.getUserTokenKey()]: validAccessToken(),
1491
+ };
1492
+
1493
+ const read: HttpResult = await send({
1494
+ port,
1495
+ method: "GET",
1496
+ path: readPath(SHARE_KEY),
1497
+ cookies,
1498
+ });
1499
+ const submitted: HttpResult = await send({
1500
+ port,
1501
+ method: "POST",
1502
+ path: submitPath(SHARE_KEY),
1503
+ cookies,
1504
+ body: { data: ANSWERS },
1505
+ });
1506
+
1507
+ expect(seen(read)).toEqual({ status: 200, body: PUBLIC_FORM });
1508
+ expect(seen(submitted)).toEqual({ status: 200, body: SUBMISSION_RESULT });
1509
+ expect(submitted.headers["set-cookie"]).toBeUndefined();
1510
+
1511
+ const created: { data: Incident; props: JSONObject } = incidentCreate.mock
1512
+ .calls[0]![0] as { data: Incident; props: JSONObject };
1513
+
1514
+ expect(created.data.createdByUserId).toBeUndefined();
1515
+ expect(created.props).toEqual({ isRoot: true });
1516
+ expect(
1517
+ Object.keys(
1518
+ getPublicForm.mock.calls[0]![0] as Record<string, unknown>,
1519
+ ).sort(),
1520
+ ).toEqual(["clientIp", "shareKey"]);
1521
+ });
1522
+
1523
+ /*
1524
+ * The CRUD routes on the same router do need to know who is calling, so
1525
+ * a dead session is still a 401 there - the answer that makes the
1526
+ * dashboard client refresh its session. Only the public routes waive it.
1527
+ */
1528
+ it("still answers the signed-in CRUD routes beside them with a 401", async () => {
1529
+ for (const [method, path] of [
1530
+ ["POST", "/api/incident-form/get-list"],
1531
+ [
1532
+ "POST",
1533
+ `/api/incident-form/${ObjectID.generate().toString()}/get-item`,
1534
+ ],
1535
+ ] as Array<["GET" | "POST", string]>) {
1536
+ const result: HttpResult = await send({
1537
+ port,
1538
+ method,
1539
+ path,
1540
+ cookies: { [CookieUtil.getUserTokenKey()]: STALE_ACCESS_TOKEN },
1541
+ body: {},
1542
+ });
1543
+
1544
+ expect(result.status).toBe(401);
1545
+ expect(errorMessageOf(result)).toBe(INVALID_ACCESS_TOKEN_MESSAGE);
1546
+ }
1547
+ });
1548
+ });
1549
+ });
1550
+
1551
+ /*
1552
+ * Route matching, read from the router itself. The public routes sit beside
1553
+ * the model's CRUD routes under /incident-form; neither may ever catch the
1554
+ * other's requests. Express matches in registration order, and BaseAPI
1555
+ * registers the CRUD routes first.
1556
+ */
1557
+ describe("the public routes and the CRUD routes never collide", () => {
1558
+ type RouteHandler = (...args: Array<unknown>) => unknown;
1559
+
1560
+ interface RouterLayer {
1561
+ route?: {
1562
+ path: string;
1563
+ methods: Dictionary<boolean>;
1564
+ stack: Array<{ handle: RouteHandler }>;
1565
+ };
1566
+ match: (path: string) => boolean;
1567
+ }
1568
+
1569
+ const router: ExpressRouter = new IncidentFormAPI().getRouter();
1570
+ const layers: Array<RouterLayer> = (
1571
+ router as unknown as { stack: Array<RouterLayer> }
1572
+ ).stack.filter((layer: RouterLayer) => {
1573
+ return Boolean(layer.route);
1574
+ });
1575
+
1576
+ // Every registered route that would answer this request, in order.
1577
+ function routesFor(method: string, path: string): Array<string> {
1578
+ return layers
1579
+ .filter((layer: RouterLayer) => {
1580
+ return (
1581
+ Boolean(layer.route!.methods[method.toLowerCase()]) &&
1582
+ layer.match(path)
1583
+ );
1584
+ })
1585
+ .map((layer: RouterLayer) => {
1586
+ return `${method} ${layer.route!.path}`;
1587
+ });
1588
+ }
1589
+
1590
+ function firstMiddlewareFor(method: string, path: string): unknown {
1591
+ const layer: RouterLayer | undefined = layers.find(
1592
+ (candidate: RouterLayer) => {
1593
+ return (
1594
+ Boolean(candidate.route!.methods[method.toLowerCase()]) &&
1595
+ candidate.match(path)
1596
+ );
1597
+ },
1598
+ );
1599
+
1600
+ return layer?.route?.stack[0]?.handle;
1601
+ }
1602
+
1603
+ const shareKey: string = ObjectID.generate().toString();
1604
+ const id: string = ObjectID.generate().toString();
1605
+
1606
+ it("sends a form link only to the public routes", () => {
1607
+ expect(routesFor("GET", `/incident-form/public/${shareKey}`)).toEqual([
1608
+ "GET /incident-form/public/:shareKey",
1609
+ ]);
1610
+ expect(
1611
+ routesFor("POST", `/incident-form/public/${shareKey}/submit`),
1612
+ ).toEqual(["POST /incident-form/public/:shareKey/submit"]);
1613
+ });
1614
+
1615
+ it.each([
1616
+ ["POST", "/incident-form"],
1617
+ ["POST", "/incident-form/get-list"],
1618
+ ["GET", "/incident-form/get-list"],
1619
+ ["POST", "/incident-form/count"],
1620
+ ["POST", `/incident-form/${id}/get-item`],
1621
+ ["GET", `/incident-form/${id}/get-item`],
1622
+ ["PUT", `/incident-form/${id}`],
1623
+ ["POST", `/incident-form/${id}/update-item`],
1624
+ ["DELETE", `/incident-form/${id}`],
1625
+ ["POST", `/incident-form/${id}/delete-item`],
1626
+ ])(
1627
+ "sends %s %s only to its CRUD route, behind the signed-in user middleware",
1628
+ (method: string, path: string) => {
1629
+ const matched: Array<string> = routesFor(method, path);
1630
+
1631
+ expect(matched).toHaveLength(1);
1632
+ expect(matched[0]).not.toContain("/public/");
1633
+ expect(firstMiddlewareFor(method, path)).toBe(
1634
+ UserMiddleware.getUserMiddleware,
1635
+ );
1636
+ },
1637
+ );
1638
+
1639
+ /*
1640
+ * A share key is a UUID, so it is never "get-item". Were one spelled like
1641
+ * a CRUD suffix, the CRUD route - registered first - would take it, with
1642
+ * its authentication: never the other way round.
1643
+ */
1644
+ it("lets the CRUD route win the one path both could match", () => {
1645
+ expect(routesFor("GET", "/incident-form/public/get-item")).toEqual([
1646
+ "GET /incident-form/:id/get-item",
1647
+ "GET /incident-form/public/:shareKey",
1648
+ ]);
1649
+ expect(firstMiddlewareFor("GET", "/incident-form/public/get-item")).toBe(
1650
+ UserMiddleware.getUserMiddleware,
1651
+ );
1652
+ });
1653
+ });