@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
@@ -18,11 +18,49 @@ import {
18
18
  DropResult,
19
19
  } from "react-beautiful-dnd";
20
20
 
21
+ /*
22
+ * A pool of columns the viewer can bring INTO the picker, rather than one
23
+ * checklist entry per possibility.
24
+ *
25
+ * Attribute columns are the motivating case: the keys inside a row's
26
+ * `attributes` map differ per event class and per source, so a project can
27
+ * easily have several hundred of them. Listing every one as a checkbox turns
28
+ * "which columns do I want?" into a scroll through noise (and hands "Show all"
29
+ * a way to put 500 columns on screen), so they are offered through a search
30
+ * box that only materializes what was asked for.
31
+ */
32
+ export interface AddableColumnsConfig<
33
+ TBaseModel extends BaseModel | AnalyticsBaseModel,
34
+ > {
35
+ title: string;
36
+ description?: string | undefined;
37
+ placeholder?: string | undefined;
38
+ // Still fetching the pool. The section says so rather than reading as empty.
39
+ isLoading?: boolean | undefined;
40
+ errorMessage?: string | undefined;
41
+ // Shown when the pool itself is empty, e.g. nothing has been ingested yet.
42
+ emptyMessage?: string | undefined;
43
+ /*
44
+ * Every column that could be added, in the order they should be offered.
45
+ * Ones already in the picker are filtered out here rather than by the
46
+ * caller, so a column added and then removed reappears in the pool.
47
+ */
48
+ columns: Array<CustomizableColumn<TBaseModel>>;
49
+ /*
50
+ * How many matches to render at once. The pool can be in the thousands and
51
+ * an unbounded list would put all of it in the DOM; the search box is what
52
+ * gets you to the rest.
53
+ */
54
+ maxResults?: number | undefined;
55
+ }
56
+
21
57
  export interface ComponentProps<
22
58
  TBaseModel extends BaseModel | AnalyticsBaseModel,
23
59
  > {
24
60
  // Every customizable column, already in the order the table renders them.
25
61
  columns: Array<CustomizableColumn<TBaseModel>>;
62
+ // Optional pool of columns the viewer can add. Omit to hide the whole section.
63
+ addableColumns?: AddableColumnsConfig<TBaseModel> | undefined;
26
64
  onSave: (columns: Array<CustomizableColumn<TBaseModel>>) => void;
27
65
  onClose: () => void;
28
66
  // Drop the stored layout and go back to what the table ships with.
@@ -97,6 +135,109 @@ const ColumnCustomizationModal: ColumnCustomizationModalFunction = <
97
135
  const isEverythingVisible: boolean = visibleCount === entries.length;
98
136
  const isOnlyOneVisible: boolean = visibleCount <= 1;
99
137
 
138
+ /*
139
+ * ---------------------------------------------------------------------
140
+ * The "add a column" pool
141
+ * ---------------------------------------------------------------------
142
+ */
143
+ const [addSearchText, setAddSearchText] = useState<string>("");
144
+
145
+ const stagedIds: Set<string> = useMemo(() => {
146
+ return new Set(
147
+ entries.map((entry: CustomizableColumn<TBaseModel>) => {
148
+ return entry.id;
149
+ }),
150
+ );
151
+ }, [entries]);
152
+
153
+ // Everything in the pool that is not already sitting in the list above.
154
+ const addableColumns: Array<CustomizableColumn<TBaseModel>> = useMemo(() => {
155
+ return (props.addableColumns?.columns || []).filter(
156
+ (entry: CustomizableColumn<TBaseModel>) => {
157
+ return !stagedIds.has(entry.id);
158
+ },
159
+ );
160
+ }, [props.addableColumns?.columns, stagedIds]);
161
+
162
+ const normalizedAddSearch: string = addSearchText.trim().toLowerCase();
163
+
164
+ const matchingAddableColumns: Array<CustomizableColumn<TBaseModel>> =
165
+ useMemo(() => {
166
+ if (normalizedAddSearch.length === 0) {
167
+ return addableColumns;
168
+ }
169
+
170
+ return addableColumns.filter((entry: CustomizableColumn<TBaseModel>) => {
171
+ return (entry.column.title || "")
172
+ .toLowerCase()
173
+ .includes(normalizedAddSearch);
174
+ });
175
+ }, [addableColumns, normalizedAddSearch]);
176
+
177
+ const addableResultLimit: number = props.addableColumns?.maxResults ?? 25;
178
+
179
+ const shownAddableColumns: Array<CustomizableColumn<TBaseModel>> =
180
+ useMemo(() => {
181
+ return matchingAddableColumns.slice(0, addableResultLimit);
182
+ }, [matchingAddableColumns, addableResultLimit]);
183
+
184
+ type AddColumnFunction = (entry: CustomizableColumn<TBaseModel>) => void;
185
+
186
+ /*
187
+ * An added column goes on the end and starts visible. Adding a column you
188
+ * then have to go and switch on would be a strange thing to ask of someone
189
+ * who just searched for it by name.
190
+ */
191
+ const addColumn: AddColumnFunction = (
192
+ entry: CustomizableColumn<TBaseModel>,
193
+ ): void => {
194
+ setEntries((current: Array<CustomizableColumn<TBaseModel>>) => {
195
+ if (
196
+ current.some((existing: CustomizableColumn<TBaseModel>) => {
197
+ return existing.id === entry.id;
198
+ })
199
+ ) {
200
+ return current;
201
+ }
202
+
203
+ return [...current, { ...entry, isVisible: true, isPinned: false }];
204
+ });
205
+ };
206
+
207
+ type RemoveColumnFunction = (id: string) => void;
208
+
209
+ const removeColumn: RemoveColumnFunction = (id: string): void => {
210
+ setEntries((current: Array<CustomizableColumn<TBaseModel>>) => {
211
+ const target: CustomizableColumn<TBaseModel> | undefined = current.find(
212
+ (entry: CustomizableColumn<TBaseModel>) => {
213
+ return entry.id === id;
214
+ },
215
+ );
216
+
217
+ if (!target || !target.isRemovable) {
218
+ return current;
219
+ }
220
+
221
+ /*
222
+ * Removing is hiding plus forgetting, so it is bound by the same rule:
223
+ * the last column standing cannot go.
224
+ */
225
+ const isLastVisible: boolean =
226
+ target.isVisible &&
227
+ current.filter((entry: CustomizableColumn<TBaseModel>) => {
228
+ return entry.isVisible;
229
+ }).length <= 1;
230
+
231
+ if (isLastVisible) {
232
+ return current;
233
+ }
234
+
235
+ return current.filter((entry: CustomizableColumn<TBaseModel>) => {
236
+ return entry.id !== id;
237
+ });
238
+ });
239
+ };
240
+
100
241
  type ToggleFunction = (id: string, isVisible: boolean) => void;
101
242
 
102
243
  const toggleColumn: ToggleFunction = (
@@ -303,6 +444,22 @@ const ColumnCustomizationModal: ColumnCustomizationModalFunction = <
303
444
  moveColumn(index, index + 1);
304
445
  }}
