@oneuptime/common 11.7.2 → 11.7.4

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 (414) hide show
  1. package/Models/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
  2. package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
  3. package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
  4. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  5. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  6. package/Models/DatabaseModels/Index.ts +5 -0
  7. package/Models/DatabaseModels/LogDropFilter.ts +89 -0
  8. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
  9. package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
  10. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  11. package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
  12. package/Models/DatabaseModels/TableView.ts +40 -0
  13. package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
  14. package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
  15. package/Server/API/AlertAPI.ts +32 -17
  16. package/Server/API/BaseAPI.ts +31 -2
  17. package/Server/API/BaseAnalyticsAPI.ts +36 -2
  18. package/Server/API/CommonAPI.ts +57 -0
  19. package/Server/API/IncidentAPI.ts +60 -33
  20. package/Server/API/IncidentEpisodeAPI.ts +31 -16
  21. package/Server/API/MonitorAPI.ts +30 -0
  22. package/Server/API/MonitorTemplateAPI.ts +8 -0
  23. package/Server/API/ProbeAPI.ts +196 -15
  24. package/Server/API/ProjectAPI.ts +79 -1
  25. package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
  26. package/Server/API/StatusPageAPI.ts +74 -37
  27. package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  34. package/Server/Services/AlertEpisodeService.ts +47 -0
  35. package/Server/Services/AlertService.ts +125 -0
  36. package/Server/Services/BillingService.ts +98 -0
  37. package/Server/Services/DatabaseService.ts +184 -20
  38. package/Server/Services/IncidentEpisodeService.ts +47 -0
  39. package/Server/Services/IncidentService.ts +123 -0
  40. package/Server/Services/IncidentTemplateService.ts +123 -1
  41. package/Server/Services/Index.ts +2 -0
  42. package/Server/Services/LogDropFilterService.ts +133 -0
  43. package/Server/Services/MonitorProbeService.ts +94 -4
  44. package/Server/Services/MonitorService.ts +48 -43
  45. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
  46. package/Server/Services/ProbeService.ts +66 -0
  47. package/Server/Services/ProjectService.ts +131 -7
  48. package/Server/Services/RecommendationDismissalService.ts +52 -0
  49. package/Server/Services/RunbookAgentJobService.ts +68 -7
  50. package/Server/Services/ScheduledMaintenanceService.ts +103 -0
  51. package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
  52. package/Server/Services/StatusPageGroupService.ts +186 -0
  53. package/Server/Services/TraceDropFilterService.ts +133 -0
  54. package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
  55. package/Server/Types/Workflow/Components/API/Get.ts +12 -0
  56. package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
  57. package/Server/Types/Workflow/Components/API/Post.ts +12 -0
  58. package/Server/Types/Workflow/Components/API/Put.ts +13 -0
  59. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
  60. package/Server/Utils/DropFilterValidation.ts +127 -0
  61. package/Server/Utils/Errors.ts +5 -0
  62. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  63. package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
  64. package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
  65. package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
  66. package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
  67. package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
  68. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
  69. package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
  70. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
  71. package/Tests/Server/API/ProbeAPI.test.ts +422 -53
  72. package/Tests/Server/API/ProjectAPI.test.ts +180 -0
  73. package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
  74. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
  75. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
  76. package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
  77. package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
  78. package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
  79. package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
  80. package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
  81. package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
  82. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
  83. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
  84. package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
  85. package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
  86. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
  87. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
  88. package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
  89. package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
  90. package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
  91. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
  92. package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
  93. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
  94. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
  95. package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
  96. package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
  97. package/Tests/Types/DateUserTimezone.test.ts +52 -0
  98. package/Tests/Types/JSONFunctions.test.ts +103 -1
  99. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
  100. package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
  101. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
  102. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
  103. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
  104. package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
  105. package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
  106. package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
  107. package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
  108. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
  109. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
  110. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
  111. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
  112. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
  113. package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
  114. package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
  115. package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
  116. package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
  117. package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
  118. package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
  119. package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
  120. package/Tests/UI/Components/InfoCard.test.tsx +358 -0
  121. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
  122. package/Tests/UI/Components/Modal.test.tsx +43 -5
  123. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
  124. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
  125. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +999 -0
  126. package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
  127. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
  128. package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
  129. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
  130. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
  131. package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
  132. package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
  133. package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
  134. package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
  135. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
  136. package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
  137. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
  138. package/Tests/Utils/UserPreferences.test.ts +563 -0
  139. package/Types/Date.ts +15 -6
  140. package/Types/JSONFunctions.ts +7 -2
  141. package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
  142. package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
  143. package/Types/Monitor/MonitorStep.ts +10 -1
  144. package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
  145. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
  146. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
  147. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
  148. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
  149. package/Types/Permission.ts +45 -0
  150. package/Types/Recommendation/RecommendationType.ts +20 -0
  151. package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
  152. package/Types/Runbook/RunbookStepTimeout.ts +202 -0
  153. package/Types/Telemetry/DropFilterSampling.ts +92 -0
  154. package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
  155. package/UI/Components/Accordion/Accordion.tsx +10 -2
  156. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
  157. package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
  158. package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
  159. package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
  160. package/UI/Components/Error/PageError.tsx +0 -4
  161. package/UI/Components/ErrorBoundary.tsx +252 -15
  162. package/UI/Components/InfoCard/InfoCard.tsx +58 -1
  163. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
  164. package/UI/Components/MasterPage/MasterPage.tsx +65 -0
  165. package/UI/Components/Modal/Modal.tsx +16 -60
  166. package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
  167. package/UI/Components/ModelTable/BaseModelTable.tsx +503 -71
  168. package/UI/Components/ModelTable/Column.ts +17 -0
  169. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +523 -0
  170. package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
  171. package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
  172. package/UI/Components/ModelTable/TableView.tsx +24 -2
  173. package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
  174. package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
  175. package/UI/Components/SideMenu/SideMenu.tsx +24 -4
  176. package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
  177. package/UI/Components/Table/Table.tsx +14 -1
  178. package/UI/Components/Table/TableRow.tsx +180 -175
  179. package/UI/Components/Table/Types/Column.ts +2 -0
  180. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
  181. package/UI/Utils/DropdownAlignment.ts +162 -0
  182. package/UI/Utils/TableFilterUrlState.ts +59 -20
  183. package/Utils/API.ts +106 -2
  184. package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
  185. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
  186. package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
  187. package/Utils/StatusPage/GroupTree.ts +307 -0
  188. package/Utils/StatusPage/ResourceUptime.ts +90 -17
  189. package/Utils/UserPreferences.ts +53 -0
  190. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
  191. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
  193. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
  195. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  197. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  199. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  201. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
  203. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
  205. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
  207. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
  208. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  209. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
  211. package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/TableView.js +40 -0
  213. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  216. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
  217. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  218. package/build/dist/Server/API/AlertAPI.js +22 -13
  219. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  220. package/build/dist/Server/API/BaseAPI.js +22 -1
  221. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  222. package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
  223. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  224. package/build/dist/Server/API/CommonAPI.js +42 -0
  225. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  226. package/build/dist/Server/API/IncidentAPI.js +43 -26
  227. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  228. package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
  229. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  230. package/build/dist/Server/API/MonitorAPI.js +23 -0
  231. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  232. package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
  233. package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
  234. package/build/dist/Server/API/ProbeAPI.js +131 -15
  235. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  236. package/build/dist/Server/API/ProjectAPI.js +62 -3
  237. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  238. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
  239. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  240. package/build/dist/Server/API/StatusPageAPI.js +61 -33
  241. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  242. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
  243. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
  249. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  256. package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
  257. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  258. package/build/dist/Server/Services/AlertService.js +95 -0
  259. package/build/dist/Server/Services/AlertService.js.map +1 -1
  260. package/build/dist/Server/Services/BillingService.js +71 -0
  261. package/build/dist/Server/Services/BillingService.js.map +1 -1
  262. package/build/dist/Server/Services/DatabaseService.js +140 -17
  263. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  264. package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
  265. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  266. package/build/dist/Server/Services/IncidentService.js +100 -0
  267. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  268. package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
  269. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  270. package/build/dist/Server/Services/Index.js +2 -0
  271. package/build/dist/Server/Services/Index.js.map +1 -1
  272. package/build/dist/Server/Services/LogDropFilterService.js +93 -0
  273. package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
  274. package/build/dist/Server/Services/MonitorProbeService.js +73 -5
  275. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  276. package/build/dist/Server/Services/MonitorService.js +34 -39
  277. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  278. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
  279. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  280. package/build/dist/Server/Services/ProbeService.js +64 -0
  281. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  282. package/build/dist/Server/Services/ProjectService.js +103 -7
  283. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  284. package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
  285. package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
  286. package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
  287. package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
  288. package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
  289. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  290. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
  291. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  292. package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
  293. package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
  294. package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
  295. package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
  296. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
  297. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  298. package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
  299. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  300. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
  301. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  302. package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
  303. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  304. package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
  305. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  306. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
  307. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
  308. package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
  309. package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
  310. package/build/dist/Server/Utils/Errors.js +3 -0
  311. package/build/dist/Server/Utils/Errors.js.map +1 -1
  312. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
  313. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  314. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
  315. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  316. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
  317. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  318. package/build/dist/Types/Date.js +14 -4
  319. package/build/dist/Types/Date.js.map +1 -1
  320. package/build/dist/Types/JSONFunctions.js +8 -3
  321. package/build/dist/Types/JSONFunctions.js.map +1 -1
  322. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
  323. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
  324. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  325. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  326. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  327. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
  328. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  329. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
  330. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
  331. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
  332. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
  333. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
  334. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
  335. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
  336. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
  337. package/build/dist/Types/Permission.js +40 -0
  338. package/build/dist/Types/Permission.js.map +1 -1
  339. package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
  340. package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
  341. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
  342. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
  343. package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
  344. package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
  345. package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
  346. package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
  347. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
  348. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  349. package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
  350. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  351. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
  352. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  353. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
  354. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  355. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
  356. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
  357. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
  358. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  359. package/build/dist/UI/Components/Error/PageError.js +1 -3
  360. package/build/dist/UI/Components/Error/PageError.js.map +1 -1
  361. package/build/dist/UI/Components/ErrorBoundary.js +145 -11
  362. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  363. package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
  364. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  365. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
  366. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  367. package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
  368. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  369. package/build/dist/UI/Components/Modal/Modal.js +4 -32
  370. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  371. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  372. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +308 -40
  373. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  374. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +230 -0
  375. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
  376. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
  377. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
  378. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
  379. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
  380. package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
  381. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  382. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
  383. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
  384. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
  385. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
  386. package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
  387. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  388. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
  389. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
  390. package/build/dist/UI/Components/Table/Table.js +15 -2
  391. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  392. package/build/dist/UI/Components/Table/TableRow.js +11 -6
  393. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  394. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
  395. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  396. package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
  397. package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
  398. package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
  399. package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
  400. package/build/dist/Utils/API.js +39 -2
  401. package/build/dist/Utils/API.js.map +1 -1
  402. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
  403. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
  404. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
  405. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
  406. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
  407. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
  408. package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
  409. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
  410. package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
  411. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  412. package/build/dist/Utils/UserPreferences.js +33 -0
  413. package/build/dist/Utils/UserPreferences.js.map +1 -1
  414. package/package.json +1 -1
