@cccsaurora/howler-ui 2.18.0 → 2.19.0-cases.862

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 (342) hide show
  1. package/api/index.d.ts +4 -0
  2. package/api/index.js +10 -2
  3. package/api/search/case.d.ts +4 -0
  4. package/api/search/case.js +8 -0
  5. package/api/search/facet/hit.d.ts +1 -3
  6. package/api/search/facet/index.d.ts +3 -1
  7. package/api/search/index.d.ts +2 -1
  8. package/api/search/index.js +2 -1
  9. package/api/socket/index.d.ts +3 -0
  10. package/api/socket/index.js +6 -0
  11. package/api/socket/viewers.d.ts +2 -0
  12. package/api/socket/viewers.js +8 -0
  13. package/api/socket/viewers.test.js +44 -0
  14. package/api/v2/case/index.d.ts +9 -0
  15. package/api/v2/case/index.js +21 -0
  16. package/api/v2/case/items.d.ts +6 -0
  17. package/api/v2/case/items.js +18 -0
  18. package/api/v2/case/rules.d.ts +6 -0
  19. package/api/v2/case/rules.js +18 -0
  20. package/api/v2/index.d.ts +4 -0
  21. package/api/v2/index.js +6 -0
  22. package/api/v2/search/facet.d.ts +3 -0
  23. package/api/v2/search/facet.js +12 -0
  24. package/api/v2/search/index.d.ts +5 -0
  25. package/api/v2/search/index.js +24 -0
  26. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  27. package/components/app/App.js +52 -12
  28. package/components/app/hooks/useMatchers.d.ts +1 -1
  29. package/components/app/hooks/useMatchers.js +23 -11
  30. package/components/app/hooks/useMatchers.test.js +22 -22
  31. package/components/app/hooks/useTitle.js +5 -5
  32. package/components/app/providers/FavouritesProvider.js +2 -2
  33. package/components/app/providers/ModalProvider.d.ts +1 -0
  34. package/components/app/providers/ParameterProvider.d.ts +9 -2
  35. package/components/app/providers/ParameterProvider.js +165 -240
  36. package/components/app/providers/ParameterProvider.test.js +346 -94
  37. package/components/app/providers/RecordProvider.d.ts +23 -0
  38. package/components/app/providers/{HitProvider.js → RecordProvider.js} +42 -42
  39. package/components/app/providers/{HitSearchProvider.d.ts → RecordSearchProvider.d.ts} +6 -6
  40. package/components/app/providers/{HitSearchProvider.js → RecordSearchProvider.js} +12 -17
  41. package/components/app/providers/{HitSearchProvider.test.js → RecordSearchProvider.test.js} +52 -71
  42. package/components/app/providers/SocketProvider.d.ts +11 -2
  43. package/components/app/providers/SocketProvider.js +18 -5
  44. package/components/app/providers/UserListProvider.js +28 -8
  45. package/components/elements/ContextMenu.d.ts +56 -0
  46. package/components/elements/ContextMenu.js +109 -0
  47. package/components/elements/ContextMenu.test.d.ts +1 -0
  48. package/components/elements/ContextMenu.test.js +215 -0
  49. package/components/{routes/overviews/OverviewEditor.js → elements/MarkdownEditor.js} +3 -3
  50. package/components/elements/ObjectDetails.d.ts +6 -0
  51. package/components/elements/{hit/HitDetails.js → ObjectDetails.js} +17 -17
  52. package/components/elements/PluginTypography.d.ts +2 -1
  53. package/components/elements/PluginTypography.js +3 -2
  54. package/components/elements/UserList.d.ts +5 -2
  55. package/components/elements/UserList.js +18 -8
  56. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  57. package/components/elements/case/CaseCard.d.ts +12 -0
  58. package/components/elements/case/CaseCard.js +42 -0
  59. package/components/elements/case/CasePreview.d.ts +6 -0
  60. package/components/elements/case/CasePreview.js +17 -0
  61. package/components/elements/case/StatusIcon.d.ts +5 -0
  62. package/components/elements/case/StatusIcon.js +13 -0
  63. package/components/elements/display/ChipPopper.d.ts +1 -1
  64. package/components/elements/display/ChipPopper.js +5 -5
  65. package/components/elements/display/HowlerCard.js +1 -1
  66. package/components/elements/display/Modal.js +2 -0
  67. package/components/elements/hit/HitActions.js +4 -4
  68. package/components/elements/hit/HitBanner.d.ts +1 -0
  69. package/components/elements/hit/HitBanner.js +34 -51
  70. package/components/elements/hit/HitCard.d.ts +2 -0
  71. package/components/elements/hit/HitCard.js +7 -7
  72. package/components/elements/hit/HitLabels.js +2 -2
  73. package/components/elements/hit/HitOutline.d.ts +1 -0
  74. package/components/elements/hit/HitOutline.js +3 -3
  75. package/components/elements/hit/{HitQuickSearch.d.ts → HitPreview.d.ts} +3 -3
  76. package/components/elements/hit/{HitQuickSearch.js → HitPreview.js} +10 -4
  77. package/components/elements/hit/HitSummary.d.ts +2 -1
  78. package/components/elements/hit/HitSummary.js +6 -5
  79. package/components/elements/hit/aggregate/HitGraph.js +8 -8
  80. package/components/elements/hit/elements/AnalyticLink.d.ts +9 -0
  81. package/components/elements/hit/elements/AnalyticLink.js +22 -0
  82. package/components/elements/hit/elements/Assigned.js +6 -3
  83. package/components/elements/hit/elements/Assigned.test.d.ts +1 -0
  84. package/components/elements/hit/elements/Assigned.test.js +65 -0
  85. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  86. package/components/elements/hit/related/RelatedRecords.js +63 -0
  87. package/components/elements/observable/ObservableCard.d.ts +6 -0
  88. package/components/elements/observable/ObservableCard.js +22 -0
  89. package/components/elements/observable/ObservablePreview.d.ts +6 -0
  90. package/components/elements/observable/ObservablePreview.js +12 -0
  91. package/components/elements/{hit/HitComments.d.ts → record/RecordComments.d.ts} +5 -4
  92. package/components/elements/{hit/HitComments.js → record/RecordComments.js} +29 -28
  93. package/components/{routes/hits/search/HitContextMenu.d.ts → elements/record/RecordContextMenu.d.ts} +3 -3
  94. package/components/elements/record/RecordContextMenu.js +268 -0
  95. package/components/elements/record/RecordContextMenu.test.d.ts +1 -0
  96. package/components/{routes/hits/search/HitContextMenu.test.js → elements/record/RecordContextMenu.test.js} +190 -39
  97. package/components/elements/record/RecordRelated.d.ts +7 -0
  98. package/components/elements/record/RecordRelated.js +34 -0
  99. package/components/elements/{hit/HitWorklog.d.ts → record/RecordWorklog.d.ts} +4 -3
  100. package/components/elements/{hit/HitWorklog.js → record/RecordWorklog.js} +15 -13
  101. package/components/elements/view/ViewTitle.d.ts +1 -0
  102. package/components/elements/view/ViewTitle.js +9 -2
  103. package/components/hooks/useHitActions.d.ts +1 -1
  104. package/components/hooks/useHitActions.js +4 -4
  105. package/components/hooks/useMyPreferences.js +10 -1
  106. package/components/hooks/useMySearch.js +2 -2
  107. package/components/hooks/useMySitemap.js +4 -1
  108. package/components/hooks/useMyTheme.js +9 -2
  109. package/components/hooks/{useHitSelection.d.ts → useRecordSelection.d.ts} +2 -2
  110. package/components/hooks/{useHitSelection.js → useRecordSelection.js} +12 -33
  111. package/components/hooks/useRelatedRecords.d.ts +13 -0
  112. package/components/hooks/useRelatedRecords.js +32 -0
  113. package/components/routes/403.d.ts +3 -0
  114. package/components/routes/403.js +10 -0
  115. package/components/routes/action/edit/ActionEditor.js +3 -3
  116. package/components/routes/action/useMyActionFunctions.js +4 -1
  117. package/components/routes/action/view/ActionDetails.js +6 -1
  118. package/components/routes/action/view/ActionSearch.js +5 -4
  119. package/components/routes/action/view/markdown/integrations.en.md.js +1 -1
  120. package/components/routes/action/view/markdown/integrations.fr.md.js +1 -1
  121. package/components/routes/advanced/QueryBuilder.js +1 -1
  122. package/components/routes/advanced/QueryEditor.js +3 -3
  123. package/components/routes/advanced/historyCompletionProvider.js +3 -3
  124. package/components/routes/analytics/AnalyticDetails.js +2 -2
  125. package/components/routes/analytics/AnalyticSearch.js +1 -1
  126. package/components/routes/cases/CaseViewer.d.ts +2 -0
  127. package/components/routes/cases/CaseViewer.js +44 -0
  128. package/components/routes/cases/CaseViewer.test.d.ts +1 -0
  129. package/components/routes/cases/CaseViewer.test.js +133 -0
  130. package/components/routes/cases/Cases.d.ts +2 -0
  131. package/components/routes/cases/Cases.js +148 -0
  132. package/components/routes/cases/constants.d.ts +6 -0
  133. package/components/routes/cases/constants.js +6 -0
  134. package/components/routes/cases/detail/AlertPanel.d.ts +6 -0
  135. package/components/routes/cases/detail/AlertPanel.js +33 -0
  136. package/components/routes/cases/detail/CaseAssets.d.ts +11 -0
  137. package/components/routes/cases/detail/CaseAssets.js +104 -0
  138. package/components/routes/cases/detail/CaseAssets.test.d.ts +1 -0
  139. package/components/routes/cases/detail/CaseAssets.test.js +167 -0
  140. package/components/routes/cases/detail/CaseDashboard.d.ts +7 -0
  141. package/components/routes/cases/detail/CaseDashboard.js +66 -0
  142. package/components/routes/cases/detail/CaseDetails.d.ts +6 -0
  143. package/components/routes/cases/detail/CaseDetails.js +70 -0
  144. package/components/routes/cases/detail/CaseOverview.d.ts +7 -0
  145. package/components/routes/cases/detail/CaseOverview.js +43 -0
  146. package/components/routes/cases/detail/CaseRules.d.ts +7 -0
  147. package/components/routes/cases/detail/CaseRules.js +57 -0
  148. package/components/routes/cases/detail/CaseRules.test.d.ts +1 -0
  149. package/components/routes/cases/detail/CaseRules.test.js +221 -0
  150. package/components/routes/cases/detail/CaseSidebar.d.ts +8 -0
  151. package/components/routes/cases/detail/CaseSidebar.js +107 -0
  152. package/components/routes/cases/detail/CaseSidebar.test.d.ts +1 -0
  153. package/components/routes/cases/detail/CaseSidebar.test.js +266 -0
  154. package/components/routes/cases/detail/CaseTask.d.ts +11 -0
  155. package/components/routes/cases/detail/CaseTask.js +66 -0
  156. package/components/routes/cases/detail/CaseTimeline.d.ts +12 -0
  157. package/components/routes/cases/detail/CaseTimeline.js +106 -0
  158. package/components/routes/cases/detail/CaseTimeline.test.d.ts +1 -0
  159. package/components/routes/cases/detail/CaseTimeline.test.js +320 -0
  160. package/components/routes/cases/detail/CreateRuleDialog.d.ts +9 -0
  161. package/components/routes/cases/detail/CreateRuleDialog.js +163 -0
  162. package/components/routes/cases/detail/CreateRuleDialog.test.d.ts +1 -0
  163. package/components/routes/cases/detail/CreateRuleDialog.test.js +259 -0
  164. package/components/routes/cases/detail/ItemPage.d.ts +6 -0
  165. package/components/routes/cases/detail/ItemPage.js +95 -0
  166. package/components/routes/cases/detail/RelatedCasePanel.d.ts +6 -0
  167. package/components/routes/cases/detail/RelatedCasePanel.js +34 -0
  168. package/components/routes/cases/detail/TaskPanel.d.ts +7 -0
  169. package/components/routes/cases/detail/TaskPanel.js +52 -0
  170. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +11 -0
  171. package/components/routes/cases/detail/aggregates/CaseAggregate.js +24 -0
  172. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +6 -0
  173. package/components/routes/cases/detail/aggregates/SourceAggregate.js +26 -0
  174. package/components/routes/cases/detail/assets/Asset.d.ts +14 -0
  175. package/components/routes/cases/detail/assets/Asset.js +12 -0
  176. package/components/routes/cases/detail/assets/Asset.test.d.ts +1 -0
  177. package/components/routes/cases/detail/assets/Asset.test.js +72 -0
  178. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +20 -0
  179. package/components/routes/cases/detail/sidebar/CaseFolder.js +83 -0
  180. package/components/routes/cases/detail/sidebar/CaseFolder.test.d.ts +1 -0
  181. package/components/routes/cases/detail/sidebar/CaseFolder.test.js +295 -0
  182. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +34 -0
  183. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +103 -0
  184. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +1 -0
  185. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +363 -0
  186. package/components/routes/cases/detail/sidebar/FolderEntry.d.ts +25 -0
  187. package/components/routes/cases/detail/sidebar/FolderEntry.js +88 -0
  188. package/components/routes/cases/detail/sidebar/FolderEntry.test.d.ts +1 -0
  189. package/components/routes/cases/detail/sidebar/FolderEntry.test.js +206 -0
  190. package/components/routes/cases/detail/sidebar/RootDropZone.d.ts +5 -0
  191. package/components/routes/cases/detail/sidebar/RootDropZone.js +33 -0
  192. package/components/routes/cases/detail/sidebar/types.d.ts +9 -0
  193. package/components/routes/cases/detail/sidebar/utils.d.ts +3 -0
  194. package/components/routes/cases/detail/sidebar/utils.js +29 -0
  195. package/components/routes/cases/detail/sidebar/utils.test.d.ts +1 -0
  196. package/components/routes/cases/detail/sidebar/utils.test.js +82 -0
  197. package/components/routes/cases/hooks/useCase.d.ts +13 -0
  198. package/components/routes/cases/hooks/useCase.js +69 -0
  199. package/components/routes/cases/hooks/useCase.test.d.ts +1 -0
  200. package/components/routes/cases/hooks/useCase.test.js +141 -0
  201. package/components/routes/cases/modals/AddToCaseModal.d.ts +7 -0
  202. package/components/routes/cases/modals/AddToCaseModal.js +59 -0
  203. package/components/routes/cases/modals/AddToCaseModal.test.d.ts +1 -0
  204. package/components/routes/cases/modals/AddToCaseModal.test.js +313 -0
  205. package/components/routes/cases/modals/CaseRecordRow.d.ts +9 -0
  206. package/components/routes/cases/modals/CaseRecordRow.js +15 -0
  207. package/components/routes/cases/modals/CreateCaseModal.d.ts +7 -0
  208. package/components/routes/cases/modals/CreateCaseModal.js +55 -0
  209. package/components/routes/cases/modals/CreateCaseModal.test.d.ts +1 -0
  210. package/components/routes/cases/modals/CreateCaseModal.test.js +358 -0
  211. package/components/routes/cases/modals/RenameItemModal.d.ts +9 -0
  212. package/components/routes/cases/modals/RenameItemModal.js +48 -0
  213. package/components/routes/cases/modals/ResolveModal.d.ts +7 -0
  214. package/components/routes/cases/modals/ResolveModal.js +115 -0
  215. package/components/routes/cases/modals/ResolveModal.test.d.ts +1 -0
  216. package/components/routes/cases/modals/ResolveModal.test.js +394 -0
  217. package/components/routes/cases/modals/hooks.d.ts +7 -0
  218. package/components/routes/cases/modals/hooks.js +44 -0
  219. package/components/routes/cases/modals/types.d.ts +5 -0
  220. package/components/routes/cases/search/CaseAssigneeFilter.d.ts +6 -0
  221. package/components/routes/cases/search/CaseAssigneeFilter.js +33 -0
  222. package/components/routes/cases/search/CaseAssigneeFilter.test.d.ts +1 -0
  223. package/components/routes/cases/search/CaseAssigneeFilter.test.js +127 -0
  224. package/components/routes/cases/search/CaseDateFilter.d.ts +13 -0
  225. package/components/routes/cases/search/CaseDateFilter.js +26 -0
  226. package/components/routes/cases/search/CaseDateFilter.test.d.ts +1 -0
  227. package/components/routes/cases/search/CaseDateFilter.test.js +115 -0
  228. package/components/routes/cases/search/CaseStatusFilter.d.ts +6 -0
  229. package/components/routes/cases/search/CaseStatusFilter.js +13 -0
  230. package/components/routes/cases/search/CaseStatusFilter.test.d.ts +1 -0
  231. package/components/routes/cases/search/CaseStatusFilter.test.js +86 -0
  232. package/components/routes/dossiers/DossierEditor.js +2 -2
  233. package/components/routes/dossiers/DossierEditor.test.js +1 -1
  234. package/components/routes/help/ActionIntroductionDocumentation.js +1 -1
  235. package/components/routes/help/ApiDocumentation.js +1 -1
  236. package/components/routes/help/HitBannerDocumentation.js +1 -0
  237. package/components/routes/help/HitDocumentation.js +1 -3
  238. package/components/routes/help/markdown/en/retention.md.js +1 -1
  239. package/components/routes/help/markdown/fr/retention.md.js +1 -1
  240. package/components/routes/hits/search/InformationPane.d.ts +1 -0
  241. package/components/routes/hits/search/InformationPane.js +50 -63
  242. package/components/routes/hits/search/LayoutSettings.js +3 -3
  243. package/components/routes/hits/search/QuerySettings.js +2 -1
  244. package/components/routes/hits/search/QuerySettings.test.js +14 -9
  245. package/components/routes/hits/search/{HitBrowser.js → RecordBrowser.js} +9 -9
  246. package/components/routes/hits/search/{HitQuery.d.ts → RecordQuery.d.ts} +2 -2
  247. package/components/routes/hits/search/{HitQuery.js → RecordQuery.js} +6 -6
  248. package/components/routes/hits/search/SearchPane.js +26 -49
  249. package/components/routes/hits/search/ViewLink.js +3 -3
  250. package/components/routes/hits/search/ViewLink.test.js +8 -8
  251. package/components/routes/hits/search/grid/AddColumnModal.js +5 -4
  252. package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -1
  253. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  254. package/components/routes/hits/search/grid/HitGrid.js +20 -18
  255. package/components/routes/hits/search/grid/{HitRow.d.ts → RecordRow.d.ts} +3 -2
  256. package/components/routes/hits/search/grid/{HitRow.js → RecordRow.js} +10 -8
  257. package/components/routes/hits/search/shared/IndexPicker.d.ts +2 -0
  258. package/components/routes/hits/search/shared/IndexPicker.js +20 -0
  259. package/components/routes/hits/view/HitViewer.js +12 -13
  260. package/components/routes/home/AddNewCard.js +1 -1
  261. package/components/routes/home/ViewCard.js +47 -41
  262. package/components/routes/observables/ObservableViewer.d.ts +7 -0
  263. package/components/routes/observables/ObservableViewer.js +27 -0
  264. package/components/routes/overviews/OverviewViewer.js +2 -2
  265. package/components/routes/overviews/template/en.md.js +1 -1
  266. package/components/routes/overviews/template/fr.md.js +1 -1
  267. package/components/routes/views/ViewComposer.js +46 -19
  268. package/locales/en/translation.json +125 -3
  269. package/locales/fr/translation.json +123 -3
  270. package/models/WithMetadata.d.ts +2 -1
  271. package/models/entities/generated/ApiType.d.ts +1 -1
  272. package/models/entities/generated/AttachmentsFile.d.ts +12 -0
  273. package/models/entities/generated/Case.d.ts +28 -0
  274. package/models/entities/generated/DestinationOriginal.d.ts +19 -0
  275. package/models/entities/generated/EmailAttachment.d.ts +8 -0
  276. package/models/entities/generated/EmailParent.d.ts +19 -0
  277. package/models/entities/generated/Enrichments.d.ts +7 -0
  278. package/models/entities/generated/EnrichmentsIndicator.d.ts +21 -0
  279. package/models/entities/generated/Hit.d.ts +1 -0
  280. package/models/entities/generated/Howler.d.ts +0 -5
  281. package/models/entities/generated/HttpResponse.d.ts +11 -0
  282. package/models/entities/generated/Item.d.ts +9 -0
  283. package/models/entities/generated/Observable.d.ts +85 -0
  284. package/models/entities/generated/ObservableCloud.d.ts +20 -0
  285. package/models/entities/generated/ObservableDestination.d.ts +23 -0
  286. package/models/entities/generated/ObservableEmail.d.ts +30 -0
  287. package/models/entities/generated/ObservableFile.d.ts +36 -0
  288. package/models/entities/generated/ObservableHowler.d.ts +42 -0
  289. package/models/entities/generated/ObservableHttp.d.ts +11 -0
  290. package/models/entities/generated/ObservableObserver.d.ts +21 -0
  291. package/models/entities/generated/ObservableOrganization.d.ts +7 -0
  292. package/models/entities/generated/ObservableProcess.d.ts +34 -0
  293. package/models/entities/generated/ObservableSource.d.ts +23 -0
  294. package/models/entities/generated/ObservableThreat.d.ts +21 -0
  295. package/models/entities/generated/ObservableTls.d.ts +12 -0
  296. package/models/entities/generated/ObserverIngress.d.ts +9 -0
  297. package/models/entities/generated/Rule.d.ts +6 -9
  298. package/models/entities/generated/Task.d.ts +10 -0
  299. package/models/entities/generated/Threat.d.ts +2 -2
  300. package/models/entities/generated/{Enrichment.d.ts → ThreatEnrichment.d.ts} +1 -1
  301. package/models/entities/generated/View.d.ts +1 -0
  302. package/models/socket/CaseUpdate.d.ts +5 -0
  303. package/models/socket/ViewersUpdate.d.ts +4 -0
  304. package/package.json +23 -8
  305. package/plugins/clue/components/ClueTypography.js +2 -2
  306. package/plugins/clue/utils.d.ts +2 -1
  307. package/tests/mocks.d.ts +11 -1
  308. package/tests/mocks.js +12 -7
  309. package/tests/server-handlers.js +6 -1
  310. package/tests/utils.d.ts +4 -0
  311. package/tests/utils.js +20 -0
  312. package/utils/constants.d.ts +4 -3
  313. package/utils/constants.js +6 -0
  314. package/utils/hitFunctions.d.ts +2 -1
  315. package/utils/hitFunctions.js +4 -4
  316. package/utils/menuUtils.js +1 -1
  317. package/utils/socketUtils.d.ts +14 -0
  318. package/utils/socketUtils.js +17 -1
  319. package/utils/socketUtils.test.d.ts +1 -0
  320. package/utils/socketUtils.test.js +59 -0
  321. package/utils/typeUtils.d.ts +7 -0
  322. package/utils/typeUtils.js +27 -0
  323. package/utils/viewUtils.js +3 -0
  324. package/components/app/providers/HitProvider.d.ts +0 -22
  325. package/components/elements/display/icons/BundleButton.d.ts +0 -6
  326. package/components/elements/display/icons/BundleButton.js +0 -32
  327. package/components/elements/hit/HitRelated.d.ts +0 -6
  328. package/components/elements/hit/HitRelated.js +0 -7
  329. package/components/routes/help/BundleDocumentation.d.ts +0 -3
  330. package/components/routes/help/BundleDocumentation.js +0 -12
  331. package/components/routes/help/markdown/en/bundles.md.js +0 -1
  332. package/components/routes/help/markdown/fr/bundles.md.js +0 -1
  333. package/components/routes/hits/search/BundleParentMenu.d.ts +0 -6
  334. package/components/routes/hits/search/BundleParentMenu.js +0 -32
  335. package/components/routes/hits/search/BundleScroller.d.ts +0 -2
  336. package/components/routes/hits/search/BundleScroller.js +0 -6
  337. package/components/routes/hits/search/HitContextMenu.js +0 -229
  338. /package/{components/app/providers/HitSearchProvider.test.d.ts → api/socket/viewers.test.d.ts} +0 -0
  339. /package/components/{routes/hits/search/HitContextMenu.test.d.ts → app/providers/RecordSearchProvider.test.d.ts} +0 -0
  340. /package/components/{routes/overviews/OverviewEditor.d.ts → elements/MarkdownEditor.d.ts} +0 -0
  341. /package/components/elements/hit/{HitDetails.d.ts → related/RelatedRecords.d.ts} +0 -0
  342. /package/components/routes/hits/search/{HitBrowser.d.ts → RecordBrowser.d.ts} +0 -0