305
446
  />
447
+ {entry.isRemovable && (
448
+ <Button
449
+ buttonSize={ButtonSize.Small}
450
+ buttonStyle={ButtonStyleType.ICON}
451
+ icon={IconProp.Close}
452
+ title=""
453
+ tooltip="Remove column"
454
+ ariaLabel={`Remove ${entry.column.title}`}
455
+ className="text-gray-400 hover:bg-red-100 hover:text-red-600 disabled:text-gray-200 disabled:hover:bg-transparent"
456
+ dataTestId={`column-remove-${entry.id}`}
457
+ disabled={isOnlyVisibleColumn}
458
+ onClick={() => {
459
+ removeColumn(entry.id);
460
+ }}
461
+ />
462
+ )}
306
463
  </div>
307
464
  </div>
308
465
  );
@@ -413,6 +570,157 @@ const ColumnCustomizationModal: ColumnCustomizationModalFunction = <
413
570
  );
414
571
  };
415
572
 
573
+ type GetAddColumnSectionFunction = () => ReactElement;
574
+
575
+ const getAddColumnSection: GetAddColumnSectionFunction = (): ReactElement => {
576
+ const config: AddableColumnsConfig<TBaseModel> | undefined =
577
+ props.addableColumns;
578
+
579
+ if (!config) {
580
+ return <></>;
581
+ }
582
+
583
+ type GetBodyFunction = () => ReactElement;
584
+
585
+ const getBody: GetBodyFunction = (): ReactElement => {
586
+ if (config.errorMessage) {
587
+ return (
588
+ <p
589
+ className="px-3 py-4 text-sm text-red-500"
590
+ data-testid="add-column-error"
591
+ >
592
+ {config.errorMessage}
593
+ </p>
594
+ );
595
+ }
596
+
597
+ if (config.isLoading) {
598
+ return (
599
+ <p
600
+ className="px-3 py-4 text-sm text-gray-500"
601
+ data-testid="add-column-loading"
602
+ >
603
+ Loading...
604
+ </p>
605
+ );
606
+ }
607
+
608
+ if (addableColumns.length === 0) {
609
+ return (
610
+ <p
611
+ className="px-3 py-4 text-sm text-gray-500"
612
+ data-testid="add-column-empty"
613
+ >
614
+ {(config.columns || []).length === 0
615
+ ? config.emptyMessage || "Nothing to add."
616
+ : "Every one of these is already in the list above."}
617
+ </p>
618
+ );
619
+ }
620
+
621
+ if (shownAddableColumns.length === 0) {
622
+ return (
623
+ <p
624
+ className="px-3 py-4 text-sm text-gray-500"
625
+ data-testid="add-column-no-results"
626
+ >
627
+ No matches for &quot;{addSearchText}&quot;.
628
+ </p>
629
+ );
630
+ }
631
+
632
+ return (
633
+ <div className="divide-y divide-gray-100">
634
+ {shownAddableColumns.map((entry: CustomizableColumn<TBaseModel>) => {
635
+ return (
636
+ <div
637
+ key={entry.id}
638
+ className="flex items-center gap-2 px-3 py-2 transition-colors hover:bg-gray-50"
639
+ >
640
+ <span className="min-w-0 flex-1 break-words text-sm text-gray-700">
641
+ {entry.column.title}
642
+ </span>
643
+ <Button
644
+ buttonSize={ButtonSize.Small}
645
+ buttonStyle={ButtonStyleType.ICON}
646
+ icon={IconProp.Add}
647
+ title=""
648
+ tooltip="Add column"
649
+ ariaLabel={`Add ${entry.column.title}`}
650
+ className="flex-none text-gray-400 hover:bg-gray-200 hover:text-gray-700"
651
+ dataTestId={`add-column-${entry.id}`}
652
+ onClick={() => {
653
+ addColumn(entry);
654
+ }}
655
+ />
656
+ </div>
657
+ );
658
+ })}
659
+ </div>
660
+ );
661
+ };
662
+
663
+ return (
664
+ <div className="space-y-2 pt-1" data-testid="add-column-section">
665
+ <div>
666
+ <h4 className="text-sm font-medium text-gray-900">{config.title}</h4>
667
+ {config.description ? (
668
+ <p className="text-xs text-gray-500">{config.description}</p>
669
+ ) : (
670
+ <></>
671
+ )}
672
+ </div>
673
+
674
+ <div className="relative">
675
+ <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
676
+ <Icon icon={IconProp.Search} className="h-4 w-4 text-gray-400" />
677
+ </div>
678
+ <Input
679
+ type={InputType.TEXT}
680
+ placeholder={config.placeholder || "Search..."}
681
+ value={addSearchText}
682
+ dataTestId="add-column-search"
683
+ outerDivClassName="w-full"
684
+ className="block w-full rounded-md border border-gray-300 bg-white py-2 pl-9 pr-9 text-sm placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
685
+ onChange={(value: string) => {
686
+ setAddSearchText(value);
687
+ }}
688
+ />
689
+ {addSearchText.length > 0 && (
690
+ <button
691
+ type="button"
692
+ aria-label="Clear search"
693
+ title="Clear search"
694
+ data-testid="add-column-search-clear"
695
+ onClick={() => {
696
+ setAddSearchText("");
697
+ }}
698
+ className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 transition-colors hover:text-gray-600 focus:outline-none focus-visible:text-gray-600"
699
+ >
700
+ <Icon icon={IconProp.Close} className="h-4 w-4" />
701
+ </button>
702
+ )}
703
+ </div>
704
+
705
+ <div className="max-h-48 overflow-y-auto overscroll-contain rounded-md border border-gray-200 bg-white">
706
+ {getBody()}
707
+ </div>
708
+
709
+ {/*
710
+ * Said out loud rather than silently truncating: a viewer who cannot
711
+ * see their key otherwise has no way to know that typing more of it
712
+ * would find it.
713
+ */}
714
+ {matchingAddableColumns.length > shownAddableColumns.length && (
715
+ <p className="text-xs text-gray-400" data-testid="add-column-hint">
716
+ Showing {shownAddableColumns.length} of{" "}
717
+ {matchingAddableColumns.length}. Search to narrow this down.
718
+ </p>
719
+ )}
720
+ </div>
721
+ );
722
+ };
723
+
416
724
  return (
417
725
  <Modal
418
726
  title={props.title || "Customize Columns"}
@@ -515,6 +823,8 @@ const ColumnCustomizationModal: ColumnCustomizationModalFunction = <
515
823
  ? "Clear the search to reorder columns."
516
824
  : "Drag a row, or use the arrows, to change the column order."}