@@ -58,6 +58,23 @@ import TableColumn from "../Table/Types/Column";
58
58
  import FieldType from "../Types/FieldType";
59
59
  import ModelTableColumn from "./Column";
60
60
  import Columns from "./Columns";
61
+ import ColumnCustomizationModal from "./ColumnCustomizationModal";
62
+ import {
63
+ ColumnPreference,
64
+ CustomizableColumn,
65
+ applyColumnPreference,
66
+ buildColumnPreference,
67
+ fromJSON as columnPreferenceFromJSON,
68
+ getCustomizableColumns,
69
+ isEmptyColumnPreference,
70
+ sanitizeColumnPreference,
71
+ getColumnIds,
72
+ toJSON as columnPreferenceToJSON,
73
+ } from "./ColumnPreference";
74
+ import { CustomFieldsColumnKey } from "./CustomFieldColumns";
75
+ import useCustomFieldColumns, {
76
+ CustomFieldColumnsResult,
77
+ } from "./useCustomFieldColumns";
61
78
  import { getExportKeysFromColumn } from "./ExportFromColumns";
62
79
  import {
63
80
  getRelationSelectFromColumns,
@@ -323,6 +340,26 @@ export interface BaseTableProps<
323
340
  * outlive the page.
324
341
  */
325
342
  disableUrlState?: boolean | undefined;
343
+
344
+ /**
345
+ * Hide the "Columns" control and always render the declared column set.
346
+ *
347
+ * Column customization is on by default for every table that renders
348
+ * columns. Turn it off for tables whose layout is load-bearing — a
349
+ * two-column key/value table, or one the surrounding page reads positions
350
+ * out of.
351
+ */
352
+ disableColumnCustomization?: boolean | undefined;
353
+
354
+ /**
355
+ * The model holding this resource's custom field *definitions* — e.g.
356
+ * `MonitorCustomField` for a table of monitors. Set it and every custom
357
+ * field the project has defined becomes an optional column, off by default
358
+ * and listed in the column picker.
359
+ *
360
+ * Only meaningful for resources with a `customFields` column of their own.
361
+ */
362
+ customFieldsModelType?: { new (): BaseModel } | undefined;
326
363
  }
