@oneuptime/common 12.0.23 → 12.0.25

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 (323) hide show
  1. package/Models/AnalyticsModels/Span.ts +101 -0
  2. package/Models/DatabaseModels/DetectionRule.ts +55 -0
  3. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
  4. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
  5. package/Models/DatabaseModels/Project.ts +68 -0
  6. package/Models/DatabaseModels/User.ts +68 -0
  7. package/Server/API/BaseAPI.ts +0 -24
  8. package/Server/API/SlackAPI.ts +0 -2
  9. package/Server/API/TelemetryAPI.ts +29 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  14. package/Server/Middleware/GzipRequestBody.ts +311 -0
  15. package/Server/Middleware/MultipartFormData.ts +87 -3
  16. package/Server/Middleware/SlackAuthorization.ts +96 -18
  17. package/Server/Services/DetectionRuleService.ts +73 -6
  18. package/Server/Services/Index.ts +205 -0
  19. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
  20. package/Server/Services/NetworkDeviceService.ts +148 -0
  21. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  22. package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
  23. package/Server/Services/ProjectService.ts +29 -19
  24. package/Server/Services/SecurityEventService.ts +90 -13
  25. package/Server/Services/StatusPageResourceService.ts +223 -0
  26. package/Server/Services/TelemetryAttributeService.ts +17 -0
  27. package/Server/Services/UserService.ts +4 -6
  28. package/Server/Types/Markdown.ts +161 -67
  29. package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
  30. package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
  31. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
  32. package/Server/Utils/Attribution.ts +4 -0
  33. package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
  34. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
  35. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
  36. package/Server/Utils/StartServer.ts +12 -32
  37. package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
  38. package/Server/Utils/Telemetry/LlmMetricSpend.ts +181 -0
  39. package/Server/Utils/Telemetry/LlmSpan.ts +46 -0
  40. package/Server/Utils/Workspace/Slack/Actions/Auth.ts +0 -12
  41. package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
  42. package/Server/Views/Partials/Head.ejs +2 -1
  43. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
  44. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
  45. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
  46. package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
  47. package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
  48. package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
  49. package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
  50. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
  51. package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
  52. package/Tests/App/Dashboard/LlmCallsTableIdentity.test.tsx +322 -0
  53. package/Tests/App/Dashboard/LlmOverview.test.tsx +335 -0
  54. package/Tests/App/Dashboard/LlmSpanDisplay.test.ts +391 -0
  55. package/Tests/App/Dashboard/LlmUsageBreakdown.test.tsx +1007 -0
  56. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
  57. package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
  58. package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
  59. package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
  60. package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
  61. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
  62. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
  63. package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
  64. package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
  65. package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
  66. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
  67. package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
  68. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
  69. package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
  70. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
  71. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
  72. package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
  73. package/Tests/Server/API/BaseAPI.test.ts +41 -0
  74. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +9 -16
  75. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
  76. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
  77. package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
  78. package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
  79. package/Tests/Server/Middleware/SlackAuthorization.test.ts +262 -5
  80. package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
  81. package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
  82. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
  83. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
  84. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
  85. package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
  86. package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
  87. package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
  88. package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
  89. package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
  90. package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
  91. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
  92. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
  93. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
  94. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
  95. package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
  96. package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +387 -4
  97. package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +386 -0
  98. package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +804 -0
  99. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
  100. package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
  101. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
  102. package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
  103. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
  104. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
  105. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
  106. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
  107. package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +584 -0
  108. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
  109. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
  110. package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
  111. package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
  112. package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
  113. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
  114. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
  115. package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
  116. package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
  117. package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
  118. package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
  119. package/Tests/UI/Components/SideOver.test.tsx +1 -1
  120. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
  121. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
  122. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
  123. package/Tests/UI/Components/Toggle.test.tsx +25 -0
  124. package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
  125. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
  126. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
  127. package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
  128. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
  129. package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +770 -0
  130. package/Types/Code/YamlSyntax.ts +396 -0
  131. package/Types/Exception/ExceptionCode.ts +1 -0
  132. package/Types/Exception/PayloadTooLargeException.ts +8 -0
  133. package/Types/Marketing/Attribution.ts +10 -0
  134. package/Types/Marketing/MarketingEvent.ts +4 -0
  135. package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
  136. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
  137. package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
  138. package/Types/Telemetry/LlmConventions.ts +255 -0
  139. package/Types/Telemetry/LlmMetricConventions.ts +329 -0
  140. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
  141. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
  142. package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
  143. package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
  144. package/UI/Components/Detail/Detail.tsx +10 -0
  145. package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
  146. package/UI/Components/Forms/Fields/FormField.tsx +31 -0
  147. package/UI/Components/Forms/Types/Field.ts +8 -0
  148. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  149. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
  150. package/UI/Components/Forms/Validation.ts +46 -0
  151. package/UI/Components/Input/Input.tsx +3 -0
  152. package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
  153. package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
  154. package/UI/Components/ModelTable/Column.ts +8 -0
  155. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
  156. package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
  157. package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
  158. package/UI/Components/SideOver/SideOver.tsx +5 -5
  159. package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
  160. package/UI/Components/StatusPage/ResourceList.tsx +197 -38
  161. package/UI/Components/Toggle/Toggle.tsx +9 -1
  162. package/UI/Components/Types/FieldType.ts +1 -0
  163. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
  164. package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
  165. package/Utils/StatusPage/ResourceExplorer.ts +258 -0
  166. package/Utils/Telemetry/LlmMetricQuery.ts +287 -0
  167. package/build/dist/Models/AnalyticsModels/Span.js +89 -0
  168. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  169. package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
  170. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  171. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
  172. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  173. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
  174. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  175. package/build/dist/Models/DatabaseModels/Project.js +76 -0
  176. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  177. package/build/dist/Models/DatabaseModels/User.js +76 -0
  178. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  179. package/build/dist/Server/API/BaseAPI.js +3 -19
  180. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  181. package/build/dist/Server/API/SlackAPI.js +0 -2
  182. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  183. package/build/dist/Server/API/TelemetryAPI.js +17 -0
  184. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  185. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
  190. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
  191. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  192. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  193. package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
  194. package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
  195. package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
  196. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  197. package/build/dist/Server/Middleware/SlackAuthorization.js +58 -7
  198. package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
  199. package/build/dist/Server/Services/DetectionRuleService.js +49 -4
  200. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  201. package/build/dist/Server/Services/Index.js +204 -0
  202. package/build/dist/Server/Services/Index.js.map +1 -1
  203. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
  204. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  205. package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
  206. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  207. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
  208. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  209. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
  210. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
  211. package/build/dist/Server/Services/ProjectService.js +25 -45
  212. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  213. package/build/dist/Server/Services/SecurityEventService.js +57 -5
  214. package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
  215. package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
  216. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  217. package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
  218. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  219. package/build/dist/Server/Services/UserService.js +2 -12
  220. package/build/dist/Server/Services/UserService.js.map +1 -1
  221. package/build/dist/Server/Types/Markdown.js +147 -61
  222. package/build/dist/Server/Types/Markdown.js.map +1 -1
  223. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
  224. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
  225. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
  226. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  227. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
  228. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  229. package/build/dist/Server/Utils/Attribution.js.map +1 -1
  230. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
  231. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
  232. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
  233. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  234. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
  235. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  236. package/build/dist/Server/Utils/StartServer.js +12 -21
  237. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  238. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
  239. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
  240. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +151 -0
  241. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
  242. package/build/dist/Server/Utils/Telemetry/LlmSpan.js +24 -1
  243. package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
  244. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +0 -10
  245. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
  246. package/build/dist/Types/Code/YamlSyntax.js +212 -0
  247. package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
  248. package/build/dist/Types/Exception/ExceptionCode.js +1 -0
  249. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  250. package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
  251. package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
  252. package/build/dist/Types/Marketing/Attribution.js +10 -0
  253. package/build/dist/Types/Marketing/Attribution.js.map +1 -1
  254. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
  255. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  256. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
  257. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  258. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
  259. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  260. package/build/dist/Types/Telemetry/LlmConventions.js +236 -0
  261. package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
  262. package/build/dist/Types/Telemetry/LlmMetricConventions.js +290 -0
  263. package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
  264. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  265. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  266. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
  267. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  268. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
  269. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  270. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
  271. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
  272. package/build/dist/UI/Components/Detail/Detail.js +9 -0
  273. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  274. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
  275. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  276. package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
  277. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  278. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  279. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  280. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  281. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
  282. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
  283. package/build/dist/UI/Components/Forms/Validation.js +27 -0
  284. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  285. package/build/dist/UI/Components/Input/Input.js +1 -1
  286. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  287. package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
  288. package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
  289. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
  290. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  291. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
  292. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  293. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
  294. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
  295. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
  296. package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
  297. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
  298. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  299. package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
  300. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  301. package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
  302. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  303. package/build/dist/UI/Components/Types/FieldType.js +1 -0
  304. package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
  305. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
  306. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
  307. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
  308. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
  309. package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
  310. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
  311. package/build/dist/Utils/Telemetry/LlmMetricQuery.js +175 -0
  312. package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
  313. package/jest.config.json +2 -0
  314. package/package.json +1 -1
  315. package/tsconfig.json +2 -1
  316. package/Tests/UI/Components/Toast.test.tsx +0 -197
  317. package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
  318. package/UI/Components/Toast/Toast.tsx +0 -189
  319. package/UI/Components/Toast/ToastInit.tsx +0 -110
  320. package/build/dist/UI/Components/Toast/Toast.js +0 -123
  321. package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
  322. package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
  323. package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