517
825
  </p>
826
+
827
+ {getAddColumnSection()}
518
828
  </div>
519
829
  </Modal>
520
830
  );
@@ -153,6 +153,12 @@ export interface CustomizableColumn<
153
153
  isVisible: boolean;
154
154
  // Pinned columns are always shown and cannot be moved.
155
155
  isPinned: boolean;
156
+ /*
157
+ * The picker may drop this column entirely, not just switch it off. True
158
+ * only for columns the viewer added themselves (attribute columns); a
159
+ * pinned column is never removable, since it is not the viewer's to touch.
160
+ */
161
+ isRemovable?: boolean | undefined;
156
162
  }
157
163
 
158
164
  export type IsDefaultHiddenFunction = <
@@ -214,7 +220,13 @@ export const getCustomizableColumns: GetCustomizableColumnsFunction = <
214
220
  }
215
221
  }
216
222
 
217
- return { id, column, isVisible, isPinned };
223
+ return {
224
+ id,
225
+ column,
226
+ isVisible,
227
+ isPinned,
228
+ isRemovable: Boolean(column.isRemovable) && !isPinned,
229
+ };
218
230
  },
219
231
  );
220
232
 
@@ -32,8 +32,8 @@ export interface ComponentProps {
32
32
  * that scales when a user groups by many attributes.
33
33
  *
34
34
  * Click a variable to copy `{{var}}` to the clipboard; the chip flips
35
- * to a "Copied" state briefly so the user gets feedback without a
36
- * toast.
35
+ * to a "Copied" state briefly so the feedback lands on the thing that
36
+ * was clicked.
37
37
  */
38
38
  const TemplateVariablesModal: FunctionComponent<ComponentProps> = (
39
39
  props: ComponentProps,
@@ -150,11 +150,11 @@ const SideOver: FunctionComponent<ComponentProps> = (
150
150
  /*
151
151
  * z-30 clears every tier of page chrome (sticky headers and table headers
152
152
  * at z-10/z-20, map and log-viewer toolbars at z-20) while staying under
153
- * the app-wide overlays that must remain visible over a panel: Toast and
154
- * the AI chat panel at z-40, Modal at z-50, portalled dropdown menus at
155
- * z-60. Deliberately NOT z-40 — Toast and AIChatPanel are mounted ahead of
156
- * the routed page in App.tsx, so an equal z-index would be broken in this
157
- * panel's favour by document order and toasts would disappear behind it.
153
+ * the app-wide overlays that must remain visible over a panel: the AI chat
154
+ * panel at z-40, Modal at z-50, portalled dropdown menus at z-60.
155
+ * Deliberately NOT z-40 — AIChatPanel is mounted ahead of the routed page
156
+ * in App.tsx, so an equal z-index would be broken in this panel's favour
157
+ * by document order and the AI drawer would disappear behind it.
158
158
  *
159
159
  * role="dialog" without aria-modal, because the panel genuinely is not
160
160
  * modal: everything outside it stays clickable by design (both topology
@@ -4,6 +4,7 @@ import StatusPageResourceExplorerUtil, {
4
4
  StatusPageResourceGridModel,
5
5
  } from "../../../Utils/StatusPage/ResourceExplorer";
6
6
  import Button, { ButtonSize, ButtonStyleType } from "../Button/Button";
7
+ import CheckboxElement from "../Checkbox/Checkbox";
7
8
  import Icon, { ThickProp } from "../Icon/Icon";
8
9
  import React, { FunctionComponent, ReactElement, useMemo } from "react";
9
10
 
@@ -22,6 +23,18 @@ export interface ComponentProps {
22
23
  isEditable: boolean;
23
24
  isDeleteable: boolean;
24
25
 
26
+ /*
27
+ * See ResourceList: a checkbox whose only action is removing, so it is drawn
28
+ * only for somebody who may remove. A grid group is the same resources as a
29
+ * list group in a different arrangement, and clearing twenty seven of them
30
+ * one at a time is no better here than it is there (issue #3419) - so the
31
+ * chips carry the same box the rows do, and the bulk bar the pane draws above
32
+ * is shared between the two.
33
+ */
34
+ isSelectable: boolean;
35
+ selectedResourceIds: Set<string>;
36
+ onToggleResourceSelected: (statusPageResource: StatusPageResource) => void;
37
+
25
38
  onAddToCell: (rowValue: string | null, columnValue: string | null) => void;
26
39
  onEdit: (statusPageResource: StatusPageResource) => void;
27
40
  onDelete: (statusPageResource: StatusPageResource) => void;
@@ -50,6 +63,57 @@ const ResourceGrid: FunctionComponent<ComponentProps> = (
50
63
  });
51
64
  }, [props.statusPageResources, props.rowValues, props.columnValues]);