327
364
 
328
365
  export interface ComponentProps<
@@ -395,6 +432,115 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
395
432
  [],
396
433
  );
397
434
 
435
+ /*
436
+ * ---------------------------------------------------------------------
437
+ * Viewer-customizable columns
438
+ * ---------------------------------------------------------------------
439
+ *
440
+ * The viewer's layout (which columns are on, and in what order) is a
441
+ * personal, presentational preference, so it lives in localStorage next to
442
+ * the page size rather than on the server: saved views are named, explicit,
443
+ * plan-gated and need write permission, none of which should stand between
444
+ * someone and hiding a column they don't care about. A saved view can still
445
+ * carry a layout of its own, and when one is active it wins.
446
+ *
447
+ * Custom fields are appended to the declared columns as additional,
448
+ * off-by-default columns; from here on they are ordinary columns.
449
+ */
450
+ const customFieldColumns: CustomFieldColumnsResult<TBaseModel> =
451
+ useCustomFieldColumns<TBaseModel>({
452
+ customFieldsModelType: props.customFieldsModelType,
453
+ });
454
+
455
+ const isColumnCustomizationEnabled: boolean = Boolean(
456
+ !props.disableColumnCustomization &&
457
+ props.userPreferencesKey &&
458
+ showAs !== ShowAs.OrderedStatesList,
459
+ );
460
+
461
+ const allColumns: Columns<TBaseModel> = useMemo(() => {
462
+ if (customFieldColumns.columns.length === 0) {
463
+ return props.columns || [];
464
+ }
465
+
466
+ return [...(props.columns || []), ...customFieldColumns.columns];
467
+ }, [props.columns, customFieldColumns.columns]);
468
+
469
+ type ReadStoredColumnPreferenceFunction = () => ColumnPreference | null;
470
+
471
+ const readStoredColumnPreference: ReadStoredColumnPreferenceFunction =
472
+ (): ColumnPreference | null => {
473
+ if (!props.userPreferencesKey) {
474
+ return null;
475
+ }
476
+
477
+ return columnPreferenceFromJSON(
478
+ UserPreferences.getUserPreferenceByTypeAsJSON({
479
+ key: props.userPreferencesKey,
480
+ userPreferenceType: UserPreferenceType.BaseModelTableColumns,
481
+ }),
482
+ );
483
+ };
484
+
485
+ const [columnPreference, setColumnPreference] =
486
+ useState<ColumnPreference | null>(readStoredColumnPreference);
487
+
488
+ const [showColumnCustomizationModal, setShowColumnCustomizationModal] =
489
+ useState<boolean>(false);
490
+
491
+ /*
492
+ * Ids are derived over the *whole* declared set, so a column dropping out
493
+ * for lack of permission can never renumber the ones that remain.
494
+ */
495
+ const allColumnIds: Array<string> = useMemo(() => {
496
+ return getColumnIds<TBaseModel>(allColumns);
497
+ }, [allColumns]);
498
+
499
+ const effectiveColumnPreference: ColumnPreference | null = useMemo(() => {
500
+ if (!isColumnCustomizationEnabled) {
501
+ return null;
502
+ }
503
+
504
+ /*
505
+ * A stored layout outlives the release that wrote it, so anything naming
506
+ * a column this table no longer has is dropped before it is applied.
507
+ */
508
+ return sanitizeColumnPreference({
509
+ preference: columnPreference,
510
+ knownColumnIds: allColumnIds,
511
+ });
512
+ }, [columnPreference, allColumnIds, isColumnCustomizationEnabled]);
513
+
514
+ type SaveColumnPreferenceFunction = (
515
+ preference: ColumnPreference | null,
516
+ ) => void;
517
+
518
+ const saveColumnPreference: SaveColumnPreferenceFunction = (
519
+ preference: ColumnPreference | null,
520
+ ): void => {
521
+ setColumnPreference(preference);
522
+
523
+ if (!props.userPreferencesKey) {
524
+ return;
525
+ }
526
+
527
+ const json: JSONObject | null = columnPreferenceToJSON(preference);
528
+
529
+ if (!json) {
530
+ UserPreferences.removeUserPreferenceByType({
531
+ key: props.userPreferencesKey,
532
+ userPreferenceType: UserPreferenceType.BaseModelTableColumns,
533
+ });
534
+ return;
535
+ }
536
+
537
+ UserPreferences.saveUserPreferenceByTypeAsJSON({
538
+ key: props.userPreferencesKey,
539
+ userPreferenceType: UserPreferenceType.BaseModelTableColumns,
540
+ value: json,
541
+ });
542
+ };
543
+
398
544
  const [classicTableFilters, setClassicTableFilters] = useState<