@@ -17,6 +17,82 @@ import Model from "../../Models/DatabaseModels/StatusPageResource";
17
17
  import Monitor from "../../Models/DatabaseModels/Monitor";
18
18
  import MonitorGroupResource from "../../Models/DatabaseModels/MonitorGroupResource";
19
19
 
20
+ /**
21
+ * The ways a create or update can name the resource's target. Everything
22
+ * server side sets the foreign key column, but the dashboard's resource form
23
+ * posts the relation - and the relation does NOT arrive in the same shape on
24
+ * both write paths:
25
+ *
26
+ * - create goes through BaseAPI.createItem, which revives the body with
27
+ * BaseModel.fromJSON, so `monitor` is a real Monitor and `monitor.id` is
28
+ * an ObjectID;
29
+ * - update goes through BaseAPI.updateItem, which only runs
30
+ * JSONFunctions.deserialize. That revives ObjectID/DateTime values, but
31
+ * never nested models, so `monitor` stays the plain `{ _id: "<uuid>" }`
32
+ * the browser sent and has no `id` at all.
33
+ *
34
+ * Reading only `.id` therefore silently saw nothing on every edit-form save,
35
+ * which is the whole path the update guard exists for - so this accepts the
36
+ * id however it arrives.
37
+ */
38
+ type StatusPageResourceTargetValue =
39
+ | ObjectID
40
+ | string
41
+ | { id?: unknown; _id?: unknown }
42
+ | null
43
+ | undefined;
44
+
45
+ interface StatusPageResourceTargetInput {
46
+ monitorId?: StatusPageResourceTargetValue;
47
+ monitor?: StatusPageResourceTargetValue;
48
+ monitorGroupId?: StatusPageResourceTargetValue;
49
+ monitorGroup?: StatusPageResourceTargetValue;
50
+ }
51
+
52
+ function toTargetObjectID(
53
+ value: StatusPageResourceTargetValue,
54
+ ): ObjectID | null {
55
+ if (!value) {
56
+ return null;
57
+ }
58
+
59
+ if (value instanceof ObjectID) {
60
+ return value;
61
+ }
62
+
63
+ if (typeof value === "string") {
64
+ return new ObjectID(value);
65
+ }
66
+
67
+ if (typeof value === "object") {
68
+ return (
69
+ toTargetObjectID(value.id as StatusPageResourceTargetValue) ||
70
+ toTargetObjectID(value._id as StatusPageResourceTargetValue)
71
+ );
72
+ }
73
+
74
+ return null;
75
+ }
76
+
77
+ interface StatusPageResourceTarget {
78
+ monitorId: ObjectID | null;
79
+ monitorGroupId: ObjectID | null;
80
+ }
81
+
82
+ /**
83
+ * Named after what the operator sees rather than what the column is, because
84
+ * this reads back to them on the resource form.
85
+ */
86
+ function duplicateResourceException(
87
+ target: StatusPageResourceTarget,
88
+ ): BadDataException {
89
+ const thing: string = target.monitorId ? "monitor" : "monitor group";
90
+
91
+ return new BadDataException(
92
+ `This ${thing} is already added to this status page. A ${thing} can only be added once so it is not shown twice to your customers.`,
93
+ );
94
+ }
95
+
20
96
  export class Service extends DatabaseService<Model> {
21
97
  public constructor() {
22
98
  super(Model);
@@ -115,6 +191,76 @@ export class Service extends DatabaseService<Model> {
115
191
  return statusPageResources;
116
192
  }
117
193
 
194
+ /**
195
+ * The id of the thing a resource points at, whichever way the caller
196
+ * expressed it. The dashboard's resource form posts the relation
197
+ * (`monitor: { _id }`) while everything server side sets the foreign key
198
+ * column, and both mean the same resource.
199
+ */
200
+ private getResourceMonitorTarget(
201
+ data: StatusPageResourceTargetInput,
202
+ ): StatusPageResourceTarget {
203
+ return {
204
+ monitorId:
205
+ toTargetObjectID(data.monitorId) || toTargetObjectID(data.monitor),
206
+ monitorGroupId:
207
+ toTargetObjectID(data.monitorGroupId) ||
208
+ toTargetObjectID(data.monitorGroup),
209
+ };
210
+ }
211
+
212
+ /**
213
+ * A status page lists a monitor once.
214
+ *
215
+ * Nothing stopped the same monitor being added twice, so re-adding a label's
216
+ * monitors after a new one joined that label created a second resource for
217
+ * every monitor already there, and the public page listed each of them twice
218
+ * (issue #3420). The rule engine has always refused to add a monitor that is
219
+ * already on the page for exactly this reason; this makes the same promise
220
+ * hold for every other way a resource is created - the resource form, the
221
+ * bulk add modal, and the API.
222
+ *
223
+ * The check is status-page-wide rather than per group: a monitor in two
224
+ * groups is still a monitor a visitor sees twice.
225
+ */
226
+ @CaptureSpan()
227
+ public async isResourceAlreadyOnStatusPage(data: {
228
+ statusPageId: ObjectID;
229
+ monitorId?: ObjectID | null | undefined;
230
+ monitorGroupId?: ObjectID | null | undefined;
231
+ excludeResourceId?: ObjectID | null | undefined;
232
+ }): Promise<boolean> {
233
+ if (!data.monitorId && !data.monitorGroupId) {
234
+ return false;
235
+ }
236
+
237
+ const query: Query<Model> = {
238
+ statusPageId: data.statusPageId,
239
+ };
240
+
241
+ if (data.monitorId) {
242
+ query.monitorId = data.monitorId;
243
+ } else {
244
+ query.monitorGroupId = data.monitorGroupId!;
245
+ }
246
+
247
+ if (data.excludeResourceId) {
248
+ query._id = QueryHelper.notEquals(data.excludeResourceId.toString());
249
+ }
250
+
251
+ const existingResource: Model | null = await this.findOneBy({
252
+ query: query,
253
+ select: {
254
+ _id: true,
255
+ },
256
+ props: {
257
+ isRoot: true,
258
+ },
259
+ });
260
+
261
+ return Boolean(existingResource);
262
+ }
263
+
118
264
  @CaptureSpan()
119
265
  protected override async onBeforeCreate(
120
266
  createBy: CreateBy<Model>,
@@ -125,6 +271,20 @@ export class Service extends DatabaseService<Model> {
125
271
  );
126
272
  }
127
273
 
274
+ const target: StatusPageResourceTarget = this.getResourceMonitorTarget(
275
+ createBy.data as unknown as StatusPageResourceTargetInput,
276
+ );
277
+
278
+ if (
279
+ await this.isResourceAlreadyOnStatusPage({
280
+ statusPageId: createBy.data.statusPageId,
281
+ monitorId: target.monitorId,
282
+ monitorGroupId: target.monitorGroupId,
283
+ })
284
+ ) {
285
+ throw duplicateResourceException(target);
286
+ }
287
+
128
288
  if (!createBy.data.order) {
129
289
  const query: Query<Model> = {
130
290
  statusPageId: createBy.data.statusPageId,
@@ -222,6 +382,69 @@ export class Service extends DatabaseService<Model> {
222
382
  protected override async onBeforeUpdate(
223
383
  updateBy: UpdateBy<Model>,
224
384
  ): Promise<OnUpdate<Model>> {
385
+ /*
386
+ * Pointing an existing resource at a monitor the page already lists is the
387
+ * same duplicate onBeforeCreate refuses, just reached from the edit form.
388
+ */
389
+ const updatedTarget: StatusPageResourceTarget =
390
+ this.getResourceMonitorTarget(
391
+ updateBy.data as unknown as StatusPageResourceTargetInput,
392
+ );
393
+
394
+ if (
395
+ (updatedTarget.monitorId || updatedTarget.monitorGroupId) &&
396
+ updateBy.query._id
397
+ ) {
398
+ const resourceBeingUpdated: Model | null = await this.findOneBy({
399
+ query: {
400
+ _id: updateBy.query._id!,
401
+ },
402
+ props: {
403
+ isRoot: true,
404
+ },
405
+ select: {
406
+ _id: true,
407
+ statusPageId: true,
408
+ monitorId: true,
409
+ monitorGroupId: true,
410
+ },
411
+ });
412
+
413
+ const currentTarget: StatusPageResourceTarget =
414
+ this.getResourceMonitorTarget(
415
+ (resourceBeingUpdated ||
416
+ {}) as unknown as StatusPageResourceTargetInput,
417
+ );
418
+
419
+ /*
420
+ * The edit form is a ModelForm, so it posts every field it collects -
421
+ * the monitor included - even when all the operator changed was the
422
+ * display name. Checking an unchanged target would refuse those saves
423
+ * on a status page that already carries a duplicate from before this
424
+ * rule existed, which would leave both of its rows uneditable.
425
+ */
426
+ const isTargetUnchanged: boolean =
427
+ (!updatedTarget.monitorId ||
428
+ updatedTarget.monitorId.toString() ===
429
+ currentTarget.monitorId?.toString()) &&
430
+ (!updatedTarget.monitorGroupId ||
431
+ updatedTarget.monitorGroupId.toString() ===
432
+ currentTarget.monitorGroupId?.toString());
433
+
434
+ if (
435
+ resourceBeingUpdated?.statusPageId &&
436
+ !isTargetUnchanged &&
437
+ (await this.isResourceAlreadyOnStatusPage({
438
+ statusPageId: resourceBeingUpdated.statusPageId,
439
+ monitorId: updatedTarget.monitorId,
440
+ monitorGroupId: updatedTarget.monitorGroupId,
441
+ excludeResourceId: resourceBeingUpdated.id,
442
+ }))
443
+ ) {
444
+ throw duplicateResourceException(updatedTarget);
445
+ }
446
+ }
447
+
225
448
  if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) {
226
449
  const resource: Model | null = await this.findOneBy({
227
450
  query: {
@@ -4,6 +4,7 @@ import LogDatabaseService from "./LogService";
4
4
  import MetricDatabaseService from "./MetricService";
5
5
  import SpanDatabaseService from "./SpanService";
6
6
  import ExceptionInstanceService from "./ExceptionInstanceService";
7
+ import SecurityEventService from "./SecurityEventService";
7
8
  import MutableMetricDatabaseService, {
8
9
  MutableMetricService as MutableMetricServiceClass,
9
10
  } from "./MutableMetricService";
@@ -101,6 +102,22 @@ export class TelemetryAttributeService {
101
102
  attributeKeysColumn: "attributeKeys",
102
103
  timeColumn: "time",
103
104
  };
105
+ /*
106
+ * Security events carry the whole source payload flattened into
107
+ * `attributes`, which is where every field the OCSF schema does not
108
+ * have a typed column for ends up (device.hostname,
109
+ * finding_info.title, metadata.product.name, ...). The security
110
+ * events table offers those as optional columns, and this is the
111
+ * list it offers.
112
+ */
113
+ case TelemetryType.SecurityEvent:
114
+ return {
115
+ service: SecurityEventService,
116
+ tableName: SecurityEventService.model.tableName,
117
+ attributesColumn: "attributes",
118
+ attributeKeysColumn: "attributeKeys",
119
+ timeColumn: "time",
120
+ };
104
121
  default:
105
122
  return null;
106
123
  }
@@ -42,7 +42,9 @@ import TeamMember from "../../Models/DatabaseModels/TeamMember";
42
42
  import Model from "../../Models/DatabaseModels/User";
43
43
  import SlackUtil from "../Utils/Workspace/Slack/Slack";
44
44
  import ProductAnalytics from "../Utils/ProductAnalytics";
45
- import MarketingEventUtil from "../Utils/Marketing/MarketingEventUtil";
45
+ import MarketingEventUtil, {
46
+ utmAnalyticsProperties,
47
+ } from "../Utils/Marketing/MarketingEventUtil";
46
48
  import { MarketingEventType } from "../../Types/Marketing/MarketingEvent";
47
49
  import UserTotpAuthService from "./UserTotpAuthService";
48
50
  import UserTwoFactorBackupCodeService from "./UserTwoFactorBackupCodeService";
@@ -242,11 +244,7 @@ export class Service extends DatabaseService<Model> {
242
244
  distinctId: createdItem.email.toString(),
243
245
  properties: {
244
246
  has_password: Boolean(createdItem.password),
245
- utm_source: createdItem.utmSource || "",
246
- utm_medium: createdItem.utmMedium || "",
247
- utm_campaign: createdItem.utmCampaign || "",
248
- utm_term: createdItem.utmTerm || "",
249
- utm_content: createdItem.utmContent || "",
247
+ ...utmAnalyticsProperties(createdItem as unknown as JSONObject),
250
248
  utm_url: createdItem.utmUrl || "",
251
249
  click_ids: createdItem.clickIds || {},
252
250
  first_touch: createdItem.firstTouchAttribution || {},
@@ -246,6 +246,68 @@ export default class Markdown {
246
246
  return markdownSlugify(text);
247
247
  }
248
248
 
249
+ /*
250
+ * Human-readable names for the languages a docs code fence can declare.
251
+ * Rendered into the code block's header bar. A fence with no language, or
252
+ * one that is not listed here, gets no label rather than a guess — the
253
+ * label used to come from highlight.js auto-detection, which cheerfully
254
+ * announced shell snippets as "PHP".
255
+ */
256
+ private static readonly codeLanguageNames: Record<string, string> = {
257
+ apache: "Apache",
258
+ bash: "Bash",
259
+ c: "C",
260
+ cpp: "C++",
261
+ csharp: "C#",
262
+ css: "CSS",
263
+ diff: "Diff",
264
+ dns: "DNS Zone",
265
+ docker: "Dockerfile",
266
+ dockerfile: "Dockerfile",
267
+ go: "Go",
268
+ graphql: "GraphQL",
269
+ groovy: "Groovy",
270
+ hcl: "HCL",
271
+ html: "HTML",
272
+ http: "HTTP",
273
+ ini: "INI",
274
+ java: "Java",
275
+ javascript: "JavaScript",
276
+ js: "JavaScript",
277
+ json: "JSON",
278
+ jsx: "JSX",
279
+ kotlin: "Kotlin",
280
+ lua: "Lua",
281
+ makefile: "Makefile",
282
+ markdown: "Markdown",
283
+ md: "Markdown",
284
+ nginx: "Nginx",
285
+ perl: "Perl",
286
+ php: "PHP",
287
+ powershell: "PowerShell",
288
+ proto: "Protobuf",
289
+ py: "Python",
290
+ python: "Python",
291
+ r: "R",
292
+ rb: "Ruby",
293
+ ruby: "Ruby",
294
+ rust: "Rust",
295
+ scala: "Scala",
296
+ sh: "Shell",
297
+ shell: "Shell",
298
+ sql: "SQL",
299
+ swift: "Swift",
300
+ terraform: "Terraform",
301
+ toml: "TOML",
302
+ ts: "TypeScript",
303
+ tsx: "TSX",
304
+ typescript: "TypeScript",
305
+ xml: "XML",
306
+ yaml: "YAML",
307
+ yml: "YAML",
308
+ zsh: "Shell",
309
+ };
310
+
249
311
  private static getDocsRenderer(): Renderer {
250
312
  if (this.docsRenderer !== null) {
251
313
  return this.docsRenderer;
@@ -253,8 +315,16 @@ export default class Markdown {
253
315
 
254
316
  const renderer: Renderer = new Renderer();
255
317
 
318
+ /*
319
+ * The docs renderer emits semantic markup with a small set of stable class
320
+ * names and leaves every colour, size and spacing decision to
321
+ * Docs/Static/css/style.css. Utility classes used to be baked in here,
322
+ * which meant the same element was styled from two places at once and made
323
+ * a dark theme impossible without editing this file.
324
+ */
325
+
256
326
  renderer.paragraph = function (text) {
257
- return `<p class="mt-2 mb-2 leading-8 text-gray-600">${text}</p>`;
327
+ return `<p>${text}</p>`;
258
328
  };
259
329
 
260
330
  renderer.blockquote = function (quote) {
@@ -264,130 +334,154 @@ export default class Markdown {
264
334
 
265
335
  if (calloutMatch) {
266
336
  const type: string = calloutMatch[1]!.toLowerCase();
267
- const configMap: Record<
268
- string,
269
- { border: string; bg: string; icon: string; label: string }
270
- > = {
337
+ const configMap: Record<string, { icon: string; label: string }> = {
271
338
  note: {
272
- border: "border-blue-400",
273
- bg: "bg-blue-50",
274
- icon: `<svg class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
339
+ icon: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>`,
275
340
  label: "Note",
276
341
  },
277
342
  info: {
278
- border: "border-blue-400",
279
- bg: "bg-blue-50",
280
- icon: `<svg class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
343
+ icon: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>`,
281
344
  label: "Info",
282
345
  },
283
346
  tip: {
284
- border: "border-green-400",
285
- bg: "bg-green-50",
286
- icon: `<svg class="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>`,
347
+ icon: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>`,
287
348
  label: "Tip",
288
349
  },
289
350
  warning: {
290
- border: "border-yellow-400",
291
- bg: "bg-yellow-50",
292
- icon: `<svg class="h-5 w-5 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>`,
351
+ icon: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>`,
293
352
  label: "Warning",
294
353
  },
295
354
  caution: {
296
- border: "border-yellow-400",
297
- bg: "bg-yellow-50",
298
- icon: `<svg class="h-5 w-5 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>`,
355
+ icon: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>`,
299
356
  label: "Caution",
300
357
  },
301
358
  danger: {
302
- border: "border-red-400",
303
- bg: "bg-red-50",
304
- icon: `<svg class="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
359
+ icon: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>`,
305
360
  label: "Danger",
306
361
  },
307
362
  };
308
363
 
309
- const config: {
310
- border: string;
311
- bg: string;
312
- icon: string;
313
- label: string;
314
- } = configMap[type] || configMap["note"]!;
364
+ const config: { icon: string; label: string } =
365
+ configMap[type] || configMap["note"]!;
315
366
 
316
367
  const content: string = quote.replace(
317
368
  /<p[^>]*>\s*<strong>(Note|Warning|Tip|Danger|Info|Caution):?<\/strong>\s*/i,
318
- '<p class="mt-2 mb-2 leading-8 text-gray-600">',
369
+ "<p>",
319
370
  );
320
371
 
321
- return `<div class="callout callout-${type} my-4 rounded-r-lg border-l-4 ${config.border} ${config.bg} p-4">
322
- <div class="flex items-center gap-2 mb-1">
323
- ${config.icon}
324
- <span class="text-sm font-semibold text-gray-700">${config.label}</span>
372
+ return `<div class="docs-callout docs-callout--${type}">
373
+ <div class="docs-callout__head">
374
+ <svg class="docs-callout__icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">${config.icon}</svg>
375
+ <span class="docs-callout__label">${config.label}</span>
325
376
  </div>
326
- <div class="leading-7 text-gray-600 text-sm">${content}</div>
377
+ <div class="docs-callout__body">${content}</div>
327
378
  </div>`;
328
379
  }
329
380
 
330
- return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500">
331
- <div class="leading-8 text-gray-600">${quote}</div>
332
- </blockquote>`;
381
+ return `<blockquote class="docs-quote">${quote}</blockquote>`;
333
382
  };
334
383
 
335
- renderer.image = function (href, _title, text) {
336
- return `<img src="${href}" alt="${text}" class="rounded-md shadow-md" />`;
384
+ renderer.image = function (href, title, text) {
385
+ const titleAttr: string = title
386
+ ? ` title="${Markdown.escapeHtml(title)}"`
387
+ : "";
388
+ return `<img src="${href}" alt="${text || ""}"${titleAttr} class="docs-image" loading="lazy" decoding="async" />`;
389
+ };
390
+
391
+ renderer.link = function (href, title, text) {
392
+ if (!href) {
393
+ return text as string;
394
+ }
395
+
396
+ const target: string = href.toLowerCase();
397
+ const isExternal: boolean =
398
+ (target.startsWith("http://") || target.startsWith("https://")) &&
399
+ !target.includes("oneuptime.com");
400
+ const titleAttr: string = title
401
+ ? ` title="${Markdown.escapeHtml(title)}"`
402
+ : "";
403
+ const externalAttrs: string = isExternal
404
+ ? ' target="_blank" rel="noopener noreferrer"'
405
+ : "";
406
+ const marker: string = isExternal
407
+ ? '<svg class="docs-link__external" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>'
408
+ : "";
409
+
410
+ return `<a class="docs-link" href="${href}"${titleAttr}${externalAttrs}>${text}${marker}</a>`;
337
411
  };
338
412
 
339
413
  renderer.code = function (code, language) {
340
- if (language === "mermaid") {
341
- return `<div class="mermaid-wrapper overflow-x-auto my-6"><div class="mermaid">${code}</div></div>`;
414
+ const lang: string = (language || "").trim().toLowerCase();
415
+
416
+ if (lang === "mermaid") {
417
+ /*
418
+ * Mermaid reads the diagram from textContent, so escaping here keeps
419
+ * the diagram identical while making sure a `<` in a node label can
420
+ * never be parsed as markup.
421
+ */
422
+ return `<div class="docs-diagram"><div class="mermaid">${Markdown.escapeHtml(code)}</div></div>`;
342
423
  }
424
+
343
425
  const escaped: string = Markdown.escapeHtml(code);
344
- return `<pre><code class="language-${language}">${escaped}</code></pre>`;
426
+ const label: string | undefined = Markdown.codeLanguageNames[lang];
427
+ /*
428
+ * `nohighlight` stops highlight.js auto-detecting a language for fences
429
+ * that never declared one — plain text is honest, a wrong grammar is not.
430
+ */
431
+ const codeClass: string = lang ? `language-${lang}` : "nohighlight";
432
+ /*
433
+ * The copy button ships hidden and the page's script unhides it once it
434
+ * has wired up the click handler and filled in the translated label —
435
+ * without scripting it would be a dead control with an English name.
436
+ * The bar reserves its height either way, so nothing shifts.
437
+ */
438
+ const bar: string = `<div class="docs-code__bar">
439
+ <span class="docs-code__lang">${label || ""}</span>
440
+ <button type="button" class="docs-code__copy" data-copy-code hidden>
441
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
442
+ <span class="docs-code__copy-text"></span>
443
+ </button>
444
+ </div>`;
445
+
446
+ return `<div class="docs-code"${lang ? ` data-language="${lang}"` : ""}>${bar}<pre><code class="${codeClass}">${escaped}</code></pre></div>`;
345
447
  };
346
448
 
347
449
  renderer.heading = function (text, level) {
348
450
  const slug: string = Markdown.slugify(text);
349
451
  const anchor: string =
350
- level === 2 || level === 3
351
- ? `<a href="#${slug}" class="anchor-link" aria-hidden="true">#</a>`
452
+ level >= 2 && level <= 4
453
+ ? `<a class="docs-anchor" href="#${slug}" aria-hidden="true" tabindex="-1">#</a>`
352
454
  : "";
353
455
 
354
- if (level === 1) {
355
- return `<h1 id="${slug}" class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`;
356
- } else if (level === 2) {
357
- return `<h2 id="${slug}" class="group my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text} ${anchor}</h2>`;
358
- } else if (level === 3) {
359
- return `<h3 id="${slug}" class="group my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text} ${anchor}</h3>`;
360
- } else if (level === 4) {
361
- return `<h4 id="${slug}" class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`;
362
- } else if (level === 5) {
363
- return `<h5 id="${slug}" class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`;
364
- }
365
- return `<h6 id="${slug}" class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`;
456
+ const safeLevel: number = Math.min(Math.max(level, 1), 6);
457
+
458
+ return `<h${safeLevel} id="${slug}" class="docs-heading docs-h${safeLevel}"><span class="docs-heading__text">${text}</span>${anchor}</h${safeLevel}>`;
459
+ };
460
+
461
+ renderer.hr = function () {
462
+ return '<hr class="docs-hr" />';
366
463
  };
367
464
 
368
465
  renderer.table = function (header, body) {
369
- return `<div class="docs-table-wrapper overflow-x-auto my-6 rounded-lg border border-slate-200">
370
- <table class="min-w-full text-sm text-left">${header}${body}</table>
371
- </div>`;
466
+ return `<div class="docs-table-wrapper" tabindex="0"><table class="docs-table">${header}${body}</table></div>`;
372
467
  };
373
468
 
374
469
  renderer.tablerow = function (content) {
375
- return `<tr class="border-b border-slate-200 last:border-b-0 hover:bg-slate-50/50 transition-colors">${content}</tr>`;
470
+ return `<tr>${content}</tr>`;
376
471
  };
377
472
 
378
473
  renderer.tablecell = function (content, flags) {
379
474
  const tag: string = flags.header ? "th" : "td";
380
- const align: string = flags.align ? ` text-${flags.align}` : "";
381
- const headerClass: string = flags.header
382
- ? " font-semibold text-slate-900 bg-slate-50"
383
- : " text-slate-600";
384
- return `<${tag} class="px-4 py-2.5${align}${headerClass}">${content}</${tag}>`;
475
+ const align: string = flags.align
476
+ ? ` style="text-align:${flags.align}"`
477
+ : "";
478
+ return `<${tag}${align}>${content}</${tag}>`;
385
479
  };
386
480
 
387
481
  // Inline code
388
482
  renderer.codespan = function (code) {
389
483
  const escaped: string = Markdown.escapeHtml(code);
390
- return `<code class="rounded-md bg-slate-100 px-1.5 py-0.5 text-sm text-slate-700 font-mono">${escaped}</code>`;
484
+ return `<code class="docs-code-inline">${escaped}</code>`;
391
485
  };
392
486
 
393
487
  this.docsRenderer = renderer;
@@ -553,10 +553,18 @@ export const QueryOnCallPoliciesTool: ObservabilityTool = {
553
553
  */
554
554
 
555
555
  /*
556
- * Roster fields are persisted on the schedule and refreshed every minute by
557
- * the RefreshHandoffTime worker (overrides included) — the same source the
558
- * dashboard's "On Call Now" column reads. Reading them costs one RBAC-checked
559
- * query instead of re-resolving schedule layers per request.
556
+ * Roster fields are persisted on the schedule and refreshed by the
557
+ * RefreshHandoffTime worker — the same source the dashboard's "On Call Now"
558
+ * column reads. Reading them costs one RBAC-checked query instead of
559
+ * re-resolving schedule layers per request.
560
+ *
561
+ * They include user overrides, with one documented limit: the roster is
562
+ * resolved in a policy's context only when the schedule is attached to exactly
563
+ * one on-call policy (OnCallDutyPolicyScheduleService.getSingleAttachedPolicyId).
564
+ * A schedule shared by two or more policies gets a policy-blind roster —
565
+ * global overrides only — because a single stored roster cannot represent
566
+ * divergent per-policy substitutions. Alert routing always resolves live with
567
+ * the escalating policy's id, so it is unaffected.
560
568
  */
561
569
  const scheduleRosterSelect: Select<OnCallDutyPolicySchedule> = {
562
570
  _id: true,
@@ -26,6 +26,18 @@ export type StatementParameter = {
26
26
  value: RecordValue;
27
27
  };
28
28
 
29
+ /*
30
+ * Escape LIKE/ILIKE metacharacters so a user-supplied value matches
31
+ * literally. Without this, "svc_" matches svcX (a false positive) and a
32
+ * value containing "%" silently corrupts the whole pattern — the worst
33
+ * possible failure mode for a security-events filter. ClickHouse honors
34
+ * backslash escapes inside (I)LIKE patterns; backslash itself must be
35
+ * escaped first.
36
+ */
37
+ export function escapeIlikePattern(value: string): string {
38
+ return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
39
+ }
40
+
29
41
  /**
30
42
  * A table-qualified column reference (`table.column`) bound as TWO
31
43
  * Identifier parameters. ClickHouse treats a dotted string bound as a
@@ -133,7 +145,7 @@ export class Statement implements BaseQueryParams {
133
145
  } else if (v.value instanceof ObjectID) {
134
146
  finalValue = v.value.toString();
135
147
  } else if (v.value instanceof Search) {
136
- finalValue = `%${v.value.toString()}%`;
148
+ finalValue = `%${escapeIlikePattern(v.value.toString())}%`;
137
149
  } else if (
138
150
  v.value instanceof LessThan ||
139
151
  v.value instanceof LessThanOrEqual ||