52
65
 
66
+ type IsResourceSelectedFunction = (
67
+ statusPageResource: StatusPageResource,
68
+ ) => boolean;
69
+
70
+ const isResourceSelected: IsResourceSelectedFunction = (
71
+ statusPageResource: StatusPageResource,
72
+ ): boolean => {
73
+ if (!props.isSelectable) {
74
+ return false;
75
+ }
76
+
77
+ const resourceId: string | null =
78
+ StatusPageResourceExplorerUtil.getResourceId(statusPageResource);
79
+
80
+ return Boolean(resourceId && props.selectedResourceIds.has(resourceId));
81
+ };
82
+
83
+ type RenderSelectFunction = (
84
+ statusPageResource: StatusPageResource,
85
+ ) => ReactElement;
86
+
87
+ const renderSelect: RenderSelectFunction = (
88
+ statusPageResource: StatusPageResource,
89
+ ): ReactElement => {
90
+ if (!props.isSelectable) {
91
+ return <></>;
92
+ }
93
+
94
+ const resourceId: string | null =
95
+ StatusPageResourceExplorerUtil.getResourceId(statusPageResource);
96
+ const name: string =
97
+ StatusPageResourceExplorerUtil.getResourceName(statusPageResource);
98
+
99
+ return (
100
+ <span
101
+ className="flex flex-shrink-0 items-center"
102
+ data-testid="status-page-resource-grid-select"
103
+ >
104
+ <CheckboxElement
105
+ value={isResourceSelected(statusPageResource)}
106
+ disabled={!resourceId}
107
+ ariaLabel={`Select ${name}`}
108
+ hoverText={`Select ${name}`}
109
+ onChange={() => {
110
+ props.onToggleResourceSelected(statusPageResource);
111
+ }}
112
+ />
113
+ </span>
114
+ );
115
+ };
116
+
53
117
  type RenderResourceChipFunction = (
54
118
  statusPageResource: StatusPageResource,
55
119
  ) => ReactElement;