399
545
  Array<ClassicFilterType<TBaseModel>>
400
546
  >([]);
@@ -519,6 +665,66 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
519
665
  const [error, setError] = useState<string>("");
520
666
  const [tableFilterError, setTableFilterError] = useState<string>("");
521
667
 
668
+ /*
669
+ * Auto-detect label support from the existing filters array. We look for
670
+ * the filter whose `filterEntityType` class name is "Label" and reuse its
671
+ * dropdown wiring (entity type, fetch query, label/value field names) so
672
+ * search inherits whatever scoping the filter popup already had.
673
+ *
674
+ * Computed before the search state below because the `labels` slice of a
675
+ * restored URL is only meaningful when this is non-null.
676
+ */
677
+ type LabelFilterConfig = {
678
+ fieldKey: string;
679
+ entityType: any;
680
+ fetchQuery: any;
681
+ labelField: string;
682
+ };
683
+
684
+ const labelFilterConfig: LabelFilterConfig | null = useMemo(() => {
685
+ const filter: Filter<TBaseModel> | undefined = props.filters.find(
686
+ (f: Filter<TBaseModel>) => {
687
+ return (
688
+ f.filterEntityType &&
689
+ (f.filterEntityType as any).name === "Label" &&
690
+ f.field &&
691
+ f.filterDropdownField
692
+ );
693
+ },
694
+ );
695
+ if (!filter || !filter.field || !filter.filterDropdownField) {
696
+ return null;
697
+ }
698
+ const fieldKey: string | undefined = Object.keys(filter.field)[0];
699
+ if (!fieldKey) {
700
+ return null;
701
+ }
702
+ return {
703
+ fieldKey,
704
+ entityType: filter.filterEntityType,
705
+ fetchQuery: filter.filterQuery || {},
706
+ labelField: filter.filterDropdownField.label,
707
+ };
708
+ }, [props.filters]);
709
+
710
+ /*
711
+ * The label chips are a feature of the in-search Label filter, so a table
712
+ * without one has to ignore the URL's `labels` slice entirely.
713
+ *
714
+ * Tables that have since moved Labels out to a facet chip (Monitors,
715
+ * Network Devices) still receive `labels` from older shared/bookmarked
716
+ * links. Seeding those ids anyway produced a chip the table could neither
717
+ * name (`availableLabels` is only fetched when there *is* a config) nor
718
+ * apply (`buildSearchQueryFragment` skips the constraint for the same
719
+ * reason): a force-expanded search box with a raw-UUID pill sitting above a
720
+ * completely unfiltered list, re-persisted on every write until the user
721
+ * clicked its ×. Dropping it here also means the next view write omits the
722
+ * key, so the URL heals itself.
723
+ */
724
+ const restoredLabelIds: Array<string> = labelFilterConfig
725
+ ? initialUrlState.view.labels || []
726
+ : [];
727
+
522
728
  const [searchText, setSearchText] = useState<string>(
523
729
  initialUrlState.view.search || "",
524
730
  );