@@ -3,74 +3,80 @@ import { OpenInNew } from '@mui/icons-material';
3
3
  import { Card, CardContent, IconButton, Skeleton, Stack, Typography } from '@mui/material';
4
4
  import api from '@cccsaurora/howler-ui/api';
5
5
  import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
6
- import { useHitContextSelector } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
6
+ import { useRecordContextSelector } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
7
7
  import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
8
8
  import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
9
9
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
10
+ import ObservableCard from '@cccsaurora/howler-ui/components/elements/observable/ObservableCard';
11
+ import RecordContextMenu from '@cccsaurora/howler-ui/components/elements/record/RecordContextMenu';
10
12
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
11
- import HitContextMenu from '@cccsaurora/howler-ui/components/routes/hits/search/HitContextMenu';
12
13
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
13
14
  import { useTranslation } from 'react-i18next';
14
15
  import { Link, useNavigate } from 'react-router-dom';
15
16
  import { useContextSelector } from 'use-context-selector';
17
+ import { isObservable } from '@cccsaurora/howler-ui/utils/typeUtils';
16
18
  import { buildViewUrl } from '@cccsaurora/howler-ui/utils/viewUtils';
17
- // Custom hook to select hits by IDs with proper memoization
18
- const useSelectHitsByIds = (hitIds) => {
19
- const hitIdsRef = useRef(hitIds);
19
+ // Custom hook to select records by IDs with proper memoization
20
+ const useSelectRecordsByIds = (recordIds) => {
21
+ const recordIdsRef = useRef(recordIds);
20
22
  const prevResultRef = useRef([]);
21
- const prevHitIdsRef = useRef([]);
22
- // Keep ref up to date with latest hitIds
23
- hitIdsRef.current = hitIds;
23
+ const prevRecordIdsRef = useRef([]);
24
+ // Keep ref up to date with latest recordIds
25
+ recordIdsRef.current = recordIds;
24
26
  const selector = useCallback(ctx => {
25
- const currentHitIds = hitIdsRef.current;
26
- // Fast path: if hitIds array didn't change, check if hit objects changed
27
- if (prevHitIdsRef.current.length === currentHitIds.length &&
28
- currentHitIds.every((id, i) => id === prevHitIdsRef.current[i])) {
29
- // HitIds unchanged - check if any hit objects changed by reference
30
- const anyHitChanged = currentHitIds.some((id, i) => ctx.hits[id] !== prevResultRef.current[i]);
31
- if (!anyHitChanged) {
27
+ const currentRecordIds = recordIdsRef.current;
28
+ // Fast path: if recordIds array didn't change, check if record objects changed
29
+ if (prevRecordIdsRef.current.length === currentRecordIds.length &&
30
+ currentRecordIds.every((id, i) => id === prevRecordIdsRef.current[i])) {
31
+ // RecordIds unchanged - check if any record objects changed by reference
32
+ const anyRecordChanged = currentRecordIds.some((id, i) => ctx.records[id] !== prevResultRef.current[i]);
33
+ if (!anyRecordChanged) {
32
34
  return prevResultRef.current;
33
35
  }
34
36
  }
35
37
  // Something changed - rebuild the array
36
- const currentHits = currentHitIds.map(id => ctx.hits[id]).filter(Boolean);
37
- prevHitIdsRef.current = currentHitIds;
38
- prevResultRef.current = currentHits;
39
- return currentHits;
38
+ const currentRecords = currentRecordIds.map(id => ctx.records[id]).filter(Boolean);
39
+ prevRecordIdsRef.current = currentRecordIds;
40
+ prevResultRef.current = currentRecords;
41
+ return currentRecords;
40
42
  }, []); // Empty deps - selector never changes
41
- return useHitContextSelector(selector);
43
+ return useRecordContextSelector(selector);
42
44
  };
43
45
  // Utility functions
44
46
  const normalize = (val) => (val == null ? '' : String(val));
45
47
  // Have to normalize the fields as websockets and api return null and undefined respectively. This causes false positives when comparing signatures if not normalized to a consistent value. We also stringify non-primitive values to ensure changes are detected.
46
- const createHitSignature = (hit) => {
47
- if (!hit)
48
+ const createRecordSignature = (record) => {
49
+ if (!record) {
48
50
  return '';
49
- return `${hit.howler?.id}:${normalize(hit.howler?.status)}:${normalize(hit.howler?.assignment)}:${normalize(hit.howler?.assessment)}`;
51
+ }
52
+ if (isObservable(record)) {
53
+ return record.howler?.id;
54
+ }
55
+ return `${record.howler?.id}:${normalize(record.howler?.status)}:${normalize(record.howler?.assignment)}:${normalize(record.howler?.assessment)}`;
50
56
  };
51
- const createSignatureFromHits = (hits) => {
52
- if (hits.length === 0)
57
+ const createSignatureFromRecords = (records) => {
58
+ if (records.length === 0)
53
59
  return '';
54
- return hits.map(createHitSignature).join('|');
60
+ return records.map(createRecordSignature).join('|');
55
61
  };
56
62
  const DEBOUNCE_TIME = 1000; // 1 second debounce for signature changes
57
63
  const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
58
64
  const navigate = useNavigate();
59
65
  const { t } = useTranslation();
60
66
  const { dispatchApi } = useMyApi();
61
- const [hitIds, setHitIds] = useState([]);
67
+ const [recordIds, setRecordIds] = useState([]);
62
68
  const [loading, setLoading] = useState(false);
63
69
  const debounceTimerRef = useRef(null);
64
70
  const isRefreshing = useRef(false);
65
71
  const lastSignature = useRef('');
66
72
  const view = useContextSelector(ViewContext, ctx => ctx.views[viewId]);
67
73
  const fetchViews = useContextSelector(ViewContext, ctx => ctx.fetchViews);
68
- const loadHits = useHitContextSelector(ctx => ctx.loadHits);
74
+ const loadRecords = useRecordContextSelector(ctx => ctx.loadRecords);
69
75
  // Subscribe to hits from HitProvider cache based on current hitIds in the view
70
76
  // Uses memoized selector to avoid unnecessary re-renders on unrelated hit updates
71
- const hits = useSelectHitsByIds(hitIds);
77
+ const records = useSelectRecordsByIds(recordIds);
72
78
  // Create a stable signature that only changes when relevant fields change
73
- const hitsSignature = useMemo(() => createSignatureFromHits(hits), [hits]);
79
+ const recordsSignature = useMemo(() => createSignatureFromRecords(records), [records]);
74
80
  const refreshView = useCallback(async () => {
75
81
  if (!view?.query || isRefreshing.current) {
76
82
  onRefreshComplete?.();
@@ -78,21 +84,21 @@ const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
78
84
  }
79
85
  isRefreshing.current = true;
80
86
  try {
81
- const res = await dispatchApi(api.search.hit.post({
87
+ const res = await dispatchApi(api.v2.search.post(view.indexes, {
82
88
  query: view.query,
83
89
  rows: limit,
84
90
  metadata: ['analytic']
85
91
  }));
86
- const fetchedHits = res.items ?? [];
87
- loadHits(fetchedHits);
88
- setHitIds(fetchedHits.map(h => h.howler.id));
89
- lastSignature.current = createSignatureFromHits(fetchedHits);
92
+ const fetchedRecords = res.items ?? [];
93
+ loadRecords(fetchedRecords);
94
+ setRecordIds(fetchedRecords.map(r => r.howler.id));
95
+ lastSignature.current = createSignatureFromRecords(fetchedRecords);
90
96
  }
91
97
  finally {
92
98
  isRefreshing.current = false;
93
99
  onRefreshComplete?.();
94
100
  }
95
- }, [dispatchApi, limit, view?.query, loadHits, onRefreshComplete]);
101
+ }, [dispatchApi, limit, view?.query, view?.indexes, loadRecords, onRefreshComplete]);
96
102
  const debouncedRefresh = useCallback(() => {
97
103
  if (debounceTimerRef.current) {
98
104
  clearTimeout(debounceTimerRef.current);
@@ -125,16 +131,16 @@ const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
125
131
  }, [view?.query, limit, refreshView]);
126
132
  // Monitor hits currently in the view for changes that might affect query results
127
133
  useEffect(() => {
128
- if (!hitsSignature || hitIds.length === 0 || !lastSignature.current) {
129
- lastSignature.current = hitsSignature;
134
+ if (!recordsSignature || recordIds.length === 0 || !lastSignature.current) {
135
+ lastSignature.current = recordsSignature;
130
136
  return;
131
137
  }
132
138
  // Check if signature actually changed
133
- if (lastSignature.current === hitsSignature) {
139
+ if (lastSignature.current === recordsSignature) {
134
140
  return;
135
141
  }
136
142
  debouncedRefresh();
137
- }, [hitsSignature, hitIds, debouncedRefresh]);
143
+ }, [recordsSignature, recordIds, debouncedRefresh]);
138
144
  useEffect(() => {
139
145
  return () => {
140
146
  if (debounceTimerRef.current) {
@@ -151,6 +157,6 @@ const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
151
157
  }
152
158
  return selectedElement.id;
153
159
  }, []);
154
- return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", component: Link, disabled: !view, to: view ? buildViewUrl(view) : '', onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : hits.length > 0 ? (_jsx(HitContextMenu, { getSelectedId: getSelectedId, children: hits.map(h => (_jsx(Card, { id: h.howler.id, variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate((h.howler.is_bundle ? '/bundles/' : '/hits/') + h.howler.id), children: _jsx(CardContent, { children: _jsx(HitBanner, { layout: HitLayout.DENSE, hit: h }) }) }, h.howler.id))) })) : (_jsx(AppListEmpty, {}))] }) }));
160
+ return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", component: Link, disabled: !view, to: view ? buildViewUrl(view) : '', onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : records.length > 0 ? (_jsx(RecordContextMenu, { getSelectedId: getSelectedId, children: records.map((r) => (_jsx(Card, { id: r.howler.id, variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate(`/hits/${r.howler.id}`), children: _jsx(CardContent, { children: r.__index == 'hit' ? (_jsx(HitBanner, { layout: HitLayout.DENSE, hit: r })) : (_jsx(ObservableCard, { observable: r })) }) }, r.howler.id))) })) : (_jsx(AppListEmpty, {}))] }) }));
155
161
  };
156
162
  export default ViewCard;
@@ -0,0 +1,7 @@
1
+ import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
2
+ import { type FC } from 'react';
3
+ declare const ObservableViewer: FC<{
4
+ observable?: Observable;
5
+ observableId?: string;
6
+ }>;
7
+ export default ObservableViewer;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Skeleton } from '@mui/material';
3
+ import api from '@cccsaurora/howler-ui/api';
4
+ import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
5
+ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
6
+ import { useEffect, useState } from 'react';
7
+ const ObservableViewer = ({ observable: provided, observableId }) => {
8
+ const { dispatchApi } = useMyApi();
9
+ const [observable, setObservable] = useState(null);
10
+ useEffect(() => {
11
+ if (provided) {
12
+ setObservable(provided);
13
+ }
14
+ }, [provided]);
15
+ useEffect(() => {
16
+ if (observableId) {
17
+ dispatchApi(api.v2.search.post('observable', { query: `howler.id:${observableId}`, rows: 1 }), {
18
+ throwError: false
19
+ }).then(res => setObservable(res.items[0]));
20
+ }
21
+ }, [dispatchApi, observableId]);
22
+ if (!observable) {
23
+ return;
24
+ }
25
+ return _jsx(Box, { p: 1, children: observable ? _jsx(ObjectDetails, { obj: observable }) : _jsx(Skeleton, { height: 120 }) });
26
+ };
27
+ export default ObservableViewer;
@@ -15,7 +15,7 @@ import useMyTheme from '@cccsaurora/howler-ui/components/hooks/useMyTheme';
15
15
  import { useSearchParams } from 'react-router-dom';
16
16
  import hitsData from '@cccsaurora/howler-ui/utils/hit.json';
17
17
  import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
18
- import OverviewEditor from './OverviewEditor';
18
+ import MarkdownEditor from '../../elements/MarkdownEditor';
19
19
  import { useStartingTemplate } from './startingTemplate';
20
20
  const OverviewViewer = () => {
21
21
  const theme = useTheme();
@@ -188,7 +188,7 @@ const OverviewViewer = () => {
188
188
  right: `calc(50% + 7px - ${x}px)`,
189
189
  mr: -2.4,
190
190
  pr: 1.5
191
- }, children: _jsx(OverviewEditor, { height: "100%", content: content, setContent: setContent }) }) }), _jsx(Box, { sx: {
191
+ }, children: _jsx(MarkdownEditor, { height: "100%", content: content, setContent: setContent }) }) }), _jsx(Box, { sx: {
192
192
  position: 'absolute',
193
193
  top: 0,
194
194
  bottom: 0,
@@ -1 +1 @@
1
- export default "# Creating an Overview\n\nOverviews can be used to modify the way data is presented on alerts that match the overview's settings. Overviews are, by design, easy to create and quite flexible.\n\n## Getting Started\n\nThe basic building blocks of overviews are:\n\n1. Markdown\n2. Handlebars\n\nWe will quickly explain these.\n\n### Markdown\n\nQuoting from the excellent [markdownguide.org](https://www.markdownguide.org/getting-started/):\n\n> Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents. Created by [John Gruber](https://daringfireball.net/projects/markdown/) in 2004, Markdown is now one of the world's most popular markup languages.\n>\n> Using Markdown is different than using a [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) editor. In an application like Microsoft Word, you click buttons to format words and phrases, and the changes are visible immediately. Markdown isn't like that. When you create a Markdown-formatted file, you add Markdown syntax to the text to indicate which words and phrases should look different.\n>\n> For example, to denote a heading, you add a number sign before it (e.g., `# Heading One`). Or to make a phrase bold, you add two asterisks before and after it (e.g., `**this text is bold**`). It may take a while to get used to seeing Markdown syntax in your text, especially if you're accustomed to WYSIWYG applications.\n\n---\n\n### Handlebars\n\nQuoting from [handlebarsjs.com](https://handlebarsjs.com/guide/):\n\n> Handlebars is a simple templating language.\n>\n> It uses a template and an input object to generate HTML or other text formats. Handlebars templates look like regular text with embedded Handlebars expressions.\n>\n>```html\n> <p>{{curly \"firstname\"}} {{curly \"lastname\"}}</p>\n>```\n>\n> A handlebars expression is a double curly bracket, some contents, followed by a set of closing double curly brackets. When the template is executed, these expressions are replaced with values from an input object.\n\n---\n\nFor our cases, we use handlebars to replace specific parts of markdown with the values included in a given howler hit. For example:\n\n```markdown\nThis analytic is **{{curly \"howler.analytic\"}}**\n```\n\nbecomes:\n\n> This analytic is **{{howler.analytic}}**.\n\nFor more information on handlebars, check out:\n\n- [What is Handlebars?](https://handlebarsjs.com/guide/#what-is-handlebars)\n- [Handlebars Expressions](https://handlebarsjs.com/guide/expressions.html)\n\n## Combining Markdown and Handlebars\n\nYou can use handlebars for template replacement throughout your markdown. Below is an example table using handlebars and markdown:\n\n```markdown\n\n| Source IP | Destination IP |\n| --- | --- |\n| {{curly \"source.ip\"}} |{{curly \"destination.ip\"}} |\n```\n\nrenders as:\n\n| Source IP | Destination IP |\n| --- | --- |\n| {{source.ip}} |{{destination.ip}} |\n\n## Advanced Handlebars\n\nHowler integrates a number of helper functions for you to work with.\n\n### Control Expressions\n\nFor use as subexpressions, we expose a number of conditional checks:\n\n**Equality:**\n\nGiven `howler.status` is {{howler.status}}:\n\n```markdown\n{{curly '#if (equals howler.status \"open\")'}}\nHit is open!\n{{curly \"/if\"}}\n{{curly '#if (equals howler.status \"resolved\")'}}\nHit is resolved!\n{{curly \"/if\"}}\n```\n\n{{#if (equals howler.status \"open\")}}\nHit is open!\n{{/if}}\n{{#if (equals howler.status \"resolved\")}}\nHit is resolved!\n{{/if}}\n\n**AND/OR/NOT:**\n\nGiven `howler.status` is {{howler.status}}, and `howler.escalation` is {{howler.escalation}}:\n\n```markdown\n{{curly '#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))'}}\nThis is correct!\n{{curly \"/if\"}}\n{{curly '#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))'}}\nThis is wrong!\n{{curly \"/if\"}}\n```\n\n{{#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))}}\nThis is correct!\n{{/if}}\n{{#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))}}\nThis is wrong!\n{{/if}}\n\n```markdown\n{{curly '#if (or howler.is_bundle (not howler.is_bundle))'}}\nAlways shows!\n{{curly \"/if\"}}\n```\n\n{{#if (or howler.is_bundle (not howler.is_bundle))}}\nAlways shows!\n{{/if}}\n\n---\n\n### String Operations\n\n**String Concatenation:**\n\n```markdown\n{{curly 'join \"string one \" \"string two\"'}}\n```\n\n{{join \"string one \" \"string two\"}}\n\n**Uppercase/Lowercase:**\n\n```markdown\n{{curly 'upper \"make this uppercase\"'}}\n{{curly 'lower \"MAKE THIS LOWERCASE\"'}}\n```\n\n{{upper \"make this uppercase\"}}\n\n{{lower \"MAKE THIS LOWERCASE\"}}\n\n---\n\n### Fetching Data\n\nYou can also make basic fetch requests for, and parse, JSON data from external sources:\n\n```markdown\n{{curly 'fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"'}}\n```\n\n{{fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"}}\n\n## Full Helper List\n"
1
+ export default "# Creating an Overview\n\nOverviews can be used to modify the way data is presented on alerts that match the overview's settings. Overviews are, by design, easy to create and quite flexible.\n\n## Getting Started\n\nThe basic building blocks of overviews are:\n\n1. Markdown\n2. Handlebars\n\nWe will quickly explain these.\n\n### Markdown\n\nQuoting from the excellent [markdownguide.org](https://www.markdownguide.org/getting-started/):\n\n> Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents. Created by [John Gruber](https://daringfireball.net/projects/markdown/) in 2004, Markdown is now one of the world's most popular markup languages.\n>\n> Using Markdown is different than using a [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) editor. In an application like Microsoft Word, you click buttons to format words and phrases, and the changes are visible immediately. Markdown isn't like that. When you create a Markdown-formatted file, you add Markdown syntax to the text to indicate which words and phrases should look different.\n>\n> For example, to denote a heading, you add a number sign before it (e.g., `# Heading One`). Or to make a phrase bold, you add two asterisks before and after it (e.g., `**this text is bold**`). It may take a while to get used to seeing Markdown syntax in your text, especially if you're accustomed to WYSIWYG applications.\n\n---\n\n### Handlebars\n\nQuoting from [handlebarsjs.com](https://handlebarsjs.com/guide/):\n\n> Handlebars is a simple templating language.\n>\n> It uses a template and an input object to generate HTML or other text formats. Handlebars templates look like regular text with embedded Handlebars expressions.\n>\n> ```html\n> <p>{{curly \"firstname\"}} {{curly \"lastname\"}}</p>\n> ```\n>\n> A handlebars expression is a double curly bracket, some contents, followed by a set of closing double curly brackets. When the template is executed, these expressions are replaced with values from an input object.\n\n---\n\nFor our cases, we use handlebars to replace specific parts of markdown with the values included in a given howler hit. For example:\n\n```markdown\nThis analytic is **{{curly \"howler.analytic\"}}**\n```\n\nbecomes:\n\n> This analytic is **{{howler.analytic}}**.\n\nFor more information on handlebars, check out:\n\n- [What is Handlebars?](https://handlebarsjs.com/guide/#what-is-handlebars)\n- [Handlebars Expressions](https://handlebarsjs.com/guide/expressions.html)\n\n## Combining Markdown and Handlebars\n\nYou can use handlebars for template replacement throughout your markdown. Below is an example table using handlebars and markdown:\n\n```markdown\n| Source IP | Destination IP |\n| --------------------- | -------------------------- |\n| {{curly \"source.ip\"}} | {{curly \"destination.ip\"}} |\n```\n\nrenders as:\n\n| Source IP | Destination IP |\n| ------------- | ------------------ |\n| {{source.ip}} | {{destination.ip}} |\n\n## Advanced Handlebars\n\nHowler integrates a number of helper functions for you to work with.\n\n### Control Expressions\n\nFor use as subexpressions, we expose a number of conditional checks:\n\n**Equality:**\n\nGiven `howler.status` is {{howler.status}}:\n\n```markdown\n{{curly '#if (equals howler.status \"open\")'}}\nHit is open!\n{{curly \"/if\"}}\n{{curly '#if (equals howler.status \"resolved\")'}}\nHit is resolved!\n{{curly \"/if\"}}\n```\n\n{{#if (equals howler.status \"open\")}}\nHit is open!\n{{/if}}\n{{#if (equals howler.status \"resolved\")}}\nHit is resolved!\n{{/if}}\n\n**AND/OR/NOT:**\n\nGiven `howler.status` is {{howler.status}}, and `howler.escalation` is {{howler.escalation}}:\n\n```markdown\n{{curly '#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))'}}\nThis is correct!\n{{curly \"/if\"}}\n{{curly '#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))'}}\nThis is wrong!\n{{curly \"/if\"}}\n```\n\n{{#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))}}\nThis is correct!\n{{/if}}\n{{#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))}}\nThis is wrong!\n{{/if}}\n\n```markdown\n{{curly '#if (or howler.is_bundle (not howler.is_bundle))'}}\nAlways shows!\n{{curly \"/if\"}}\n```\n\n{{#if (or howler.is_bundle (not howler.is_bundle))}}\nAlways shows!\n{{/if}}\n\n---\n\n### String Operations\n\n**String Concatenation:**\n\n```markdown\n{{curly 'join \"string one \" \"string two\"'}}\n```\n\n{{join \"string one \" \"string two\"}}\n\n**Uppercase/Lowercase:**\n\n```markdown\n{{curly 'upper \"make this uppercase\"'}}\n{{curly 'lower \"MAKE THIS LOWERCASE\"'}}\n```\n\n{{upper \"make this uppercase\"}}\n\n{{lower \"MAKE THIS LOWERCASE\"}}\n\n---\n\n### Fetching Data\n\nYou can also make basic fetch requests for, and parse, JSON data from external sources:\n\n```markdown\n{{curly 'fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"'}}\n```\n\n{{fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"}}\n\n## Full Helper List\n"
@@ -1 +1 @@
1
- export default "# Cr\u00e9er un aper\u00e7u\n\nLes aper\u00e7us peuvent \u00eatre utilis\u00e9s pour modifier la fa\u00e7on dont les donn\u00e9es sont pr\u00e9sent\u00e9es sur les alertes qui correspondent aux param\u00e8tres de l'aper\u00e7u. Les aper\u00e7us sont, par conception, faciles \u00e0 cr\u00e9er et assez flexibles.\n\n## Premiers pas\n\nLes \u00e9l\u00e9ments de base des aper\u00e7us sont :\n\n1. Markdown\n2. Handlebars\n\nNous allons rapidement expliquer ces \u00e9l\u00e9ments.\n\n### Markdown\n\nCitation de l'excellent [markdownguide.org](https://www.markdownguide.org/getting-started/) :\n\n> Markdown est un langage de balisage l\u00e9ger que vous pouvez utiliser pour ajouter des \u00e9l\u00e9ments de formatage aux documents texte en texte brut. Cr\u00e9\u00e9 par [John Gruber](https://daringfireball.net/projects/markdown/) en 2004, Markdown est maintenant l'un des langages de balisage les plus populaires au monde.\n>\n> L'utilisation de Markdown est diff\u00e9rente de l'utilisation d'un \u00e9diteur [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG). Dans une application comme Microsoft Word, vous cliquez sur des boutons pour formater les mots et les phrases, et les changements sont visibles imm\u00e9diatement. Markdown n'est pas comme cela. Lorsque vous cr\u00e9ez un fichier format\u00e9 en Markdown, vous ajoutez une syntaxe Markdown au texte pour indiquer quels mots et phrases doivent appara\u00eetre diff\u00e9remment.\n>\n> Par exemple, pour d\u00e9signer un titre, vous ajoutez un signe di\u00e8se avant celui-ci (par ex., `# Titre Un`). Ou pour mettre une phrase en gras, vous ajoutez deux ast\u00e9risques avant et apr\u00e8s (par ex., `**ce texte est en gras**`). Il peut falloir un certain temps pour s'habituer \u00e0 voir la syntaxe Markdown dans votre texte, surtout si vous \u00eates habitu\u00e9 aux applications WYSIWYG.\n\n---\n\n### Handlebars\n\nCitation de [handlebarsjs.com](https://handlebarsjs.com/guide/) :\n\n> Handlebars est un langage de template simple.\n>\n> Il utilise un template et un objet d'entr\u00e9e pour g\u00e9n\u00e9rer du HTML ou d'autres formats de texte. Les templates Handlebars ressemblent \u00e0 du texte normal avec des expressions Handlebars int\u00e9gr\u00e9es.\n>\n>```html\n> <p>{{curly \"firstname\"}} {{curly \"lastname\"}}</p>\n>```\n>\n> Une expression handlebars est une double accolade, du contenu, suivi d'un ensemble d'accolades fermantes doubles. Lorsque le template est ex\u00e9cut\u00e9, ces expressions sont remplac\u00e9es par des valeurs d'un objet d'entr\u00e9e.\n\n---\n\nDans nos cas, nous utilisons handlebars pour remplacer des parties sp\u00e9cifiques du markdown par les valeurs incluses dans un r\u00e9sultat howler donn\u00e9. Par exemple :\n\n```markdown\nCette analytique est **{{curly \"howler.analytic\"}}**\n```\n\ndevient :\n\n> Cette analytique est **{{howler.analytic}}**.\n\nPour plus d'informations sur handlebars, consultez :\n\n- [Qu'est-ce que Handlebars ?](https://handlebarsjs.com/guide/#what-is-handlebars)\n- [Expressions Handlebars](https://handlebarsjs.com/guide/expressions.html)\n\n## Combiner Markdown et Handlebars\n\nVous pouvez utiliser handlebars pour le remplacement de template dans tout votre markdown. Voici un exemple de tableau utilisant handlebars et markdown :\n\n```markdown\n\n| IP Source | IP Destination |\n| --- | --- |\n| {{curly \"source.ip\"}} |{{curly \"destination.ip\"}} |\n```\n\ns'affiche comme :\n\n| IP Source | IP Destination |\n| --- | --- |\n| {{source.ip}} |{{destination.ip}} |\n\n## Handlebars avanc\u00e9s\n\nHowler int\u00e8gre un certain nombre de fonctions d'aide avec lesquelles vous pouvez travailler.\n\n### Expressions de contr\u00f4le\n\nPour utilisation comme sous-expressions, nous exposons un certain nombre de v\u00e9rifications conditionnelles :\n\n**\u00c9galit\u00e9 :**\n\n\u00c9tant donn\u00e9 que `howler.status` est {{howler.status}} :\n\n```markdown\n{{curly '#if (equals howler.status \"open\")'}}\nLe r\u00e9sultat est ouvert !\n{{curly \"/if\"}}\n{{curly '#if (equals howler.status \"resolved\")'}}\nLe r\u00e9sultat est r\u00e9solu !\n{{curly \"/if\"}}\n```\n\n{{#if (equals howler.status \"open\")}}\nLe r\u00e9sultat est ouvert !\n{{/if}}\n{{#if (equals howler.status \"resolved\")}}\nLe r\u00e9sultat est r\u00e9solu !\n{{/if}}\n\n**ET/OU/NON :**\n\n\u00c9tant donn\u00e9 que `howler.status` est {{howler.status}}, et `howler.escalation` est {{howler.escalation}} :\n\n```markdown\n{{curly '#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))'}}\nC'est correct !\n{{curly \"/if\"}}\n{{curly '#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))'}}\nC'est incorrect !\n{{curly \"/if\"}}\n```\n\n{{#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))}}\nC'est correct !\n{{/if}}\n{{#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))}}\nC'est incorrect !\n{{/if}}\n\n```markdown\n{{curly '#if (or howler.is_bundle (not howler.is_bundle))'}}\nS'affiche toujours !\n{{curly \"/if\"}}\n```\n\n{{#if (or howler.is_bundle (not howler.is_bundle))}}\nS'affiche toujours !\n{{/if}}\n\n---\n\n### Op\u00e9rations sur les cha\u00eenes\n\n**Concat\u00e9nation de cha\u00eenes :**\n\n```markdown\n{{curly 'join \"cha\u00eene une \" \"cha\u00eene deux\"'}}\n```\n\n{{join \"cha\u00eene une \" \"cha\u00eene deux\"}}\n\n**Majuscules/Minuscules :**\n\n```markdown\n{{curly 'upper \"mettre ceci en majuscules\"'}}\n{{curly 'lower \"METTRE CECI EN MINUSCULES\"'}}\n```\n\n{{upper \"mettre ceci en majuscules\"}}\n\n{{lower \"METTRE CECI EN MINUSCULES\"}}\n\n---\n\n### R\u00e9cup\u00e9ration de donn\u00e9es\n\nVous pouvez \u00e9galement faire des requ\u00eates fetch de base pour r\u00e9cup\u00e9rer et analyser des donn\u00e9es JSON de sources externes :\n\n```markdown\n{{curly 'fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"'}}\n```\n\n{{fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"}}\n\n## Liste compl\u00e8te des assistants\n"
1
+ export default "# Cr\u00e9er un aper\u00e7u\n\nLes aper\u00e7us peuvent \u00eatre utilis\u00e9s pour modifier la fa\u00e7on dont les donn\u00e9es sont pr\u00e9sent\u00e9es sur les alertes qui correspondent aux param\u00e8tres de l'aper\u00e7u. Les aper\u00e7us sont, par conception, faciles \u00e0 cr\u00e9er et assez flexibles.\n\n## Premiers pas\n\nLes \u00e9l\u00e9ments de base des aper\u00e7us sont :\n\n1. Markdown\n2. Handlebars\n\nNous allons rapidement expliquer ces \u00e9l\u00e9ments.\n\n### Markdown\n\nCitation de l'excellent [markdownguide.org](https://www.markdownguide.org/getting-started/) :\n\n> Markdown est un langage de balisage l\u00e9ger que vous pouvez utiliser pour ajouter des \u00e9l\u00e9ments de formatage aux documents texte en texte brut. Cr\u00e9\u00e9 par [John Gruber](https://daringfireball.net/projects/markdown/) en 2004, Markdown est maintenant l'un des langages de balisage les plus populaires au monde.\n>\n> L'utilisation de Markdown est diff\u00e9rente de l'utilisation d'un \u00e9diteur [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG). Dans une application comme Microsoft Word, vous cliquez sur des boutons pour formater les mots et les phrases, et les changements sont visibles imm\u00e9diatement. Markdown n'est pas comme cela. Lorsque vous cr\u00e9ez un fichier format\u00e9 en Markdown, vous ajoutez une syntaxe Markdown au texte pour indiquer quels mots et phrases doivent appara\u00eetre diff\u00e9remment.\n>\n> Par exemple, pour d\u00e9signer un titre, vous ajoutez un signe di\u00e8se avant celui-ci (par ex., `# Titre Un`). Ou pour mettre une phrase en gras, vous ajoutez deux ast\u00e9risques avant et apr\u00e8s (par ex., `**ce texte est en gras**`). Il peut falloir un certain temps pour s'habituer \u00e0 voir la syntaxe Markdown dans votre texte, surtout si vous \u00eates habitu\u00e9 aux applications WYSIWYG.\n\n---\n\n### Handlebars\n\nCitation de [handlebarsjs.com](https://handlebarsjs.com/guide/) :\n\n> Handlebars est un langage de template simple.\n>\n> Il utilise un template et un objet d'entr\u00e9e pour g\u00e9n\u00e9rer du HTML ou d'autres formats de texte. Les templates Handlebars ressemblent \u00e0 du texte normal avec des expressions Handlebars int\u00e9gr\u00e9es.\n>\n> ```html\n> <p>{{curly \"firstname\"}} {{curly \"lastname\"}}</p>\n> ```\n>\n> Une expression handlebars est une double accolade, du contenu, suivi d'un ensemble d'accolades fermantes doubles. Lorsque le template est ex\u00e9cut\u00e9, ces expressions sont remplac\u00e9es par des valeurs d'un objet d'entr\u00e9e.\n\n---\n\nDans nos cas, nous utilisons handlebars pour remplacer des parties sp\u00e9cifiques du markdown par les valeurs incluses dans un r\u00e9sultat howler donn\u00e9. Par exemple :\n\n```markdown\nCette analytique est **{{curly \"howler.analytic\"}}**\n```\n\ndevient :\n\n> Cette analytique est **{{howler.analytic}}**.\n\nPour plus d'informations sur handlebars, consultez :\n\n- [Qu'est-ce que Handlebars ?](https://handlebarsjs.com/guide/#what-is-handlebars)\n- [Expressions Handlebars](https://handlebarsjs.com/guide/expressions.html)\n\n## Combiner Markdown et Handlebars\n\nVous pouvez utiliser handlebars pour le remplacement de template dans tout votre markdown. Voici un exemple de tableau utilisant handlebars et markdown :\n\n```markdown\n| IP Source | IP Destination |\n| --------------------- | -------------------------- |\n| {{curly \"source.ip\"}} | {{curly \"destination.ip\"}} |\n```\n\ns'affiche comme :\n\n| IP Source | IP Destination |\n| ------------- | ------------------ |\n| {{source.ip}} | {{destination.ip}} |\n\n## Handlebars avanc\u00e9s\n\nHowler int\u00e8gre un certain nombre de fonctions d'aide avec lesquelles vous pouvez travailler.\n\n### Expressions de contr\u00f4le\n\nPour utilisation comme sous-expressions, nous exposons un certain nombre de v\u00e9rifications conditionnelles :\n\n**\u00c9galit\u00e9 :**\n\n\u00c9tant donn\u00e9 que `howler.status` est {{howler.status}} :\n\n```markdown\n{{curly '#if (equals howler.status \"open\")'}}\nLe r\u00e9sultat est ouvert !\n{{curly \"/if\"}}\n{{curly '#if (equals howler.status \"resolved\")'}}\nLe r\u00e9sultat est r\u00e9solu !\n{{curly \"/if\"}}\n```\n\n{{#if (equals howler.status \"open\")}}\nLe r\u00e9sultat est ouvert !\n{{/if}}\n{{#if (equals howler.status \"resolved\")}}\nLe r\u00e9sultat est r\u00e9solu !\n{{/if}}\n\n**ET/OU/NON :**\n\n\u00c9tant donn\u00e9 que `howler.status` est {{howler.status}}, et `howler.escalation` est {{howler.escalation}} :\n\n```markdown\n{{curly '#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))'}}\nC'est correct !\n{{curly \"/if\"}}\n{{curly '#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))'}}\nC'est incorrect !\n{{curly \"/if\"}}\n```\n\n{{#if (and (equals howler.status \"open\") (equals howler.escalation \"alert\"))}}\nC'est correct !\n{{/if}}\n{{#if (and (equals howler.status \"resolved\") (equals howler.escalation \"hit\"))}}\nC'est incorrect !\n{{/if}}\n\n```markdown\n{{curly '#if (or howler.is_bundle (not howler.is_bundle))'}}\nS'affiche toujours !\n{{curly \"/if\"}}\n```\n\n{{#if (or howler.is_bundle (not howler.is_bundle))}}\nS'affiche toujours !\n{{/if}}\n\n---\n\n### Op\u00e9rations sur les cha\u00eenes\n\n**Concat\u00e9nation de cha\u00eenes :**\n\n```markdown\n{{curly 'join \"cha\u00eene une \" \"cha\u00eene deux\"'}}\n```\n\n{{join \"cha\u00eene une \" \"cha\u00eene deux\"}}\n\n**Majuscules/Minuscules :**\n\n```markdown\n{{curly 'upper \"mettre ceci en majuscules\"'}}\n{{curly 'lower \"METTRE CECI EN MINUSCULES\"'}}\n```\n\n{{upper \"mettre ceci en majuscules\"}}\n\n{{lower \"METTRE CECI EN MINUSCULES\"}}\n\n---\n\n### R\u00e9cup\u00e9ration de donn\u00e9es\n\nVous pouvez \u00e9galement faire des requ\u00eates fetch de base pour r\u00e9cup\u00e9rer et analyser des donn\u00e9es JSON de sources externes :\n\n```markdown\n{{curly 'fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"'}}\n```\n\n{{fetch \"/api/v1/configs\" \"api_response.c12nDef.UNRESTRICTED\"}}\n\n## Liste compl\u00e8te des assistants\n"
@@ -4,10 +4,11 @@ import { useTranslation } from 'react-i18next';
4
4
  import { HelpOutline, Save } from '@mui/icons-material';
5
5
  import { Alert, Checkbox, CircularProgress, LinearProgress, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from '@mui/material';
6
6
  import api from '@cccsaurora/howler-ui/api';
7
+ import {} from '@cccsaurora/howler-ui/api/search';
7
8
  import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
8
9
  import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
9
- import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
10
10
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
11
+ import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
11
12
  import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
12
13
  import CustomButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomButton';
13
14
  import FlexPort from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexPort';
@@ -17,6 +18,7 @@ import VSBoxHeader from '@cccsaurora/howler-ui/components/elements/addons/layout
17
18
  import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
18
19
  import HitCard from '@cccsaurora/howler-ui/components/elements/hit/HitCard';
19
20
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
21
+ import ObservableCard from '@cccsaurora/howler-ui/components/elements/observable/ObservableCard';
20
22
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
21
23
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
22
24
  import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
@@ -26,8 +28,9 @@ import { DEFAULT_QUERY, StorageKey } from '@cccsaurora/howler-ui/utils/constants
26
28
  import { convertDateToLucene } from '@cccsaurora/howler-ui/utils/utils';
27
29
  import { buildViewUrl } from '@cccsaurora/howler-ui/utils/viewUtils';
28
30
  import ErrorBoundary from '../ErrorBoundary';
29
- import HitQuery from '../hits/search/HitQuery';
31
+ import RecordQuery from '../hits/search/RecordQuery';
30
32
  import HitSort from '../hits/search/shared/HitSort';
33
+ import IndexPicker from '../hits/search/shared/IndexPicker';
31
34
  import SearchSpan from '../hits/search/shared/SearchSpan';
32
35
  const ViewComposer = () => {
33
36
  const { t } = useTranslation();
@@ -38,8 +41,10 @@ const ViewComposer = () => {
38
41
  const addView = useContextSelector(ViewContext, ctx => ctx.addView);
39
42
  const editView = useContextSelector(ViewContext, ctx => ctx.editView);
40
43
  const getCurrentViews = useContextSelector(ViewContext, ctx => ctx.getCurrentViews);
44
+ const indexes = useContextSelector(ParameterContext, ctx => ctx.indexes);
45
+ const setIndexes = useContextSelector(ParameterContext, ctx => ctx.setIndexes);
41
46
  const pageCount = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25)[0];
42
- const loadHits = useContextSelector(HitContext, ctx => ctx.loadHits);
47
+ const loadRecords = useContextSelector(RecordContext, ctx => ctx.loadRecords);
43
48
  // view state
44
49
  const [title, setTitle] = useState('');
45
50
  const [type, setType] = useState('global');
@@ -56,14 +61,17 @@ const ViewComposer = () => {
56
61
  const [searching, setSearching] = useState(false);
57
62
  const [error, setError] = useState(null);
58
63
  const [response, setResponse] = useState();
64
+ const [isLoadingView, setIsLoadingView] = useState(!!routeParams.id);
59
65
  const onSave = useCallback(async () => {
60
66
  setLoading(true);
61
67
  try {
68
+ const normalizedIndexes = indexes?.length > 0 ? indexes : ['hit'];
62
69
  if (!routeParams.id) {
63
70
  const newView = await addView({
64
71
  title,
65
72
  type,
66
73
  query,
74
+ indexes: normalizedIndexes,
67
75
  sort: sort || null,
68
76
  span: span || null,
69
77
  settings: {
@@ -77,6 +85,7 @@ const ViewComposer = () => {
77
85
  title,
78
86
  type,
79
87
  query,
88
+ indexes: normalizedIndexes,
80
89
  sort,
81
90
  span,
82
91
  settings: { advance_on_triage: advanceOnTriage }
@@ -101,23 +110,24 @@ const ViewComposer = () => {
101
110
  sort,
102
111
  span,
103
112
  advanceOnTriage,
113
+ indexes,
104
114
  navigate,
105
115
  editView,
106
116
  showErrorMessage
107
117
  ]);
108
- const search = useCallback(async (_query) => {
109
- setQuery(_query);
118
+ const performSearch = useCallback(async (searchQuery, searchIndexes, searchSort, searchSpan) => {
110
119
  setSearching(true);
111
120
  setError(null);
112
121
  try {
113
- const _response = await dispatchApi(api.search.hit.post({
122
+ const normalizedIndexes = searchIndexes?.length > 0 ? searchIndexes : ['hit'];
123
+ const _response = await dispatchApi(api.v2.search.post(normalizedIndexes, {
114
124
  rows: pageCount,
115
- query: _query,
116
- sort,
117
- filters: span ? [`event.created:${convertDateToLucene(span)}`] : [],
125
+ query: searchQuery,
126
+ sort: searchSort,
127
+ filters: searchSpan ? [`event.created:${convertDateToLucene(searchSpan)}`] : [],
118
128
  metadata: ['template', 'analytic']
119
129
  }), { showError: false, throwError: true });
120
- loadHits(_response.items);
130
+ loadRecords(_response.items);
121
131
  setResponse(_response);
122
132
  }
123
133
  catch (e) {
@@ -126,18 +136,25 @@ const ViewComposer = () => {
126
136
  finally {
127
137
  setSearching(false);
128
138
  }
129
- }, [dispatchApi, loadHits, pageCount, setQuery, sort, span]);
139
+ }, [dispatchApi, loadRecords, pageCount]);
140
+ const search = useCallback(async (_query) => {
141
+ setQuery(_query);
142
+ await performSearch(_query, indexes, sort, span);
143
+ }, [performSearch, indexes, sort, span, setQuery]);
130
144
  useEffect(() => {
131
- search(query || DEFAULT_QUERY);
145
+ // Only run initial search if we're NOT editing an existing view
146
+ if (!routeParams.id) {
147
+ search(query || DEFAULT_QUERY);
148
+ }
132
149
  // eslint-disable-next-line react-hooks/exhaustive-deps
133
- }, []);
150
+ }, [routeParams.id]);
134
151
  // We only run this when ancillary properties (i.e. filters, sorting) change
135
152
  useEffect(() => {
136
- if (query) {
153
+ if (query && !isLoadingView) {
137
154
  search(query);
138
155
  }
139
156
  // eslint-disable-next-line react-hooks/exhaustive-deps
140
- }, [sort, span]);
157
+ }, [sort, span, indexes, isLoadingView]);
141
158
  useEffect(() => {
142
159
  if (!routeParams.id) {
143
160
  return;
@@ -153,13 +170,23 @@ const ViewComposer = () => {
153
170
  }
154
171
  setTitle(viewToEdit.title);
155
172
  setAdvanceOnTriage(viewToEdit.settings?.advance_on_triage ?? false);
156
- setQuery(viewToEdit.query);
173
+ const loadedQuery = viewToEdit.query || DEFAULT_QUERY;
174
+ const loadedIndexes = viewToEdit.indexes || indexes;
175
+ const loadedSort = viewToEdit.sort || sort;
176
+ const loadedSpan = viewToEdit.span || span;
177
+ setQuery(loadedQuery);
178
+ if (viewToEdit.indexes) {
179
+ setIndexes(loadedIndexes);
180
+ }
157
181
  if (viewToEdit.sort) {
158
- setSort(viewToEdit.sort);
182
+ setSort(loadedSort);
159
183
  }
160
184
  if (viewToEdit.span) {
161
- setSpan(viewToEdit.span);
185
+ setSpan(loadedSpan);
162
186
  }
187
+ // Perform search with the loaded values to avoid using stale state
188
+ await performSearch(loadedQuery, loadedIndexes, loadedSort, loadedSpan);
189
+ setIsLoadingView(false);
163
190
  })();
164
191
  // eslint-disable-next-line react-hooks/exhaustive-deps
165
192
  }, [routeParams.id]);
@@ -172,6 +199,6 @@ const ViewComposer = () => {
172
199
  fontSize: '0.9em',
173
200
  fontStyle: 'italic',
174
201
  mb: 0.5
175
- }), variant: "body2", children: t('hit.search.prompt') }), _jsx(HitQuery, { triggerSearch: search, searching: searching, onChange: (_query, isDirty) => setIsSearchDirty(isDirty) }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(HitSort, {}), _jsx(SearchSpan, { omitCustom: true }), _jsx("div", { style: { flex: 1 } }), _jsxs(Stack, { spacing: 1, direction: "row", alignItems: "center", sx: { flex: '0 !important', minWidth: '300px' }, children: [_jsx(Typography, { component: "span", children: t('view.settings.advance_on_triage') }), _jsx(Tooltip, { title: t('view.settings.advance_on_triage.description'), children: _jsx(HelpOutline, { sx: { fontSize: '16px' } }) }), _jsx(Checkbox, { size: "small", checked: advanceOnTriage, onChange: (_event, checked) => setAdvanceOnTriage(checked) })] })] }), response?.total ? (_jsx(SearchTotal, { total: response.total, pageLength: response.items.length, offset: response.offset, sx: theme => ({ color: theme.palette.text.secondary, fontSize: '0.9em', fontStyle: 'italic' }) })) : null, _jsx(LinearProgress, { sx: [!searching && { opacity: 0 }] })] }) }), _jsx(VSBoxContent, { children: _jsxs(Stack, { spacing: 1, children: [!response?.total && _jsx(AppListEmpty, {}), response?.items.map(hit => (_jsx(HitCard, { id: hit.howler.id, layout: HitLayout.DENSE }, hit.howler.id)))] }) })] }) }) }) }));
202
+ }), variant: "body2", children: t('hit.search.prompt') }), _jsx(RecordQuery, { triggerSearch: search, searching: searching, onChange: (_query, isDirty) => setIsSearchDirty(isDirty) }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(IndexPicker, {}), _jsx(HitSort, {}), _jsx(SearchSpan, { omitCustom: true }), _jsx("div", { style: { flex: 1 } }), _jsxs(Stack, { spacing: 1, direction: "row", alignItems: "center", sx: { flex: '0 !important', minWidth: '300px' }, children: [_jsx(Typography, { component: "span", children: t('view.settings.advance_on_triage') }), _jsx(Tooltip, { title: t('view.settings.advance_on_triage.description'), children: _jsx(HelpOutline, { sx: { fontSize: '16px' } }) }), _jsx(Checkbox, { size: "small", checked: advanceOnTriage, onChange: (_event, checked) => setAdvanceOnTriage(checked) })] })] }), response?.total ? (_jsx(SearchTotal, { total: response.total, pageLength: response.items.length, offset: response.offset, sx: theme => ({ color: theme.palette.text.secondary, fontSize: '0.9em', fontStyle: 'italic' }) })) : null, _jsx(LinearProgress, { sx: [!searching && { opacity: 0 }] })] }) }), _jsx(VSBoxContent, { children: _jsxs(Stack, { spacing: 1, children: [!response?.total && _jsx(AppListEmpty, {}), response?.items.map(record => record.__index === 'hit' ? (_jsx(HitCard, { id: record.howler.id, layout: HitLayout.DENSE }, record.howler.id)) : (_jsx(ObservableCard, { observable: record }, record.howler.id)))] }) })] }) }) }) }));
176
203
  };
177
204
  export default ViewComposer;