@@ -60,12 +124,21 @@ const ResourceGrid: FunctionComponent<ComponentProps> = (
60
124
  const name: string =
61
125
  StatusPageResourceExplorerUtil.getResourceName(statusPageResource);
62
126
 
127
+ const isSelected: boolean = isResourceSelected(statusPageResource);
128
+
63
129
  return (
64
130
  <div
65
131
  key={statusPageResource._id?.toString()}
66
- className="group flex items-center justify-between gap-2 rounded-lg border border-gray-200 bg-white px-2 py-1.5 text-xs shadow-sm transition-shadow hover:shadow"
132
+ className={`group flex items-center justify-between gap-2 rounded-lg border px-2 py-1.5 text-xs shadow-sm transition-shadow hover:shadow ${
133
+ isSelected
134
+ ? "border-indigo-300 bg-indigo-50"
135
+ : "border-gray-200 bg-white"
136
+ }`}
67
137
  data-testid="status-page-resource-grid-cell-item"
138
+ data-selected={isSelected}
68
139
  >
140
+ {renderSelect(statusPageResource)}
141
+
69
142
  <div className="min-w-0 flex-1">
70
143
  <div className="truncate font-medium text-gray-900">
71
144
  {props.getResourceElement(statusPageResource)}
@@ -248,9 +321,16 @@ const ResourceGrid: FunctionComponent<ComponentProps> = (
248
321
  return (
249
322
  <div
250
323
  key={statusPageResource._id?.toString()}
251
- className="flex items-center justify-between gap-2 rounded-lg border border-amber-200 bg-white px-3 py-2 text-xs"
324
+ className={`flex items-center justify-between gap-2 rounded-lg border border-amber-200 px-3 py-2 text-xs ${
325
+ isResourceSelected(statusPageResource)
326
+ ? "bg-indigo-50"
327
+ : "bg-white"
328
+ }`}
252
329
  data-testid="status-page-resource-grid-orphan"
330
+ data-selected={isResourceSelected(statusPageResource)}
253
331
  >
332
+ {renderSelect(statusPageResource)}
333
+
254
334
  <div className="min-w-0 flex-1">
255
335
  <div className="truncate font-medium text-gray-900">
256
336
  {props.getResourceElement(statusPageResource)}