@@ -527,9 +733,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
527
733
  );
528
734
  const [isSearchFocused, setIsSearchFocused] = useState<boolean>(false);
529
735
  const [isSearchExpanded, setIsSearchExpanded] = useState<boolean>(
530
- Boolean(
531
- initialUrlState.view.search || (initialUrlState.view.labels || []).length,
532
- ),
736
+ Boolean(initialUrlState.view.search || restoredLabelIds.length),
533
737
  );
534
738
  const searchInputRef: React.RefObject<HTMLInputElement> =
535
739
  React.useRef<HTMLInputElement>(null!);
@@ -551,7 +755,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
551
755
  const [selectedLabels, setSelectedLabels] = useState<
552
756
  Array<SearchLabelOption>
553
757
  >(() => {
554
- return (initialUrlState.view.labels || []).map((id: string) => {
758
+ return restoredLabelIds.map((id: string) => {
555
759
  return { id: id, name: id, color: "#94a3b8" };
556
760
  });
557
761
  });
@@ -647,43 +851,24 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
647
851
  }, [debouncedSearchText, selectedLabelIdsKey]);
648
852
 
649
853
  /*
650
- * Auto-detect label support from the existing filters array. We look for
651
- * the filter whose `filterEntityType` class name is "Label" and reuse its
652
- * dropdown wiring (entity type, fetch query, label/value field names) so
653
- * search inherits whatever scoping the filter popup already had.
854
+ * `props.filters` is not frozen a page can rebuild it after mount, and
855
+ * that is exactly how a Labels filter leaves the popup for the facet bar.
856
+ * Chips left over from a config that no longer exists are unnameable and
857
+ * unappliable, so drop them here too rather than only at mount.
654
858
  */
655
- type LabelFilterConfig = {
656
- fieldKey: string;
657
- entityType: any;
658
- fetchQuery: any;
659
- labelField: string;
660
- };
661
-
662
- const labelFilterConfig: LabelFilterConfig | null = useMemo(() => {
663
- const filter: Filter<TBaseModel> | undefined = props.filters.find(
664
- (f: Filter<TBaseModel>) => {
665
- return (
666
- f.filterEntityType &&
667
- (f.filterEntityType as any).name === "Label" &&
668
- f.field &&
669
- f.filterDropdownField
670
- );
671
- },
672
- );
673
- if (!filter || !filter.field || !filter.filterDropdownField) {
674
- return null;
675
- }
676
- const fieldKey: string | undefined = Object.keys(filter.field)[0];
677
- if (!fieldKey) {
678
- return null;
859
+ useEffect(() => {
860
+ if (labelFilterConfig) {
861
+ return;
679
862
  }
680
- return {
681
- fieldKey,
682
- entityType: filter.filterEntityType,
683
- fetchQuery: filter.filterQuery || {},
684
- labelField: filter.filterDropdownField.label,
685
- };
686
- }, [props.filters]);
863
+
864
+ setSelectedLabels((existing: Array<SearchLabelOption>) => {
865
+ /*
866
+ * Same array when there is nothing to clear, so this costs an idle table
867
+ * no re-render.
868
+ */
869
+ return existing.length === 0 ? existing : [];
870
+ });
871
+ }, [labelFilterConfig]);
687
872
 
688
873
  // Fetch labels on first search expansion if this resource supports them.
689
874
  useEffect(() => {
@@ -903,8 +1088,11 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
903
1088
 
904
1089
  const getRelationSelect: GetRelationSelectFunction =
905
1090
  (): Select<TBaseModel> => {
1091
+ /*
1092
+ * Deliberately the *unfiltered* column set. See getSelect() below.
1093
+ */
906
1094
  return getRelationSelectFromColumns<TBaseModel>({
907
- columns: props.columns || [],
1095
+ columns: allColumns,
908
1096
  model: model,
909
1097
  });
910
1098
  };
@@ -950,7 +1138,30 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
950
1138
  const accessControl: Dictionary<ColumnAccessControl> =
951
1139
  model.getColumnAccessControlForAllColumns();
952
1140
 
953
- for (const column of props.columns || []) {
1141
+ /*
1142
+ * The viewer's layout is applied to the *input* of this loop, never to
1143
+ * the finished array: the Actions column is appended below and has to
1144
+ * stay last, and it is generated rather than declared, so it is not the
1145
+ * viewer's to move or switch off.
1146
+ */
1147
+ const columnsToRender: Columns<TBaseModel> =
1148
+ applyColumnPreference<TBaseModel>({
1149
+ columns: allColumns,
1150
+ preference: effectiveColumnPreference,
1151
+ });
1152
+
1153
+ const columnIdByColumn: Map<
1154
+ ModelTableColumn<TBaseModel>,
1155
+ string
1156
+ > = new Map();
1157
+
1158
+ allColumns.forEach(
1159
+ (column: ModelTableColumn<TBaseModel>, index: number) => {
1160
+ columnIdByColumn.set(column, allColumnIds[index] as string);
1161
+ },
1162
+ );
1163
+
1164
+ for (const column of columnsToRender) {
954
1165
  const hasPermission: boolean =
955
1166
  hasPermissionToReadColumn(column) || User.isMasterAdmin();
956
1167
  const key: keyof TBaseModel | null = getColumnKey(column);
@@ -983,6 +1194,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
983
1194
 
984
1195
  columns.push({
985
1196
  ...column,
1197
+ id: columnIdByColumn.get(column),
986
1198
  disableSort: column.disableSort || shouldDisableSort(key),
987
1199
  key: columnKey,
988
1200
  /*
@@ -1692,14 +1904,39 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1692
1904
  type GetSelectFunction = () => Select<TBaseModel>;
1693
1905
 
1694
1906
  const getSelect: GetSelectFunction = (): Select<TBaseModel> => {
1907
+ /*
1908
+ * Every declared column, including the ones the viewer has switched off.
1909
+ *
1910
+ * Hiding must not narrow the request. A column's `getElement` is arbitrary
1911
+ * caller code that can read any field on the row — a "Status" cell that
1912
+ * also reads `currentMonitorStatus`, say — so dropping a hidden column's
1913
+ * fields from the select would silently blank a *different*, visible cell,
1914
+ * and there is no way to detect that statically. The sort field is
1915
+ * likewise independent of what is on screen. The cost is a few unused
1916
+ * fields on the wire.
1917
+ */
1695
1918
  const selectFields: Select<TBaseModel> = getSelectFromColumns<TBaseModel>({
1696
- columns: props.columns || [],
1919
+ columns: allColumns,
1697
1920
  model: model,
1698
1921
  hasPermissionToReadField: (field: string): boolean => {
1699
1922
  return hasPermissionToReadField(field as keyof TBaseModel);
1700
1923
  },
1701
1924
  });
1702
1925
 
1926
+ /*
1927
+ * Custom field columns arrive asynchronously, but the table does not
1928
+ * refetch when its column set changes — so the JSON column that backs
1929
+ * every one of them is selected up front, off the synchronous prop, and
1930
+ * the values are there the moment the definitions land.
1931
+ */
1932
+ if (
1933
+ props.customFieldsModelType &&
1934
+ model.hasColumn(CustomFieldsColumnKey) &&
1935
+ hasPermissionToReadField(CustomFieldsColumnKey as keyof TBaseModel)
1936
+ ) {
1937
+ (selectFields as Dictionary<boolean>)[CustomFieldsColumnKey] = true;
1938
+ }
1939
+
1703
1940
  const selectMoreFields: Array<keyof TBaseModel> = props.selectMoreFields
1704
1941
  ? (Object.keys(props.selectMoreFields) as Array<keyof TBaseModel>)
1705
1942
  : [];
@@ -1751,6 +1988,9 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1751
1988
  currentItemsOnPage={itemsOnPage}
1752
1989
  currentSortOrder={sortOrder}
1753
1990
  currentFacetState={props.currentFacetState}
1991
+ currentColumns={
1992
+ columnPreferenceToJSON(effectiveColumnPreference) || undefined
1993
+ }
1754
1994
  onViewChange={async (tableView: TableView | null) => {
1755
1995
  setTableView(tableView);
1756
1996
 
@@ -1785,6 +2025,20 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1785
2025
  (tableView.facets as JSONObject | undefined) || null,
1786
2026
  );
1787
2027
  }
2028
+
2029
+ /*
2030
+ * A saved view carries the columns it was saved with. Views
2031
+ * created before this existed have none, which restores the
2032
+ * table's default layout rather than leaving the previous
2033
+ * view's columns in place.
2034
+ */
2035
+ if (isColumnCustomizationEnabled) {
2036
+ saveColumnPreference(
2037
+ columnPreferenceFromJSON(
2038
+ (tableView.columns as JSONObject | undefined) || null,
2039
+ ),
2040
+ );
2041
+ }
1788
2042
  } else {
1789
2043
  setQuery({});
1790
2044
  /*
@@ -1802,6 +2056,13 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1802
2056
  if (props.onFacetStateRestored) {
1803
2057
  props.onFacetStateRestored(null);
1804
2058
  }
2059
+
2060
+ /*
2061
+ * Columns are deliberately left alone here. Clearing a saved
2062
+ * view means "stop filtering like that", not "throw away the
2063
+ * column layout I built" - which the viewer may well have set
2064
+ * up long before this view existed.
2065
+ */
1805
2066
  }
1806
2067
  }}
1807
2068
  />
@@ -1935,6 +2196,23 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1935
2196
  });
1936
2197
  }
1937
2198
 
2199
+ /*
2200
+ * Only worth offering once there is a choice to make. A single-column
2201
+ * table has nothing to hide and nothing to reorder.
2202
+ */
2203
+ if (isColumnCustomizationEnabled && allColumns.length > 1) {
2204
+ headerbuttons.push({
2205
+ title: "",
2206
+ buttonStyle: ButtonStyleType.ICON,
2207
+ buttonSize: ButtonSize.Small,
2208
+ className: "",
2209
+ onClick: () => {
2210
+ setShowColumnCustomizationModal(true);
2211
+ },
2212
+ icon: IconProp.TableCells,
2213
+ });
2214
+ }
2215
+
1938
2216
  setCardButtons(headerbuttons);
1939
2217
  };
1940
2218
 
@@ -2108,6 +2386,14 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
2108
2386
  serializeToTableColumns();
2109
2387
  }, [props.columns]);
2110
2388
 
2389
+ /*
2390
+ * The viewer changed their layout, or the project's custom field
2391
+ * definitions finally arrived.
2392
+ */
2393
+ useEffect(() => {
2394
+ serializeToTableColumns();
2395
+ }, [effectiveColumnPreference, customFieldColumns.columns]);
2396
+
2111
2397
  const setActionSchema: VoidFunction = () => {
2112
2398
  const permissions: Array<Permission> = PermissionUtil.getAllPermissions();
2113
2399
 
@@ -3016,36 +3302,46 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
3016
3302
  />
3017
3303
  {/* Pills + input wrap row */}
3018
3304
  <div className="flex min-w-0 flex-1 flex-wrap items-center gap-1.5">
3019
- {selectedLabels.map((label: SearchLabelOption) => {
3020
- return (
3021
- <span
3022
- key={label.id}
3023
- className="inline-flex items-center gap-1 rounded-full bg-gray-50 py-0.5 pl-2 pr-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-200 transition-all hover:bg-gray-100"
3024
- title={`Label: ${label.name}`}
3025
- >
3305
+ {/*
3306
+ * Gated on label support as well as on the chips themselves: a
3307
+ * pill this table cannot name, apply or refill is never worth
3308
+ * drawing, whatever left it in state.
3309
+ */}
3310
+ {hasLabelSupport &&
3311
+ selectedLabels.map((label: SearchLabelOption) => {
3312
+ return (
3026
3313
  <span
3027
- className="h-2 w-2 flex-none rounded-full"
3028
- style={{ backgroundColor: label.color }}
3029
- aria-hidden="true"
3030
- />
3031
- <span className="max-w-[8rem] truncate">{label.name}</span>
3032
- <button
3033
- type="button"
3034
- onMouseDown={(e: React.MouseEvent<HTMLButtonElement>) => {
3035
- e.preventDefault();
3036
- }}
3037
- onClick={() => {
3038
- removeLabel(label.id);
3039
- }}
3040
- title="Remove label"
3041
- aria-label={`Remove ${label.name}`}
3042
- className="ml-0.5 flex-none rounded-full p-0.5 text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700"
3314
+ key={label.id}
3315
+ className="inline-flex items-center gap-1 rounded-full bg-gray-50 py-0.5 pl-2 pr-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-200 transition-all hover:bg-gray-100"
3316
+ title={`Label: ${label.name}`}
3043
3317
  >
3044
- <Icon icon={IconProp.Close} className="h-3 w-3" />
3045
- </button>
3046
- </span>
3047
- );
3048
- })}
3318
+ <span
3319
+ className="h-2 w-2 flex-none rounded-full"
3320
+ style={{ backgroundColor: label.color }}
3321
+ aria-hidden="true"
3322
+ />
3323
+ <span className="max-w-[8rem] truncate">
3324
+ {label.name}
3325
+ </span>
3326
+ <button
3327
+ type="button"
3328
+ onMouseDown={(
3329
+ e: React.MouseEvent<HTMLButtonElement>,
3330
+ ) => {
3331
+ e.preventDefault();
3332
+ }}
3333
+ onClick={() => {
3334
+ removeLabel(label.id);
3335
+ }}
3336
+ title="Remove label"
3337
+ aria-label={`Remove ${label.name}`}
3338
+ className="ml-0.5 flex-none rounded-full p-0.5 text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700"
3339
+ >
3340
+ <Icon icon={IconProp.Close} className="h-3 w-3" />
3341
+ </button>
3342
+ </span>
3343
+ );
3344
+ })}
3049
3345
  <input
3050
3346
  ref={searchInputRef}
3051
3347
  type="text"
@@ -3375,6 +3671,8 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
3375
3671
  return "Watch Demo";
3376
3672
  case IconProp.Search:
3377
3673
  return "Search";
3674
+ case IconProp.TableCells:
3675
+ return "Columns";
3378
3676
  default:
3379
3677
  return "Action";
3380
3678
  }
@@ -3581,6 +3879,133 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
3581
3879
  return originalTitle;
3582
3880
  };
3583
3881
 
3882
+ type IsPickableColumnFunction = (
3883
+ column: ModelTableColumn<TBaseModel>,
3884
+ ) => boolean;
3885
+
3886
+ /*
3887
+ * A column the viewer cannot read is left out of the picker entirely.
3888
+ * Listing it would advertise a field they have no access to, and switching
3889
+ * it on would put that field in the select — which the API rejects for the
3890
+ * whole request, blanking the table.
3891
+ */
3892
+ const isPickableColumn: IsPickableColumnFunction = (
3893
+ column: ModelTableColumn<TBaseModel>,
3894
+ ): boolean => {
3895
+ if (column.isNotCustomizable) {
3896
+ return false;
3897
+ }
3898
+
3899
+ return hasPermissionToReadColumn(column) || User.isMasterAdmin();
3900
+ };
3901
+
3902
+ type GetPickerEntriesFunction = (
3903
+ preference: ColumnPreference | null,
3904
+ ) => Array<CustomizableColumn<TBaseModel>>;
3905
+
3906
+ const getPickerEntries: GetPickerEntriesFunction = (
3907
+ preference: ColumnPreference | null,
3908
+ ): Array<CustomizableColumn<TBaseModel>> => {
3909
+ return getCustomizableColumns<TBaseModel>({
3910
+ columns: allColumns,
3911
+ preference: preference,
3912
+ }).filter((entry: CustomizableColumn<TBaseModel>) => {
3913
+ return isPickableColumn(entry.column);
3914
+ });
3915
+ };
3916
+
3917
+ type GetColumnSortKeyFunction = (
3918
+ column: ModelTableColumn<TBaseModel>,
3919
+ ) => string | null;
3920
+
3921
+ // Mirrors how serializeToTableColumns derives the key the header sorts on.
3922
+ const getColumnSortKey: GetColumnSortKeyFunction = (
3923
+ column: ModelTableColumn<TBaseModel>,
3924
+ ): string | null => {
3925
+ const key: keyof TBaseModel | null = getColumnKey(column);
3926
+
3927
+ if (!key) {
3928
+ return null;
3929
+ }
3930
+
3931
+ return column.selectedProperty
3932
+ ? `${String(key)}.${column.selectedProperty}`
3933
+ : String(key);
3934
+ };
3935
+
3936
+ type OnColumnCustomizationSaveFunction = (
3937
+ entries: Array<CustomizableColumn<TBaseModel>>,
3938
+ ) => void;
3939
+
3940
+ const onColumnCustomizationSave: OnColumnCustomizationSaveFunction = (
3941
+ entries: Array<CustomizableColumn<TBaseModel>>,
3942
+ ): void => {
3943
+ const preference: ColumnPreference =
3944
+ buildColumnPreference<TBaseModel>(entries);
3945
+
3946
+ /*
3947
+ * If the layout the viewer just saved matches what the table ships with,
3948
+ * store nothing. Otherwise every table anyone ever opened the picker on
3949
+ * would be pinned to the column set of the release they opened it in, and
3950
+ * columns added later would arrive already stale.
3951
+ */
3952
+ const defaultPreference: ColumnPreference =
3953
+ buildColumnPreference<TBaseModel>(getPickerEntries(null));
3954
+
3955
+ const isBackToDefault: boolean =
3956
+ JSON.stringify(preference) === JSON.stringify(defaultPreference);
3957
+
3958
+ /*
3959
+ * Sorting by a column that is no longer on screen leaves the viewer with
3960
+ * an ordering they can neither see nor undo — there is no header left to
3961
+ * click. Fall back to the table's own default sort.
3962
+ */
3963
+ if (sortBy) {
3964
+ const hiddenIds: Set<string> = new Set(preference.hidden);
3965
+
3966
+ const isSortedColumnHidden: boolean = entries.some(
3967
+ (entry: CustomizableColumn<TBaseModel>) => {
3968
+ return (
3969
+ hiddenIds.has(entry.id) &&
3970
+ getColumnSortKey(entry.column) === String(sortBy)
3971
+ );
3972
+ },
3973
+ );
3974
+
3975
+ if (isSortedColumnHidden) {
3976
+ setSortBy((props.sortBy as keyof TBaseModel | undefined) || null);
3977
+ setSortOrder(props.sortOrder || SortOrder.Ascending);
3978
+ setCurrentPageNumber(1);
3979
+ }
3980
+ }
3981
+
3982
+ saveColumnPreference(isBackToDefault ? null : preference);
3983
+ setShowColumnCustomizationModal(false);
3984
+ };
3985
+
3986
+ const getColumnCustomizationModal: () => ReactElement | null =
3987
+ (): ReactElement | null => {
3988
+ if (!showColumnCustomizationModal || !isColumnCustomizationEnabled) {
3989
+ return null;
3990
+ }
3991
+
3992
+ return (
3993
+ <ColumnCustomizationModal<TBaseModel>
3994
+ columns={getPickerEntries(effectiveColumnPreference)}
3995
+ isDefaultLayout={isEmptyColumnPreference(effectiveColumnPreference)}
3996
+ title={tx("Customize Columns")}
3997
+ onSave={onColumnCustomizationSave}
3998
+ onReset={() => {
3999
+ saveColumnPreference(null);
4000
+ setShowColumnCustomizationModal(false);
4001
+ }}
4002
+ onClose={() => {
4003
+ setShowColumnCustomizationModal(false);
4004
+ }}
4005
+ />
4006
+ );
4007
+ };
4008
+
3584
4009
  const getCardComponent: GetReactElementFunction = (): ReactElement => {
3585
4010
  const headerButtons: Array<CardButtonSchema | ReactElement> =
3586
4011
  getHeaderButtonsWithSearch();
@@ -3601,7 +4026,12 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
3601
4026
  getCardTitle(props.cardProps.title || ""),
3602
4027
  )}
3603
4028
  >
3604
- {tableColumns.length === 0 && props.columns.length > 0 ? (
4029
+ {/*
4030
+ * A viewer's layout can never empty this out — applyColumnPreference
4031
+ * refuses to return nothing — so zero columns still means exactly
4032
+ * what it always did: every column was denied by permission.
4033
+ */}
4034
+ {tableColumns.length === 0 && allColumns.length > 0 ? (
3605
4035
  <ErrorMessage
3606
4036
  message={`You are not authorized to view this table. You need any one of these permissions: ${PermissionHelper.getPermissionTitles(
3607
4037
  model.getReadPermissions(),
@@ -3799,6 +4229,8 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
3799
4229
  </div>
3800
4230
  </Modal>
3801
4231
  )}
4232
+
4233
+ {getColumnCustomizationModal()}
3802
4234
  </>
3803
4235
  );
3804
4236
  };