@cryptotaxi247 / CoPilot / commits / 5a1378ce

Refactor 1 (#723)

* Refactor: Renames useGoto to useNavigation This commit renames the `useGoto` composable to `useNavigation` to provide a more accurate and descriptive name that reflects its purpose of handling application navigation. This change improves code clarity and maintainability by using a more semantically correct name for the composable. * format * Fix: Handles empty children and formats code Fixes an issue where the directive fails when the element has no children. Improves code formatting by attempting to parse and stringify JSON with indentation for better readability. * Refactor: Centralizes loading bar setup Moves loading bar initialization to a global listener. This ensures the loading bar is consistently managed across the application and avoids redundant setup in individual components. * format * Style: Improves Wazuh deployment descriptions Updates the Wazuh deployment description text color to improve readability. * Refactor: Improves card stat layout Refactors the CardStatsMulti component to use a grid layout with dividers for better visual separation of stat items, enhancing readability and responsiveness. Removes redundant border style rule. * Style: Improves chat query UI Enhances the chat query component's appearance and spacing. Adjusts input field padding for better visual alignment. Improves spacing in options for enhanced readability. * Refactor: Improves code readability Refactors code in multiple components for better readability and maintainability by removing unnecessary code and adding TODOs for future improvements. Applies minor formatting adjustments. * Refactor: Rename `goto` to `route` Renames the `goto` navigation functions to `route` for clarity and consistency. This change improves the codebase by using a more descriptive and consistent naming convention for navigation functions, making the code easier to understand and maintain. * Refactors date formatting Migrates date formatting logic to a dedicated `format.ts` file. This change promotes code reusability and maintainability by centralizing date formatting functions and removing redundant implementations across various components. * Refactors date formatting Refactors the date formatting utility to improve consistency and allow for customizable date formats via settings. This change moves the formatDate function to a dedicated "format" directory within the utils folder and updates all components that use it to import from the new location. It also enhances the GitHubAuditReportCard component to use the date format specified in the settings store. * Feat: Adds Wazuh API port to customer meta form Adds the Wazuh API port field to the customer meta form. This change allows users to configure the Wazuh API port for each customer. It includes adding the field to the form, updating the data model, and ensuring the value is correctly passed to the backend. * Refactor: Adds TODO for router refactoring Adds a TODO comment to indicate a future refactoring of the router implementation, aiming for a more modular and maintainable approach, similar to the MDR pattern. * Refactor: Enhance navigation logic Refactors the navigation logic to provide a more consistent and flexible approach to routing. This change introduces a `routerConstructor` to encapsulate the navigation functions, allowing for easier navigation and URL manipulation throughout the frontend application. This change modifies the click events to execute the .navigate() function * refactor * Refactors snapshot components This commit introduces minor refactoring to snapshot components. It addresses code cleanliness and maintainability by importing the Icon component using the correct path, and adds TODO comments where further refactoring is needed * Refactors date formatting to use user settings Refactors the date formatting in the GitHub audit card to utilize the user-defined date format from the settings store. This ensures consistency and personalization of date representations throughout the application. * Fix: Corrects SonicWall model name typo Fixes a typo in the SonicWall model interface name, ensuring consistency and preventing potential import issues. * Refactor: Moves ScriptParameterType definition Moves the `ScriptParameterType` definition to be a type alias instead of an enum. This improves type clarity and consistency. * Refactor: Improves customer form input handling Refactors the customer form to iterate over `fieldsMeta` instead of the `form` object directly. This provides a more robust and maintainable way to manage form inputs and their associated metadata. * Refactor: Streamlines InvokeActionForm layout Refactors the InvokeActionForm component for improved readability and maintainability. - Updates the layout using flexbox for better alignment and spacing. - Removes unnecessary divs and simplifies the overall structure. - Improves the handling of agent selection and parameter input. * Refactor: Updates AccessDenied component icons Refactors the AccessDenied component to use a centralized Icon component instead of individual icon imports. This improves code maintainability and consistency across the application. * Chore: Refactors API endpoint files Refactors multiple API endpoint files to improve code readability and maintainability. Adds TODO comments to highlight areas needing further refactoring. * Chore: Adds TODO comments for future refactoring Adds TODO comments in `Navbar/items.tsx`, `router/index.ts`, `Indices.vue`, and `Snapshots.vue` to indicate areas needing refactoring. This change serves as a reminder for future work and does not introduce any functional changes. * Chore: Adds TODO comments for future refactor Adds "TODO: refactor" comments to type definition files. These comments serve as reminders to refactor these files in the future, likely to improve code organization, readability, or maintainability. No functional code is changed. * Chore: Refactors components for better maintainability Refactors multiple components to improve code readability and maintainability by adding TODOs for future refactoring. It also adjustes text styles in AgentToolbar * Chore: Updates dependencies Updates dependencies in package.json to their latest compatible versions, including pnpm, codemirror, shiki, vue, and other related packages. This ensures that the project uses the latest features and security updates from the respective libraries. * Refactors: Removes unused ref properties Removes unused `ref` properties from multiple components. These properties were no longer necessary for the component's functionality, simplifying the code and potentially improving performance. * chore: updates dependencies Updates various dependencies in package.json to their latest versions. This ensures the project is using the most recent versions of its dependencies, which can include bug fixes, performance improvements, and new features. * Chore: Updates dev dependencies Updates `@vue/tsconfig` and adds `baseline-browser-mapping` to align with refactoring efforts. * Fix: Prevents errors on missing data Addresses potential errors caused by missing data when rendering components. Adds checks for null or undefined values in various components to prevent errors and improve application stability. This includes: - Handling null `entry` values in `useResizeObserver` hooks. - Adding `pageSize.value` check to avoid errors when it's initially null. - Using optional chaining and nullish coalescing operators to safely access properties and provide default values. * Fix: Addresses potential null values Addresses potential null or undefined values in various parts of the codebase, preventing unexpected errors and improving application stability. Specifically: - Uses nullish coalescing operator (`??`) to provide default values when accessing potentially null user roles in the authentication store. - Adds a check for null `body` element before manipulating its classes in the theme store. - Includes a null check for style values to prevent errors when setting CSS variables in the theme store. - Uses optional chaining and nullish coalescing when extracting indentation from code snippets in the highlighter utility. - Applies nullish coalescing operator (`??`) to avoid errors when accessing theme colors. - Adds optional chaining to prevent errors when splitting options. * Fix: Handles potential null values Addresses potential null or undefined values in several components to prevent errors. This change introduces the nullish coalescing operator (??) and optional chaining (?.) to safely access properties and prevent the application from crashing when dealing with potentially missing data. Also adds "TODO: refactor" to multiple components. * Fix: Prevents potential null reference errors Addresses potential null reference exceptions by adding null safety checks in multiple components. This change ensures that the application handles cases where data might be missing, preventing runtime errors and improving overall stability. The affected components are: - CustomerPortal Form - CustomerItem - CustomerMetaForm - CustomerIntegrationForm - CustomerNetworkConnectorForm - Graylog Alerts List - Graylog Metrics List Adds "TODO: refactor" to some files * Fix: Prevents errors with null values Addresses potential errors caused by null or undefined values in several components. This change ensures that the application gracefully handles cases where optional values are not provided, preventing unexpected crashes or incorrect behavior. Adds null checks for array lengths and optional values before accessing properties or rendering components. * Feat: Introduces composable for breadcrumbs Introduces a composable function for managing breadcrumb items. This promotes code reuse and simplifies breadcrumb management across different components. It also updates the breadcrumb component to use the new composable. Additionally, adds a default value to the alerts filter timerange. * updated pnpm lock file --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Feb 17, 2026 at 08:04 UTC 5a1378ced0c8791cf203ded9af2d0d08b4e0ca3c
238 files changed +9595 -9367
frontend/package.json
+36 -35
@@ -3,7 +3,7 @@
3 "type": "module",
4 "version": "1.0.0",
5 "private": true,
6 - "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
6 + "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
7 "engines": {
8 "node": ">=18.0.0"
9 },
@@ -37,35 +37,35 @@
37 },
38 "dependencies": {
39 "@ajoelp/json-to-formdata": "^1.5.0",
40 - "@codemirror/commands": "^6.10.0",
40 + "@codemirror/commands": "^6.10.2",
41 "@codemirror/lang-javascript": "^6.2.4",
42 "@codemirror/lang-xml": "^6.1.0",
43 - "@codemirror/lint": "^6.9.2",
43 + "@codemirror/lint": "^6.9.4",
44 "@codemirror/theme-one-dark": "^6.1.3",
45 - "@codemirror/view": "^6.38.8",
45 + "@codemirror/view": "^6.39.14",
46 "@f3ve/vue-markdown-it": "^0.2.3",
47 "@fontsource/jetbrains-mono": "^5.2.8",
48 "@fontsource/lexend": "^5.2.11",
49 "@fontsource/public-sans": "^5.2.7",
50 "@microsoft/fetch-event-source": "^2.0.1",
51 - "@shikijs/markdown-it": "^3.19.0",
51 + "@shikijs/markdown-it": "^3.22.0",
52 "@singulio/app-auth-search": "^0.0.3",
53 "@types/codemirror": "^5.60.17",
54 - "@vueuse/core": "^14.1.0",
54 + "@vueuse/core": "^14.2.1",
55 "@vueuse/motion": "^3.0.3",
56 - "axios": "^1.13.2",
56 + "axios": "^1.13.5",
57 "bytes": "^3.1.2",
58 "codemirror": "~6.0.2",
59 "colord": "^2.9.3",
60 "dayjs": "^1.11.19",
61 "detect-touch-device": "^1.1.6",
62 "echarts": "^6.0.0",
63 - "fast-xml-parser": "^5.3.2",
63 + "fast-xml-parser": "^5.3.6",
64 "file-saver": "^2.0.5",
65 "html-entities": "^2.6.0",
66 "jose": "^6.1.3",
67 "js-md5": "^0.8.3",
68 - "lodash": "^4.17.21",
68 + "lodash": "^4.17.23",
69 "mitt": "^3.0.1",
70 "naive-ui": "^2.43.2",
71 "nanoid": "^5.1.6",
@@ -73,15 +73,15 @@
73 "pinia": "^3.0.4",
74 "pinia-plugin-persistedstate": "^4.7.1",
75 "secure-ls": "^2.0.0",
76 - "shiki": "^3.19.0",
76 + "shiki": "^3.22.0",
77 "thememirror": "^2.0.1",
78 - "validator": "^13.15.23",
79 - "vue": "^3.5.25",
78 + "validator": "^13.15.26",
79 + "vue": "^3.5.28",
80 "vue-advanced-cropper": "^2.8.9",
81 "vue-codemirror": "^6.1.1",
82 "vue-highlight-words": "^3.0.1",
83 - "vue-i18n": "^11.2.2",
84 - "vue-router": "^4.6.3",
83 + "vue-i18n": "^11.2.8",
84 + "vue-router": "^5.0.2",
85 "vue-sjv": "^0.0.6",
86 "vue3-apexcharts": "^1.10.0",
87 "vue3-marquee": "^4.2.2",
@@ -95,45 +95,46 @@
95 "vueuc": "^0.4.64"
96 },
97 "devDependencies": {
98 - "@antfu/eslint-config": "~6.4.2",
99 - "@clack/prompts": "^0.11.0",
98 + "@antfu/eslint-config": "~7.4.3",
99 + "@clack/prompts": "^1.0.1",
100 "@iconify/vue": "^5.0.0",
101 - "@tailwindcss/vite": "^4.1.17",
102 - "@tsconfig/node20": "^20.1.8",
101 + "@tailwindcss/vite": "^4.1.18",
102 + "@tsconfig/node20": "^20.1.9",
103 "@types/bytes": "^3.1.5",
104 "@types/file-saver": "^2.0.7",
105 "@types/fs-extra": "^11.0.4",
106 "@types/jsdom": "^27.0.0",
107 - "@types/lodash": "^4.17.21",
107 + "@types/lodash": "^4.17.23",
108 "@types/markdown-it": "^14.1.2",
109 - "@types/node": "^24.10.1",
109 + "@types/node": "^25.2.3",
110 "@types/validator": "^13.15.10",
111 - "@vitejs/plugin-vue": "^6.0.2",
112 - "@vitejs/plugin-vue-jsx": "^5.1.2",
111 + "@vitejs/plugin-vue": "^6.0.4",
112 + "@vitejs/plugin-vue-jsx": "^5.1.4",
113 "@vue/test-utils": "^2.4.6",
114 - "@vue/tsconfig": "~0.7.0",
115 - "cypress": "^15.7.1",
114 + "@vue/tsconfig": "~0.8.1",
115 + "baseline-browser-mapping": "^2.9.19",
116 + "cypress": "^15.10.0",
117 "depcheck": "^1.4.7",
117 - "eslint": "~9.39.1",
118 + "eslint": "~10.0.0",
119 "flourite": "^1.3.0",
119 - "fs-extra": "^11.3.2",
120 - "jsdom": "^27.2.0",
120 + "fs-extra": "^11.3.3",
121 + "jsdom": "^28.1.0",
122 "npm-run-all2": "^8.0.4",
122 - "prettier": "^3.7.4",
123 + "prettier": "^3.8.1",
124 "prettier-plugin-tailwindcss": "^0.7.2",
124 - "sass": "^1.94.2",
125 + "sass": "^1.97.3",
126 "start-server-and-test": "^2.1.3",
126 - "tailwindcss": "^4.1.17",
127 + "tailwindcss": "^4.1.18",
128 "taze": "^19.9.2",
128 - "type-fest": "^5.3.0",
129 + "type-fest": "^5.4.4",
130 "typescript": "~5.9.3",
130 - "vite": "^7.2.6",
131 + "vite": "^7.3.1",
132 "vite-bundle-visualizer": "^1.2.1",
133 "vite-plugin-inspect": "^11.3.3",
133 - "vite-plugin-vue-devtools": "^8.0.5",
134 + "vite-plugin-vue-devtools": "^8.0.6",
135 "vite-svg-loader": "^5.1.0",
135 - "vitest": "^4.0.15",
136 - "vue-tsc": "^3.1.5"
136 + "vitest": "^4.0.18",
137 + "vue-tsc": "^3.2.4"
138 },
139 "pnpm": {
140 "onlyBuiltDependencies": [
frontend/pnpm-lock.yaml
+2183 -2250
@@ -12,8 +12,8 @@ importers:
12 specifier: ^1.5.0
13 version: 1.5.0
14 '@codemirror/commands':
15 - specifier: ^6.10.0
16 - version: 6.10.0
15 + specifier: ^6.10.2
16 + version: 6.10.2
17 '@codemirror/lang-javascript':
18 specifier: ^6.2.4
19 version: 6.2.4
@@ -21,17 +21,17 @@ importers:
21 specifier: ^6.1.0
22 version: 6.1.0
23 '@codemirror/lint':
24 - specifier: ^6.9.2
25 - version: 6.9.2
24 + specifier: ^6.9.4
25 + version: 6.9.4
26 '@codemirror/theme-one-dark':
27 specifier: ^6.1.3
28 version: 6.1.3
29 '@codemirror/view':
30 - specifier: ^6.38.8
31 - version: 6.38.8
30 + specifier: ^6.39.14
31 + version: 6.39.14
32 '@f3ve/vue-markdown-it':
33 specifier: ^0.2.3
34 - version: 0.2.3(vue@3.5.25(typescript@5.9.3))
34 + version: 0.2.3(vue@3.5.28(typescript@5.9.3))
35 '@fontsource/jetbrains-mono':
36 specifier: ^5.2.8
37 version: 5.2.8
@@ -45,8 +45,8 @@ importers:
45 specifier: ^2.0.1
46 version: 2.0.1
47 '@shikijs/markdown-it':
48 - specifier: ^3.19.0
49 - version: 3.19.0
48 + specifier: ^3.22.0
49 + version: 3.22.0
50 '@singulio/app-auth-search':
51 specifier: ^0.0.3
52 version: 0.0.3
@@ -54,14 +54,14 @@ importers:
54 specifier: ^5.60.17
55 version: 5.60.17
56 '@vueuse/core':
57 - specifier: ^14.1.0
58 - version: 14.1.0(vue@3.5.25(typescript@5.9.3))
57 + specifier: ^14.2.1
58 + version: 14.2.1(vue@3.5.28(typescript@5.9.3))
59 '@vueuse/motion':
60 specifier: ^3.0.3
61 - version: 3.0.3(vue@3.5.25(typescript@5.9.3))
61 + version: 3.0.3(vue@3.5.28(typescript@5.9.3))
62 axios:
63 - specifier: ^1.13.2
64 - version: 1.13.2(debug@4.4.3)
63 + specifier: ^1.13.5
64 + version: 1.13.5(debug@4.4.3)
65 bytes:
66 specifier: ^3.1.2
67 version: 3.1.2
@@ -81,8 +81,8 @@ importers:
81 specifier: ^6.0.0
82 version: 6.0.0
83 fast-xml-parser:
84 - specifier: ^5.3.2
85 - version: 5.3.2
84 + specifier: ^5.3.6
85 + version: 5.3.6
86 file-saver:
87 specifier: ^2.0.5
88 version: 2.0.5
@@ -96,14 +96,14 @@ importers:
96 specifier: ^0.8.3
97 version: 0.8.3
98 lodash:
99 - specifier: ^4.17.21
100 - version: 4.17.21
99 + specifier: ^4.17.23
100 + version: 4.17.23
101 mitt:
102 specifier: ^3.0.1
103 version: 3.0.1
104 naive-ui:
105 specifier: ^2.43.2
106 - version: 2.43.2(vue@3.5.25(typescript@5.9.3))
106 + version: 2.43.2(vue@3.5.28(typescript@5.9.3))
107 nanoid:
108 specifier: ^5.1.6
109 version: 5.1.6
@@ -112,74 +112,74 @@ importers:
112 version: 5.3.0
113 pinia:
114 specifier: ^3.0.4
115 - version: 3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
115 + version: 3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))
116 pinia-plugin-persistedstate:
117 specifier: ^4.7.1
118 - version: 4.7.1(@nuxt/kit@3.20.0)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)))
118 + version: 4.7.1(@nuxt/kit@3.21.1)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))
119 secure-ls:
120 specifier: ^2.0.0
121 version: 2.0.0
122 shiki:
123 - specifier: ^3.19.0
124 - version: 3.19.0
123 + specifier: ^3.22.0
124 + version: 3.22.0
125 thememirror:
126 specifier: ^2.0.1
127 - version: 2.0.1(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)
127 + version: 2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.14)
128 validator:
129 - specifier: ^13.15.23
130 - version: 13.15.23
129 + specifier: ^13.15.26
130 + version: 13.15.26
131 vue:
132 - specifier: ^3.5.25
133 - version: 3.5.25(typescript@5.9.3)
132 + specifier: ^3.5.28
133 + version: 3.5.28(typescript@5.9.3)
134 vue-advanced-cropper:
135 specifier: ^2.8.9
136 - version: 2.8.9(vue@3.5.25(typescript@5.9.3))
136 + version: 2.8.9(vue@3.5.28(typescript@5.9.3))
137 vue-codemirror:
138 specifier: ^6.1.1
139 - version: 6.1.1(codemirror@6.0.2)(vue@3.5.25(typescript@5.9.3))
139 + version: 6.1.1(codemirror@6.0.2)(vue@3.5.28(typescript@5.9.3))
140 vue-highlight-words:
141 specifier: ^3.0.1
142 - version: 3.0.1(vue@3.5.25(typescript@5.9.3))
142 + version: 3.0.1(vue@3.5.28(typescript@5.9.3))
143 vue-i18n:
144 - specifier: ^11.2.2
145 - version: 11.2.2(vue@3.5.25(typescript@5.9.3))
144 + specifier: ^11.2.8
145 + version: 11.2.8(vue@3.5.28(typescript@5.9.3))
146 vue-router:
147 - specifier: ^4.6.3
148 - version: 4.6.3(vue@3.5.25(typescript@5.9.3))
147 + specifier: ^5.0.2
148 + version: 5.0.2(@vue/compiler-sfc@3.5.28)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(vue@3.5.28(typescript@5.9.3))
149 vue-sjv:
150 specifier: ^0.0.6
151 - version: 0.0.6(vue@3.5.25(typescript@5.9.3))
151 + version: 0.0.6(vue@3.5.28(typescript@5.9.3))
152 vue3-apexcharts:
153 specifier: ^1.10.0
154 - version: 1.10.0(apexcharts@5.3.5)(vue@3.5.25(typescript@5.9.3))
154 + version: 1.10.0(apexcharts@5.5.1)(vue@3.5.28(typescript@5.9.3))
155 vue3-marquee:
156 specifier: ^4.2.2
157 - version: 4.2.2(vue@3.5.25(typescript@5.9.3))
157 + version: 4.2.2(vue@3.5.28(typescript@5.9.3))
158 vuedraggable:
159 specifier: ^4.1.0
160 - version: 4.1.0(vue@3.5.25(typescript@5.9.3))
160 + version: 4.1.0(vue@3.5.28(typescript@5.9.3))
161 xmllint:
162 specifier: ^0.1.1
163 version: 0.1.1
164 xmllint-wasm:
165 specifier: ^5.1.0
166 - version: 5.1.0(@types/node@24.10.1)
166 + version: 5.1.0(@types/node@25.2.3)
167 devDependencies:
168 '@antfu/eslint-config':
169 - specifier: ~6.4.2
170 - version: 6.4.2(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.15(@types/node@24.10.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))
169 + specifier: ~7.4.3
170 + version: 7.4.3(@vue/compiler-sfc@3.5.28)(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
171 '@clack/prompts':
172 - specifier: ^0.11.0
173 - version: 0.11.0
172 + specifier: ^1.0.1
173 + version: 1.0.1
174 '@iconify/vue':
175 specifier: ^5.0.0
176 - version: 5.0.0(vue@3.5.25(typescript@5.9.3))
176 + version: 5.0.0(vue@3.5.28(typescript@5.9.3))
177 '@tailwindcss/vite':
178 - specifier: ^4.1.17
179 - version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))
178 + specifier: ^4.1.18
179 + version: 4.1.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
180 '@tsconfig/node20':
181 - specifier: ^20.1.8
182 - version: 20.1.8
181 + specifier: ^20.1.9
182 + version: 20.1.9
183 '@types/bytes':
184 specifier: ^3.1.5
185 version: 3.1.5
@@ -193,185 +193,191 @@ importers:
193 specifier: ^27.0.0
194 version: 27.0.0
195 '@types/lodash':
196 - specifier: ^4.17.21
197 - version: 4.17.21
196 + specifier: ^4.17.23
197 + version: 4.17.23
198 '@types/markdown-it':
199 specifier: ^14.1.2
200 version: 14.1.2
201 '@types/node':
202 - specifier: ^24.10.1
203 - version: 24.10.1
202 + specifier: ^25.2.3
203 + version: 25.2.3
204 '@types/validator':
205 specifier: ^13.15.10
206 version: 13.15.10
207 '@vitejs/plugin-vue':
208 - specifier: ^6.0.2
209 - version: 6.0.2(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))
208 + specifier: ^6.0.4
209 + version: 6.0.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
210 '@vitejs/plugin-vue-jsx':
211 - specifier: ^5.1.2
212 - version: 5.1.2(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))
211 + specifier: ^5.1.4
212 + version: 5.1.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
213 '@vue/test-utils':
214 specifier: ^2.4.6
215 version: 2.4.6
216 '@vue/tsconfig':
217 - specifier: ~0.7.0
218 - version: 0.7.0(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
217 + specifier: ~0.8.1
218 + version: 0.8.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))
219 + baseline-browser-mapping:
220 + specifier: ^2.9.19
221 + version: 2.9.19
222 cypress:
220 - specifier: ^15.7.1
221 - version: 15.7.1
223 + specifier: ^15.10.0
224 + version: 15.10.0
225 depcheck:
226 specifier: ^1.4.7
227 version: 1.4.7
228 eslint:
226 - specifier: ~9.39.1
227 - version: 9.39.1(jiti@2.6.1)
229 + specifier: ~10.0.0
230 + version: 10.0.0(jiti@2.6.1)
231 flourite:
232 specifier: ^1.3.0
233 version: 1.3.0
234 fs-extra:
232 - specifier: ^11.3.2
233 - version: 11.3.2
235 + specifier: ^11.3.3
236 + version: 11.3.3
237 jsdom:
235 - specifier: ^27.2.0
236 - version: 27.2.0
238 + specifier: ^28.1.0
239 + version: 28.1.0
240 npm-run-all2:
241 specifier: ^8.0.4
242 version: 8.0.4
243 prettier:
241 - specifier: ^3.7.4
242 - version: 3.7.4
244 + specifier: ^3.8.1
245 + version: 3.8.1
246 prettier-plugin-tailwindcss:
247 specifier: ^0.7.2
245 - version: 0.7.2(prettier@3.7.4)
248 + version: 0.7.2(prettier@3.8.1)
249 sass:
247 - specifier: ^1.94.2
248 - version: 1.94.2
250 + specifier: ^1.97.3
251 + version: 1.97.3
252 start-server-and-test:
253 specifier: ^2.1.3
254 version: 2.1.3
255 tailwindcss:
253 - specifier: ^4.1.17
254 - version: 4.1.17
256 + specifier: ^4.1.18
257 + version: 4.1.18
258 taze:
259 specifier: ^19.9.2
260 version: 19.9.2
261 type-fest:
259 - specifier: ^5.3.0
260 - version: 5.3.0
262 + specifier: ^5.4.4
263 + version: 5.4.4
264 typescript:
265 specifier: ~5.9.3
266 version: 5.9.3
267 vite:
265 - specifier: ^7.2.6
266 - version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2)
268 + specifier: ^7.3.1
269 + version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
270 vite-bundle-visualizer:
271 specifier: ^1.2.1
269 - version: 1.2.1(rollup@4.52.5)
272 + version: 1.2.1(rollup@4.57.1)
273 vite-plugin-inspect:
274 specifier: ^11.3.3
272 - version: 11.3.3(@nuxt/kit@3.20.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))
275 + version: 11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
276 vite-plugin-vue-devtools:
274 - specifier: ^8.0.5
275 - version: 8.0.5(@nuxt/kit@3.20.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))
277 + specifier: ^8.0.6
278 + version: 8.0.6(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
279 vite-svg-loader:
280 specifier: ^5.1.0
278 - version: 5.1.0(vue@3.5.25(typescript@5.9.3))
281 + version: 5.1.0(vue@3.5.28(typescript@5.9.3))
282 vitest:
280 - specifier: ^4.0.15
281 - version: 4.0.15(@types/node@24.10.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2)
283 + specifier: ^4.0.18
284 + version: 4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
285 vue-tsc:
283 - specifier: ^3.1.5
284 - version: 3.1.5(typescript@5.9.3)
286 + specifier: ^3.2.4
287 + version: 3.2.4(typescript@5.9.3)
288 optionalDependencies:
289 '@rollup/rollup-linux-x64-gnu':
290 specifier: ^4.46.2
288 - version: 4.52.5
291 + version: 4.57.1
292 treemate:
293 specifier: ^0.3.11
294 version: 0.3.11
295 vueuc:
296 specifier: ^0.4.64
294 - version: 0.4.65(vue@3.5.25(typescript@5.9.3))
297 + version: 0.4.65(vue@3.5.28(typescript@5.9.3))
298
299 packages:
300
298 - '@acemir/cssom@0.9.24':
299 - resolution: {integrity: sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==}
301 + '@acemir/cssom@0.9.31':
302 + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==}
303
304 '@ajoelp/json-to-formdata@1.5.0':
305 resolution: {integrity: sha512-nrlfeTSL0X0dtx5r2KpzPiqLSIQquiiJjUKsQAKzWaCmO2QoYZCyb5ENZwF3YoffKronOCJr25mxaD8JRJmK8w==}
306
304 - '@algolia/abtesting@1.8.0':
305 - resolution: {integrity: sha512-Hb4BkGNnvgCj3F9XzqjiFTpA5IGkjOXwGAOV13qtc27l2qNF8X9rzSp1H5hu8XewlC0DzYtQtZZIOYzRZDyuXg==}
307 + '@algolia/abtesting@1.14.1':
308 + resolution: {integrity: sha512-Dkj0BgPiLAaim9sbQ97UKDFHJE/880wgStAM18U++NaJ/2Cws34J5731ovJifr6E3Pv4T2CqvMXf8qLCC417Ew==}
309 engines: {node: '>= 14.0.0'}
310
308 - '@algolia/client-abtesting@5.42.0':
309 - resolution: {integrity: sha512-JLyyG7bb7XOda+w/sp8ch7rEVy6LnWs3qtxr6VJJ2XIINqGsY6U+0L3aJ6QFliBRNUeEAr2QBDxSm8u9Sal5uA==}
311 + '@algolia/client-abtesting@5.48.1':
312 + resolution: {integrity: sha512-LV5qCJdj+/m9I+Aj91o+glYszrzd7CX6NgKaYdTOj4+tUYfbS62pwYgUfZprYNayhkQpVFcrW8x8ZlIHpS23Vw==}
313 engines: {node: '>= 14.0.0'}
314
312 - '@algolia/client-analytics@5.42.0':
313 - resolution: {integrity: sha512-SkCrvtZpdSWjNq9NGu/TtOg4TbzRuUToXlQqV6lLePa2s/WQlEyFw7QYjrz4itprWG9ASuH+StDlq7n49F2sBA==}
315 + '@algolia/client-analytics@5.48.1':
316 + resolution: {integrity: sha512-/AVoMqHhPm14CcHq7mwB+bUJbfCv+jrxlNvRjXAuO+TQa+V37N8k1b0ijaRBPdmSjULMd8KtJbQyUyabXOu6Kg==}
317 engines: {node: '>= 14.0.0'}
318
316 - '@algolia/client-common@5.42.0':
317 - resolution: {integrity: sha512-6iiFbm2tRn6B2OqFv9XDTcw5LdWPudiJWIbRk+fsTX+hkPrPm4e1/SbU+lEYBciPoaTShLkDbRge4UePEyCPMQ==}
319 + '@algolia/client-common@5.48.1':
320 + resolution: {integrity: sha512-VXO+qu2Ep6ota28ktvBm3sG53wUHS2n7bgLWmce5jTskdlCD0/JrV4tnBm1l7qpla1CeoQb8D7ShFhad+UoSOw==}
321 engines: {node: '>= 14.0.0'}
322
320 - '@algolia/client-insights@5.42.0':
321 - resolution: {integrity: sha512-iEokmw2k6FBa8g/TT7ClyEriaP/FUEmz3iczRoCklEHWSgoABMkaeYrxRXrA2yx76AN+gyZoC8FX0iCJ55dsOg==}
323 + '@algolia/client-insights@5.48.1':
324 + resolution: {integrity: sha512-zl+Qyb0nLg+Y5YvKp1Ij+u9OaPaKg2/EPzTwKNiVyOHnQJlFxmXyUZL1EInczAZsEY8hVpPCLtNfhMhfxluXKQ==}
325 engines: {node: '>= 14.0.0'}
326
324 - '@algolia/client-personalization@5.42.0':
325 - resolution: {integrity: sha512-ivVniRqX2ARd+jGvRHTxpWeOtO9VT+rK+OmiuRgkSunoTyxk0vjeDO7QkU7+lzBOXiYgakNjkZrBtIpW9c+muw==}
327 + '@algolia/client-personalization@5.48.1':
328 + resolution: {integrity: sha512-r89Qf9Oo9mKWQXumRu/1LtvVJAmEDpn8mHZMc485pRfQUMAwSSrsnaw1tQ3sszqzEgAr1c7rw6fjBI+zrAXTOw==}
329 engines: {node: '>= 14.0.0'}
330
328 - '@algolia/client-query-suggestions@5.42.0':
329 - resolution: {integrity: sha512-9+BIw6rerUfA+eLMIS2lF4mgoeBGTCIHiqb35PLn3699Rm3CaJXz03hChdwAWcA6SwGw0haYXYJa7LF0xI6EpA==}
331 + '@algolia/client-query-suggestions@5.48.1':
332 + resolution: {integrity: sha512-TPKNPKfghKG/bMSc7mQYD9HxHRUkBZA4q1PEmHgICaSeHQscGqL4wBrKkhfPlDV1uYBKW02pbFMUhsOt7p4ZpA==}
333 engines: {node: '>= 14.0.0'}
334
332 - '@algolia/client-search@5.42.0':
333 - resolution: {integrity: sha512-NZR7yyHj2WzK6D5X8gn+/KOxPdzYEXOqVdSaK/biU8QfYUpUuEA0sCWg/XlO05tPVEcJelF/oLrrNY3UjRbOww==}
335 + '@algolia/client-search@5.48.1':
336 + resolution: {integrity: sha512-4Fu7dnzQyQmMFknYwTiN/HxPbH4DyxvQ1m+IxpPp5oslOgz8m6PG5qhiGbqJzH4HiT1I58ecDiCAC716UyVA8Q==}
337 engines: {node: '>= 14.0.0'}
338
336 - '@algolia/ingestion@1.42.0':
337 - resolution: {integrity: sha512-MBkjRymf4BT6VOvMpJlg6kq8K+PkH9q+N+K4YMNdzTXlL40YwOa1wIWQ5LxP/Jhlz64kW5g9/oaMWY06Sy9dcw==}
339 + '@algolia/ingestion@1.48.1':
340 + resolution: {integrity: sha512-/RFq3TqtXDUUawwic/A9xylA2P3LDMO8dNhphHAUOU51b1ZLHrmZ6YYJm3df1APz7xLY1aht6okCQf+/vmrV9w==}
341 engines: {node: '>= 14.0.0'}
342
340 - '@algolia/monitoring@1.42.0':
341 - resolution: {integrity: sha512-kmLs7YfjT4cpr4FnhhRmnoSX4psh9KYZ9NAiWt/YcUV33m0B/Os5L4QId30zVXkOqAPAEpV5VbDPWep+/aoJdQ==}
343 + '@algolia/monitoring@1.48.1':
344 + resolution: {integrity: sha512-Of0jTeAZRyRhC7XzDSjJef0aBkgRcvRAaw0ooYRlOw57APii7lZdq+layuNdeL72BRq1snaJhoMMwkmLIpJScw==}
345 engines: {node: '>= 14.0.0'}
346
344 - '@algolia/recommend@5.42.0':
345 - resolution: {integrity: sha512-U5yZ8+Jj+A4ZC0IMfElpPcddQ9NCoawD1dKyWmjHP49nzN2Z4284IFVMAJWR6fq/0ddGf4OMjjYO9cnF8L+5tw==}
347 + '@algolia/recommend@5.48.1':
348 + resolution: {integrity: sha512-bE7JcpFXzxF5zHwj/vkl2eiCBvyR1zQ7aoUdO+GDXxGp0DGw7nI0p8Xj6u8VmRQ+RDuPcICFQcCwRIJT5tDJFw==}
349 engines: {node: '>= 14.0.0'}
350
348 - '@algolia/requester-browser-xhr@5.42.0':
349 - resolution: {integrity: sha512-EbuxgteaYBlKgc2Fs3JzoPIKAIaevAIwmv1F+fakaEXeibG4pkmVNsyTUjpOZIgJ1kXeqNvDrcjRb6g3vYBJ9A==}
351 + '@algolia/requester-browser-xhr@5.48.1':
352 + resolution: {integrity: sha512-MK3wZ2koLDnvH/AmqIF1EKbJlhRS5j74OZGkLpxI4rYvNi9Jn/C7vb5DytBnQ4KUWts7QsmbdwHkxY5txQHXVw==}
353 engines: {node: '>= 14.0.0'}
354
352 - '@algolia/requester-fetch@5.42.0':
353 - resolution: {integrity: sha512-4vnFvY5Q8QZL9eDNkywFLsk/eQCRBXCBpE8HWs8iUsFNHYoamiOxAeYMin0W/nszQj6abc+jNxMChHmejO+ftQ==}
355 + '@algolia/requester-fetch@5.48.1':
356 + resolution: {integrity: sha512-2oDT43Y5HWRSIQMPQI4tA/W+TN/N2tjggZCUsqQV440kxzzoPGsvv9QP1GhQ4CoDa+yn6ygUsGp6Dr+a9sPPSg==}
357 engines: {node: '>= 14.0.0'}
358
356 - '@algolia/requester-node-http@5.42.0':
357 - resolution: {integrity: sha512-gkLNpU+b1pCIwk1hKTJz2NWQPT8gsfGhQasnZ5QVv4jd79fKRL/1ikd86P0AzuIQs9tbbhlMwxsSTyJmlq502w==}
359 + '@algolia/requester-node-http@5.48.1':
360 + resolution: {integrity: sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==}
361 engines: {node: '>= 14.0.0'}
362
360 - '@antfu/eslint-config@6.4.2':
361 - resolution: {integrity: sha512-kj6tZEKXMu07pCBH9TjPyRsQf8oZCwd3I6RFycKz8gPP5/uALictvUJsEA9bXBqZvtqA8SUEwLPLhVRZTSUTWQ==}
363 + '@antfu/eslint-config@7.4.3':
364 + resolution: {integrity: sha512-qHOG2408wBz21x191n0Mm9r2q/PqMYul+YE+DUBqZMtJYUn+bd+Dh3g8LIkyItMJR+Xs9f9TKXJehZLRYxJlHg==}
365 hasBin: true
366 peerDependencies:
364 - '@eslint-react/eslint-plugin': ^2.0.1
367 + '@angular-eslint/eslint-plugin': ^21.1.0
368 + '@angular-eslint/eslint-plugin-template': ^21.1.0
369 + '@angular-eslint/template-parser': ^21.1.0
370 + '@eslint-react/eslint-plugin': ^2.11.0
371 '@next/eslint-plugin-next': '>=15.0.0'
372 '@prettier/plugin-xml': ^3.4.1
373 '@unocss/eslint-plugin': '>=0.50.0'
374 astro-eslint-parser: ^1.0.2
369 - eslint: ^9.10.0
375 + eslint: ^9.10.0 || ^10.0.0
376 eslint-plugin-astro: ^1.2.0
377 eslint-plugin-format: '>=0.1.0'
378 eslint-plugin-jsx-a11y: '>=6.10.2'
379 eslint-plugin-react-hooks: ^7.0.0
374 - eslint-plugin-react-refresh: ^0.4.19
380 + eslint-plugin-react-refresh: ^0.5.0
381 eslint-plugin-solid: ^0.14.3
382 eslint-plugin-svelte: '>=2.35.1'
383 eslint-plugin-vuejs-accessibility: ^2.4.1
@@ -379,6 +385,12 @@ packages:
385 prettier-plugin-slidev: ^1.0.5
386 svelte-eslint-parser: '>=0.37.0'
387 peerDependenciesMeta:
388 + '@angular-eslint/eslint-plugin':
389 + optional: true
390 + '@angular-eslint/eslint-plugin-template':
391 + optional: true
392 + '@angular-eslint/template-parser':
393 + optional: true
394 '@eslint-react/eslint-plugin':
395 optional: true
396 '@next/eslint-plugin-next':
@@ -420,41 +432,41 @@ packages:
432 engines: {node: '>=20'}
433 hasBin: true
434
423 - '@asamuzakjp/css-color@4.0.5':
424 - resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==}
435 + '@asamuzakjp/css-color@4.1.2':
436 + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==}
437
426 - '@asamuzakjp/dom-selector@6.7.4':
427 - resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==}
438 + '@asamuzakjp/dom-selector@6.8.1':
439 + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==}
440
441 '@asamuzakjp/nwsapi@2.3.9':
442 resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
443
432 - '@babel/code-frame@7.27.1':
433 - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
444 + '@babel/code-frame@7.29.0':
445 + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
446 engines: {node: '>=6.9.0'}
447
436 - '@babel/compat-data@7.28.5':
437 - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
448 + '@babel/compat-data@7.29.0':
449 + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
450 engines: {node: '>=6.9.0'}
451
440 - '@babel/core@7.28.5':
441 - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
452 + '@babel/core@7.29.0':
453 + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
454 engines: {node: '>=6.9.0'}
455
444 - '@babel/generator@7.28.5':
445 - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
456 + '@babel/generator@7.29.1':
457 + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
458 engines: {node: '>=6.9.0'}
459
460 '@babel/helper-annotate-as-pure@7.27.3':
461 resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
462 engines: {node: '>=6.9.0'}
463
452 - '@babel/helper-compilation-targets@7.27.2':
453 - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
464 + '@babel/helper-compilation-targets@7.28.6':
465 + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
466 engines: {node: '>=6.9.0'}
467
456 - '@babel/helper-create-class-features-plugin@7.28.5':
457 - resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==}
468 + '@babel/helper-create-class-features-plugin@7.28.6':
469 + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==}
470 engines: {node: '>=6.9.0'}
471 peerDependencies:
472 '@babel/core': ^7.0.0
@@ -467,12 +479,12 @@ packages:
479 resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==}
480 engines: {node: '>=6.9.0'}
481
470 - '@babel/helper-module-imports@7.27.1':
471 - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
482 + '@babel/helper-module-imports@7.28.6':
483 + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
484 engines: {node: '>=6.9.0'}
485
474 - '@babel/helper-module-transforms@7.28.3':
475 - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
486 + '@babel/helper-module-transforms@7.28.6':
487 + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
488 engines: {node: '>=6.9.0'}
489 peerDependencies:
490 '@babel/core': ^7.0.0
@@ -481,12 +493,12 @@ packages:
493 resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
494 engines: {node: '>=6.9.0'}
495
484 - '@babel/helper-plugin-utils@7.27.1':
485 - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
496 + '@babel/helper-plugin-utils@7.28.6':
497 + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
498 engines: {node: '>=6.9.0'}
499
488 - '@babel/helper-replace-supers@7.27.1':
489 - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==}
500 + '@babel/helper-replace-supers@7.28.6':
501 + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==}
502 engines: {node: '>=6.9.0'}
503 peerDependencies:
504 '@babel/core': ^7.0.0
@@ -507,29 +519,29 @@ packages:
519 resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
520 engines: {node: '>=6.9.0'}
521
510 - '@babel/helpers@7.28.4':
511 - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
522 + '@babel/helpers@7.28.6':
523 + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
524 engines: {node: '>=6.9.0'}
525
514 - '@babel/parser@7.28.5':
515 - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
526 + '@babel/parser@7.29.0':
527 + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
528 engines: {node: '>=6.0.0'}
529 hasBin: true
530
519 - '@babel/plugin-proposal-decorators@7.28.0':
520 - resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==}
531 + '@babel/plugin-proposal-decorators@7.29.0':
532 + resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==}
533 engines: {node: '>=6.9.0'}
534 peerDependencies:
535 '@babel/core': ^7.0.0-0
536
525 - '@babel/plugin-syntax-decorators@7.27.1':
526 - resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==}
537 + '@babel/plugin-syntax-decorators@7.28.6':
538 + resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==}
539 engines: {node: '>=6.9.0'}
540 peerDependencies:
541 '@babel/core': ^7.0.0-0
542
531 - '@babel/plugin-syntax-import-attributes@7.27.1':
532 - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==}
543 + '@babel/plugin-syntax-import-attributes@7.28.6':
544 + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==}
545 engines: {node: '>=6.9.0'}
546 peerDependencies:
547 '@babel/core': ^7.0.0-0
@@ -539,47 +551,51 @@ packages:
551 peerDependencies:
552 '@babel/core': ^7.0.0-0
553
542 - '@babel/plugin-syntax-jsx@7.27.1':
543 - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
554 + '@babel/plugin-syntax-jsx@7.28.6':
555 + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==}
556 engines: {node: '>=6.9.0'}
557 peerDependencies:
558 '@babel/core': ^7.0.0-0
559
548 - '@babel/plugin-syntax-typescript@7.27.1':
549 - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
560 + '@babel/plugin-syntax-typescript@7.28.6':
561 + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==}
562 engines: {node: '>=6.9.0'}
563 peerDependencies:
564 '@babel/core': ^7.0.0-0
565
554 - '@babel/plugin-transform-typescript@7.28.5':
555 - resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==}
566 + '@babel/plugin-transform-typescript@7.28.6':
567 + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==}
568 engines: {node: '>=6.9.0'}
569 peerDependencies:
570 '@babel/core': ^7.0.0-0
571
560 - '@babel/template@7.27.2':
561 - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
572 + '@babel/template@7.28.6':
573 + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
574 engines: {node: '>=6.9.0'}
575
564 - '@babel/traverse@7.28.5':
565 - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
576 + '@babel/traverse@7.29.0':
577 + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
578 engines: {node: '>=6.9.0'}
579
568 - '@babel/types@7.28.5':
569 - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
580 + '@babel/types@7.29.0':
581 + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
582 engines: {node: '>=6.9.0'}
583
572 - '@clack/core@0.5.0':
573 - resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==}
584 + '@bramus/specificity@2.4.2':
585 + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
586 + hasBin: true
587 +
588 + '@clack/core@1.0.1':
589 + resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==}
590
575 - '@clack/prompts@0.11.0':
576 - resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==}
591 + '@clack/prompts@1.0.1':
592 + resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==}
593
578 - '@codemirror/autocomplete@6.19.1':
579 - resolution: {integrity: sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==}
594 + '@codemirror/autocomplete@6.20.0':
595 + resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
596
581 - '@codemirror/commands@6.10.0':
582 - resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==}
597 + '@codemirror/commands@6.10.2':
598 + resolution: {integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==}
599
600 '@codemirror/lang-javascript@6.2.4':
601 resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
@@ -587,23 +603,23 @@ packages:
603 '@codemirror/lang-xml@6.1.0':
604 resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==}
605
590 - '@codemirror/language@6.11.3':
591 - resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==}
606 + '@codemirror/language@6.12.1':
607 + resolution: {integrity: sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==}
608
593 - '@codemirror/lint@6.9.2':
594 - resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==}
609 + '@codemirror/lint@6.9.4':
610 + resolution: {integrity: sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==}
611
596 - '@codemirror/search@6.5.11':
597 - resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==}
612 + '@codemirror/search@6.6.0':
613 + resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==}
614
599 - '@codemirror/state@6.5.2':
600 - resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
615 + '@codemirror/state@6.5.4':
616 + resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==}
617
618 '@codemirror/theme-one-dark@6.1.3':
619 resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
620
605 - '@codemirror/view@6.38.8':
606 - resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==}
621 + '@codemirror/view@6.39.14':
622 + resolution: {integrity: sha512-WJcvgHm/6Q7dvGT0YFv/6PSkoc36QlR0VCESS6x9tGsnF1lWLmmYxOgX3HH6v8fo6AvSLgpcs+H0Olre6MKXlg==}
623
624 '@css-render/plugin-bem@0.15.14':
625 resolution: {integrity: sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==}
@@ -615,40 +631,39 @@ packages:
631 peerDependencies:
632 vue: ^3.0.11
633
618 - '@csstools/color-helpers@5.1.0':
619 - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
620 - engines: {node: '>=18'}
634 + '@csstools/color-helpers@6.0.1':
635 + resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==}
636 + engines: {node: '>=20.19.0'}
637
622 - '@csstools/css-calc@2.1.4':
623 - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
624 - engines: {node: '>=18'}
638 + '@csstools/css-calc@3.1.1':
639 + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==}
640 + engines: {node: '>=20.19.0'}
641 peerDependencies:
626 - '@csstools/css-parser-algorithms': ^3.0.5
627 - '@csstools/css-tokenizer': ^3.0.4
642 + '@csstools/css-parser-algorithms': ^4.0.0
643 + '@csstools/css-tokenizer': ^4.0.0
644
629 - '@csstools/css-color-parser@3.1.0':
630 - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
631 - engines: {node: '>=18'}
645 + '@csstools/css-color-parser@4.0.1':
646 + resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==}
647 + engines: {node: '>=20.19.0'}
648 peerDependencies:
633 - '@csstools/css-parser-algorithms': ^3.0.5
634 - '@csstools/css-tokenizer': ^3.0.4
649 + '@csstools/css-parser-algorithms': ^4.0.0
650 + '@csstools/css-tokenizer': ^4.0.0
651
636 - '@csstools/css-parser-algorithms@3.0.5':
637 - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
638 - engines: {node: '>=18'}
652 + '@csstools/css-parser-algorithms@4.0.0':
653 + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
654 + engines: {node: '>=20.19.0'}
655 peerDependencies:
640 - '@csstools/css-tokenizer': ^3.0.4
656 + '@csstools/css-tokenizer': ^4.0.0
657
642 - '@csstools/css-syntax-patches-for-csstree@1.0.15':
643 - resolution: {integrity: sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==}
644 - engines: {node: '>=18'}
658 + '@csstools/css-syntax-patches-for-csstree@1.0.27':
659 + resolution: {integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==}
660
646 - '@csstools/css-tokenizer@3.0.4':
647 - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
648 - engines: {node: '>=18'}
661 + '@csstools/css-tokenizer@4.0.0':
662 + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
663 + engines: {node: '>=20.19.0'}
664
650 - '@cypress/request@3.0.9':
651 - resolution: {integrity: sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==}
665 + '@cypress/request@3.0.10':
666 + resolution: {integrity: sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ==}
667 engines: {node: '>= 6'}
668
669 '@cypress/xvfb@1.2.4':
@@ -657,182 +672,182 @@ packages:
672 '@emotion/hash@0.8.0':
673 resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
674
660 - '@es-joy/jsdoccomment@0.50.2':
661 - resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==}
662 - engines: {node: '>=18'}
663 -
664 - '@es-joy/jsdoccomment@0.76.0':
665 - resolution: {integrity: sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==}
675 + '@es-joy/jsdoccomment@0.78.0':
676 + resolution: {integrity: sha512-rQkU5u8hNAq2NVRzHnIUUvR6arbO0b6AOlvpTNS48CkiKSn/xtNfOzBK23JE4SiW89DgvU7GtxLVgV4Vn2HBAw==}
677 engines: {node: '>=20.11.0'}
678
679 + '@es-joy/jsdoccomment@0.84.0':
680 + resolution: {integrity: sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==}
681 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
682 +
683 '@es-joy/resolve.exports@1.2.0':
684 resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==}
685 engines: {node: '>=10'}
686
672 - '@esbuild/aix-ppc64@0.25.11':
673 - resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
687 + '@esbuild/aix-ppc64@0.27.3':
688 + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
689 engines: {node: '>=18'}
690 cpu: [ppc64]
691 os: [aix]
692
678 - '@esbuild/android-arm64@0.25.11':
679 - resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==}
693 + '@esbuild/android-arm64@0.27.3':
694 + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
695 engines: {node: '>=18'}
696 cpu: [arm64]
697 os: [android]
698
684 - '@esbuild/android-arm@0.25.11':
685 - resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==}
699 + '@esbuild/android-arm@0.27.3':
700 + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
701 engines: {node: '>=18'}
702 cpu: [arm]
703 os: [android]
704
690 - '@esbuild/android-x64@0.25.11':
691 - resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==}
705 + '@esbuild/android-x64@0.27.3':
706 + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
707 engines: {node: '>=18'}
708 cpu: [x64]
709 os: [android]
710
696 - '@esbuild/darwin-arm64@0.25.11':
697 - resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==}
711 + '@esbuild/darwin-arm64@0.27.3':
712 + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
713 engines: {node: '>=18'}
714 cpu: [arm64]
715 os: [darwin]
716
702 - '@esbuild/darwin-x64@0.25.11':
703 - resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==}
717 + '@esbuild/darwin-x64@0.27.3':
718 + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
719 engines: {node: '>=18'}
720 cpu: [x64]
721 os: [darwin]
722
708 - '@esbuild/freebsd-arm64@0.25.11':
709 - resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==}
723 + '@esbuild/freebsd-arm64@0.27.3':
724 + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
725 engines: {node: '>=18'}
726 cpu: [arm64]
727 os: [freebsd]
728
714 - '@esbuild/freebsd-x64@0.25.11':
715 - resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==}
729 + '@esbuild/freebsd-x64@0.27.3':
730 + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
731 engines: {node: '>=18'}
732 cpu: [x64]
733 os: [freebsd]
734
720 - '@esbuild/linux-arm64@0.25.11':
721 - resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==}
735 + '@esbuild/linux-arm64@0.27.3':
736 + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
737 engines: {node: '>=18'}
738 cpu: [arm64]
739 os: [linux]
740
726 - '@esbuild/linux-arm@0.25.11':
727 - resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==}
741 + '@esbuild/linux-arm@0.27.3':
742 + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
743 engines: {node: '>=18'}
744 cpu: [arm]
745 os: [linux]
746
732 - '@esbuild/linux-ia32@0.25.11':
733 - resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==}
747 + '@esbuild/linux-ia32@0.27.3':
748 + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
749 engines: {node: '>=18'}
750 cpu: [ia32]
751 os: [linux]
752
738 - '@esbuild/linux-loong64@0.25.11':
739 - resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==}
753 + '@esbuild/linux-loong64@0.27.3':
754 + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
755 engines: {node: '>=18'}
756 cpu: [loong64]
757 os: [linux]
758
744 - '@esbuild/linux-mips64el@0.25.11':
745 - resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==}
759 + '@esbuild/linux-mips64el@0.27.3':
760 + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
761 engines: {node: '>=18'}
762 cpu: [mips64el]
763 os: [linux]
764
750 - '@esbuild/linux-ppc64@0.25.11':
751 - resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==}
765 + '@esbuild/linux-ppc64@0.27.3':
766 + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
767 engines: {node: '>=18'}
768 cpu: [ppc64]
769 os: [linux]
770
756 - '@esbuild/linux-riscv64@0.25.11':
757 - resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==}
771 + '@esbuild/linux-riscv64@0.27.3':
772 + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
773 engines: {node: '>=18'}
774 cpu: [riscv64]
775 os: [linux]
776
762 - '@esbuild/linux-s390x@0.25.11':
763 - resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==}
777 + '@esbuild/linux-s390x@0.27.3':
778 + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
779 engines: {node: '>=18'}
780 cpu: [s390x]
781 os: [linux]
782
768 - '@esbuild/linux-x64@0.25.11':
769 - resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==}
783 + '@esbuild/linux-x64@0.27.3':
784 + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
785 engines: {node: '>=18'}
786 cpu: [x64]
787 os: [linux]
788
774 - '@esbuild/netbsd-arm64@0.25.11':
775 - resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==}
789 + '@esbuild/netbsd-arm64@0.27.3':
790 + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
791 engines: {node: '>=18'}
792 cpu: [arm64]
793 os: [netbsd]
794
780 - '@esbuild/netbsd-x64@0.25.11':
781 - resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==}
795 + '@esbuild/netbsd-x64@0.27.3':
796 + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
797 engines: {node: '>=18'}
798 cpu: [x64]
799 os: [netbsd]
800
786 - '@esbuild/openbsd-arm64@0.25.11':
787 - resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==}
801 + '@esbuild/openbsd-arm64@0.27.3':
802 + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
803 engines: {node: '>=18'}
804 cpu: [arm64]
805 os: [openbsd]
806
792 - '@esbuild/openbsd-x64@0.25.11':
793 - resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==}
807 + '@esbuild/openbsd-x64@0.27.3':
808 + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
809 engines: {node: '>=18'}
810 cpu: [x64]
811 os: [openbsd]
812
798 - '@esbuild/openharmony-arm64@0.25.11':
799 - resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==}
813 + '@esbuild/openharmony-arm64@0.27.3':
814 + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
815 engines: {node: '>=18'}
816 cpu: [arm64]
817 os: [openharmony]
818
804 - '@esbuild/sunos-x64@0.25.11':
805 - resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==}
819 + '@esbuild/sunos-x64@0.27.3':
820 + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
821 engines: {node: '>=18'}
822 cpu: [x64]
823 os: [sunos]
824
810 - '@esbuild/win32-arm64@0.25.11':
811 - resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==}
825 + '@esbuild/win32-arm64@0.27.3':
826 + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
827 engines: {node: '>=18'}
828 cpu: [arm64]
829 os: [win32]
830
816 - '@esbuild/win32-ia32@0.25.11':
817 - resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==}
831 + '@esbuild/win32-ia32@0.27.3':
832 + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
833 engines: {node: '>=18'}
834 cpu: [ia32]
835 os: [win32]
836
822 - '@esbuild/win32-x64@0.25.11':
823 - resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==}
837 + '@esbuild/win32-x64@0.27.3':
838 + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
839 engines: {node: '>=18'}
840 cpu: [x64]
841 os: [win32]
842
828 - '@eslint-community/eslint-plugin-eslint-comments@4.5.0':
829 - resolution: {integrity: sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg==}
843 + '@eslint-community/eslint-plugin-eslint-comments@4.6.0':
844 + resolution: {integrity: sha512-2EX2bBQq1ez++xz2o9tEeEQkyvfieWgUFMH4rtJJri2q0Azvhja3hZGXsjPXs31R4fQkZDtWzNDDK2zQn5UE5g==}
845 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
846 peerDependencies:
847 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
848
834 - '@eslint-community/eslint-utils@4.9.0':
835 - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
849 + '@eslint-community/eslint-utils@4.9.1':
850 + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
851 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
852 peerDependencies:
853 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
@@ -850,38 +865,47 @@ packages:
865 eslint:
866 optional: true
867
853 - '@eslint/config-array@0.21.1':
854 - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
855 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
868 + '@eslint/config-array@0.23.1':
869 + resolution: {integrity: sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==}
870 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
871
857 - '@eslint/config-helpers@0.4.2':
858 - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
859 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
872 + '@eslint/config-helpers@0.5.2':
873 + resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==}
874 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
875
876 '@eslint/core@0.17.0':
877 resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
878 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
879
865 - '@eslint/eslintrc@3.3.1':
866 - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
867 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
868 -
869 - '@eslint/js@9.39.1':
870 - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==}
871 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
880 + '@eslint/core@1.1.0':
881 + resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
882 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
883
884 '@eslint/markdown@7.5.1':
885 resolution: {integrity: sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ==}
886 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
887
877 - '@eslint/object-schema@2.1.7':
878 - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
879 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
888 + '@eslint/object-schema@3.0.1':
889 + resolution: {integrity: sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==}
890 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
891
892 '@eslint/plugin-kit@0.4.1':
893 resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
894 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
895
896 + '@eslint/plugin-kit@0.6.0':
897 + resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==}
898 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
899 +
900 + '@exodus/bytes@1.14.1':
901 + resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==}
902 + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
903 + peerDependencies:
904 + '@noble/hashes': ^1.8.0 || ^2.0.0
905 + peerDependenciesMeta:
906 + '@noble/hashes':
907 + optional: true
908 +
909 '@f3ve/vue-markdown-it@0.2.3':
910 resolution: {integrity: sha512-v0VNd7wb55kwsUUy3n6DLI9+0FYSG0PrCTD3bWuSRo6WS3OHD5wghh/aHzebVdsVkSBXfVpiEUlMA3DrxLs7Lw==}
911 peerDependencies:
@@ -909,8 +933,8 @@ packages:
933 '@hapi/pinpoint@2.0.1':
934 resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==}
935
912 - '@hapi/tlds@1.1.4':
913 - resolution: {integrity: sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==}
936 + '@hapi/tlds@1.1.5':
937 + resolution: {integrity: sha512-Vq/1gnIIsvFUpKlDdfrPd/ssHDpAyBP/baVukh3u2KSG2xoNjsnRNjQiPmuyPPGqsn1cqVWWhtZHfOBaLizFRQ==}
938 engines: {node: '>=14.0.0'}
939
940 '@hapi/topo@6.0.2':
@@ -940,22 +964,26 @@ packages:
964 peerDependencies:
965 vue: '>=3'
966
943 - '@intlify/core-base@11.2.2':
944 - resolution: {integrity: sha512-0mCTBOLKIqFUP3BzwuFW23hYEl9g/wby6uY//AC5hTgQfTsM2srCYF2/hYGp+a5DZ/HIFIgKkLJMzXTt30r0JQ==}
967 + '@intlify/core-base@11.2.8':
968 + resolution: {integrity: sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==}
969 engines: {node: '>= 16'}
970
947 - '@intlify/message-compiler@11.2.2':
948 - resolution: {integrity: sha512-XS2p8Ff5JxWsKhgfld4/MRQzZRQ85drMMPhb7Co6Be4ZOgqJX1DzcZt0IFgGTycgqL8rkYNwgnD443Q+TapOoA==}
971 + '@intlify/message-compiler@11.2.8':
972 + resolution: {integrity: sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==}
973 engines: {node: '>= 16'}
974
951 - '@intlify/shared@11.2.2':
952 - resolution: {integrity: sha512-OtCmyFpSXxNu/oET/aN6HtPCbZ01btXVd0f3w00YsHOb13Kverk1jzA2k47pAekM55qbUw421fvPF1yxZ+gicw==}
975 + '@intlify/shared@11.2.8':
976 + resolution: {integrity: sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==}
977 engines: {node: '>= 16'}
978
979 '@isaacs/cliui@8.0.2':
980 resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
981 engines: {node: '>=12'}
982
983 + '@isaacs/cliui@9.0.0':
984 + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
985 + engines: {node: '>=18'}
986 +
987 '@jridgewell/gen-mapping@0.3.13':
988 resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
989
@@ -975,8 +1003,8 @@ packages:
1003 '@juggle/resize-observer@3.4.0':
1004 resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
1005
978 - '@lezer/common@1.3.0':
979 - resolution: {integrity: sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==}
1006 + '@lezer/common@1.5.1':
1007 + resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
1008
1009 '@lezer/highlight@1.2.3':
1010 resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
@@ -984,8 +1012,8 @@ packages:
1012 '@lezer/javascript@1.5.4':
1013 resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==}
1014
987 - '@lezer/lr@1.4.2':
988 - resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
1015 + '@lezer/lr@1.4.8':
1016 + resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
1017
1018 '@lezer/xml@1.0.6':
1019 resolution: {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==}
@@ -996,105 +1024,99 @@ packages:
1024 '@microsoft/fetch-event-source@2.0.1':
1025 resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==}
1026
999 - '@nodelib/fs.scandir@2.1.5':
1000 - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
1001 - engines: {node: '>= 8'}
1002 -
1003 - '@nodelib/fs.stat@2.0.5':
1004 - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
1005 - engines: {node: '>= 8'}
1006 -
1007 - '@nodelib/fs.walk@1.2.8':
1008 - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
1009 - engines: {node: '>= 8'}
1010 -
1011 - '@nuxt/kit@3.20.0':
1012 - resolution: {integrity: sha512-EoF1Gf0SPj9vxgAIcGEH+a4PRLC7Dwsy21K6f5+POzylT8DgssN8zL5pwXC+X7OcfzBrwYFh7mM7phvh7ubgeg==}
1027 + '@nuxt/kit@3.21.1':
1028 + resolution: {integrity: sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg==}
1029 engines: {node: '>=18.12.0'}
1030
1031 '@one-ini/wasm@0.1.1':
1032 resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
1033
1018 - '@parcel/watcher-android-arm64@2.5.1':
1019 - resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
1034 + '@parcel/watcher-android-arm64@2.5.6':
1035 + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
1036 engines: {node: '>= 10.0.0'}
1037 cpu: [arm64]
1038 os: [android]
1039
1024 - '@parcel/watcher-darwin-arm64@2.5.1':
1025 - resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
1040 + '@parcel/watcher-darwin-arm64@2.5.6':
1041 + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
1042 engines: {node: '>= 10.0.0'}
1043 cpu: [arm64]
1044 os: [darwin]
1045
1030 - '@parcel/watcher-darwin-x64@2.5.1':
1031 - resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
1046 + '@parcel/watcher-darwin-x64@2.5.6':
1047 + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
1048 engines: {node: '>= 10.0.0'}
1049 cpu: [x64]
1050 os: [darwin]
1051
1036 - '@parcel/watcher-freebsd-x64@2.5.1':
1037 - resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
1052 + '@parcel/watcher-freebsd-x64@2.5.6':
1053 + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
1054 engines: {node: '>= 10.0.0'}
1055 cpu: [x64]
1056 os: [freebsd]
1057
1042 - '@parcel/watcher-linux-arm-glibc@2.5.1':
1043 - resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
1058 + '@parcel/watcher-linux-arm-glibc@2.5.6':
1059 + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
1060 engines: {node: '>= 10.0.0'}
1061 cpu: [arm]
1062 os: [linux]
1063 + libc: [glibc]
1064
1048 - '@parcel/watcher-linux-arm-musl@2.5.1':
1049 - resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
1065 + '@parcel/watcher-linux-arm-musl@2.5.6':
1066 + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
1067 engines: {node: '>= 10.0.0'}
1068 cpu: [arm]
1069 os: [linux]
1070 + libc: [musl]
1071
1054 - '@parcel/watcher-linux-arm64-glibc@2.5.1':
1055 - resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
1072 + '@parcel/watcher-linux-arm64-glibc@2.5.6':
1073 + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
1074 engines: {node: '>= 10.0.0'}
1075 cpu: [arm64]
1076 os: [linux]
1077 + libc: [glibc]
1078
1060 - '@parcel/watcher-linux-arm64-musl@2.5.1':
1061 - resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
1079 + '@parcel/watcher-linux-arm64-musl@2.5.6':
1080 + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
1081 engines: {node: '>= 10.0.0'}
1082 cpu: [arm64]
1083 os: [linux]
1084 + libc: [musl]
1085
1066 - '@parcel/watcher-linux-x64-glibc@2.5.1':
1067 - resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
1086 + '@parcel/watcher-linux-x64-glibc@2.5.6':
1087 + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
1088 engines: {node: '>= 10.0.0'}
1089 cpu: [x64]
1090 os: [linux]
1091 + libc: [glibc]
1092
1072 - '@parcel/watcher-linux-x64-musl@2.5.1':
1073 - resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
1093 + '@parcel/watcher-linux-x64-musl@2.5.6':
1094 + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
1095 engines: {node: '>= 10.0.0'}
1096 cpu: [x64]
1097 os: [linux]
1098 + libc: [musl]
1099
1078 - '@parcel/watcher-win32-arm64@2.5.1':
1079 - resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
1100 + '@parcel/watcher-win32-arm64@2.5.6':
1101 + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
1102 engines: {node: '>= 10.0.0'}
1103 cpu: [arm64]
1104 os: [win32]
1105
1084 - '@parcel/watcher-win32-ia32@2.5.1':
1085 - resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
1106 + '@parcel/watcher-win32-ia32@2.5.6':
1107 + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
1108 engines: {node: '>= 10.0.0'}
1109 cpu: [ia32]
1110 os: [win32]
1111
1090 - '@parcel/watcher-win32-x64@2.5.1':
1091 - resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
1112 + '@parcel/watcher-win32-x64@2.5.6':
1113 + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
1114 engines: {node: '>= 10.0.0'}
1115 cpu: [x64]
1116 os: [win32]
1117
1096 - '@parcel/watcher@2.5.1':
1097 - resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
1118 + '@parcel/watcher@2.5.6':
1119 + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
1120 engines: {node: '>= 10.0.0'}
1121
1122 '@pkgjs/parseargs@0.11.0':
@@ -1108,150 +1130,178 @@ packages:
1130 '@polka/url@1.0.0-next.29':
1131 resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
1132
1111 - '@quansync/fs@0.1.5':
1112 - resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==}
1133 + '@quansync/fs@1.0.0':
1134 + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
1135
1114 - '@rolldown/pluginutils@1.0.0-beta.50':
1115 - resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==}
1136 + '@rolldown/pluginutils@1.0.0-rc.2':
1137 + resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==}
1138
1117 - '@rolldown/pluginutils@1.0.0-beta.52':
1118 - resolution: {integrity: sha512-/L0htLJZbaZFL1g9OHOblTxbCYIGefErJjtYOwgl9ZqNx27P3L0SDfjhhHIss32gu5NWgnxuT2a2Hnnv6QGHKA==}
1139 + '@rolldown/pluginutils@1.0.0-rc.4':
1140 + resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==}
1141
1120 - '@rollup/rollup-android-arm-eabi@4.52.5':
1121 - resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==}
1142 + '@rollup/rollup-android-arm-eabi@4.57.1':
1143 + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==}
1144 cpu: [arm]
1145 os: [android]
1146
1125 - '@rollup/rollup-android-arm64@4.52.5':
1126 - resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==}
1147 + '@rollup/rollup-android-arm64@4.57.1':
1148 + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==}
1149 cpu: [arm64]
1150 os: [android]
1151
1130 - '@rollup/rollup-darwin-arm64@4.52.5':
1131 - resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==}
1152 + '@rollup/rollup-darwin-arm64@4.57.1':
1153 + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==}
1154 cpu: [arm64]
1155 os: [darwin]
1156
1135 - '@rollup/rollup-darwin-x64@4.52.5':
1136 - resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==}
1157 + '@rollup/rollup-darwin-x64@4.57.1':
1158 + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==}
1159 cpu: [x64]
1160 os: [darwin]
1161
1140 - '@rollup/rollup-freebsd-arm64@4.52.5':
1141 - resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==}
1162 + '@rollup/rollup-freebsd-arm64@4.57.1':
1163 + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==}
1164 cpu: [arm64]
1165 os: [freebsd]
1166
1145 - '@rollup/rollup-freebsd-x64@4.52.5':
1146 - resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==}
1167 + '@rollup/rollup-freebsd-x64@4.57.1':
1168 + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==}
1169 cpu: [x64]
1170 os: [freebsd]
1171
1150 - '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
1151 - resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
1172 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1':
1173 + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==}
1174 cpu: [arm]
1175 os: [linux]
1176 + libc: [glibc]
1177
1155 - '@rollup/rollup-linux-arm-musleabihf@4.52.5':
1156 - resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
1178 + '@rollup/rollup-linux-arm-musleabihf@4.57.1':
1179 + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==}
1180 cpu: [arm]
1181 os: [linux]
1182 + libc: [musl]
1183
1160 - '@rollup/rollup-linux-arm64-gnu@4.52.5':
1161 - resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
1184 + '@rollup/rollup-linux-arm64-gnu@4.57.1':
1185 + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==}
1186 cpu: [arm64]
1187 os: [linux]
1188 + libc: [glibc]
1189
1165 - '@rollup/rollup-linux-arm64-musl@4.52.5':
1166 - resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
1190 + '@rollup/rollup-linux-arm64-musl@4.57.1':
1191 + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==}
1192 cpu: [arm64]
1193 os: [linux]
1194 + libc: [musl]
1195
1170 - '@rollup/rollup-linux-loong64-gnu@4.52.5':
1171 - resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
1196 + '@rollup/rollup-linux-loong64-gnu@4.57.1':
1197 + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==}
1198 cpu: [loong64]
1199 os: [linux]
1200 + libc: [glibc]
1201
1175 - '@rollup/rollup-linux-ppc64-gnu@4.52.5':
1176 - resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
1202 + '@rollup/rollup-linux-loong64-musl@4.57.1':
1203 + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==}
1204 + cpu: [loong64]
1205 + os: [linux]
1206 + libc: [musl]
1207 +
1208 + '@rollup/rollup-linux-ppc64-gnu@4.57.1':
1209 + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==}
1210 cpu: [ppc64]
1211 os: [linux]
1212 + libc: [glibc]
1213
1180 - '@rollup/rollup-linux-riscv64-gnu@4.52.5':
1181 - resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
1214 + '@rollup/rollup-linux-ppc64-musl@4.57.1':
1215 + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==}
1216 + cpu: [ppc64]
1217 + os: [linux]
1218 + libc: [musl]
1219 +
1220 + '@rollup/rollup-linux-riscv64-gnu@4.57.1':
1221 + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==}
1222 cpu: [riscv64]
1223 os: [linux]
1224 + libc: [glibc]
1225
1185 - '@rollup/rollup-linux-riscv64-musl@4.52.5':
1186 - resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
1226 + '@rollup/rollup-linux-riscv64-musl@4.57.1':
1227 + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==}
1228 cpu: [riscv64]
1229 os: [linux]
1230 + libc: [musl]
1231
1190 - '@rollup/rollup-linux-s390x-gnu@4.52.5':
1191 - resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
1232 + '@rollup/rollup-linux-s390x-gnu@4.57.1':
1233 + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==}
1234 cpu: [s390x]
1235 os: [linux]
1236 + libc: [glibc]
1237
1195 - '@rollup/rollup-linux-x64-gnu@4.52.5':
1196 - resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
1238 + '@rollup/rollup-linux-x64-gnu@4.57.1':
1239 + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==}
1240 cpu: [x64]
1241 os: [linux]
1242 + libc: [glibc]
1243
1200 - '@rollup/rollup-linux-x64-musl@4.52.5':
1201 - resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
1244 + '@rollup/rollup-linux-x64-musl@4.57.1':
1245 + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==}
1246 cpu: [x64]
1247 os: [linux]
1248 + libc: [musl]
1249 +
1250 + '@rollup/rollup-openbsd-x64@4.57.1':
1251 + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==}
1252 + cpu: [x64]
1253 + os: [openbsd]
1254
1205 - '@rollup/rollup-openharmony-arm64@4.52.5':
1206 - resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
1255 + '@rollup/rollup-openharmony-arm64@4.57.1':
1256 + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==}
1257 cpu: [arm64]
1258 os: [openharmony]
1259
1210 - '@rollup/rollup-win32-arm64-msvc@4.52.5':
1211 - resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==}
1260 + '@rollup/rollup-win32-arm64-msvc@4.57.1':
1261 + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==}
1262 cpu: [arm64]
1263 os: [win32]
1264
1215 - '@rollup/rollup-win32-ia32-msvc@4.52.5':
1216 - resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==}
1265 + '@rollup/rollup-win32-ia32-msvc@4.57.1':
1266 + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==}
1267 cpu: [ia32]
1268 os: [win32]
1269
1220 - '@rollup/rollup-win32-x64-gnu@4.52.5':
1221 - resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==}
1270 + '@rollup/rollup-win32-x64-gnu@4.57.1':
1271 + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==}
1272 cpu: [x64]
1273 os: [win32]
1274
1225 - '@rollup/rollup-win32-x64-msvc@4.52.5':
1226 - resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==}
1275 + '@rollup/rollup-win32-x64-msvc@4.57.1':
1276 + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==}
1277 cpu: [x64]
1278 os: [win32]
1279
1230 - '@shikijs/core@3.19.0':
1231 - resolution: {integrity: sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==}
1280 + '@shikijs/core@3.22.0':
1281 + resolution: {integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==}
1282
1233 - '@shikijs/engine-javascript@3.19.0':
1234 - resolution: {integrity: sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ==}
1283 + '@shikijs/engine-javascript@3.22.0':
1284 + resolution: {integrity: sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==}
1285
1236 - '@shikijs/engine-oniguruma@3.19.0':
1237 - resolution: {integrity: sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==}
1286 + '@shikijs/engine-oniguruma@3.22.0':
1287 + resolution: {integrity: sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==}
1288
1239 - '@shikijs/langs@3.19.0':
1240 - resolution: {integrity: sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==}
1289 + '@shikijs/langs@3.22.0':
1290 + resolution: {integrity: sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==}
1291
1242 - '@shikijs/markdown-it@3.19.0':
1243 - resolution: {integrity: sha512-T9Lt3pPZoK5uu7t2/jQ6OzhwZ9FPBtZhHKSk62IdPaJkj2gVPeRWSJb825dpliSFvURp4JQvcInBkrJZYmhynQ==}
1292 + '@shikijs/markdown-it@3.22.0':
1293 + resolution: {integrity: sha512-l0DehGhJN+JkkzGwULVhE57S9o7zQA2lVfpEjcvpSkxTbhzTEuoKHjuQQUN0VNbYeyj4obZwB4CTVrMijvYaeg==}
1294 peerDependencies:
1295 markdown-it-async: ^2.2.0
1296 peerDependenciesMeta:
1297 markdown-it-async:
1298 optional: true
1299
1250 - '@shikijs/themes@3.19.0':
1251 - resolution: {integrity: sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==}
1300 + '@shikijs/themes@3.22.0':
1301 + resolution: {integrity: sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==}
1302
1253 - '@shikijs/types@3.19.0':
1254 - resolution: {integrity: sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==}
1303 + '@shikijs/types@3.22.0':
1304 + resolution: {integrity: sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==}
1305
1306 '@shikijs/vscode-textmate@10.0.2':
1307 resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
@@ -1263,99 +1313,78 @@ packages:
1313 '@singulio/app-auth-search@0.0.3':
1314 resolution: {integrity: sha512-4U/of6Gry5hal3CdZoMkrdfbSYza43ZcvlJoh4pJ1UCm16Vpegmw3MwmbrZHjg8wiFWe+f5WnX/WkgQpuuZnHw==}
1315
1266 - '@standard-schema/spec@1.0.0':
1267 - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
1316 + '@standard-schema/spec@1.1.0':
1317 + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
1318
1269 - '@stylistic/eslint-plugin@5.6.1':
1270 - resolution: {integrity: sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==}
1319 + '@stylistic/eslint-plugin@5.8.0':
1320 + resolution: {integrity: sha512-WNPVF/FfBAjyi3OA7gok8swRiImNLKI4dmV3iK/GC/0xSJR7eCzBFsw9hLZVgb1+MYNLy7aDsjohxN1hA/FIfQ==}
1321 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1322 peerDependencies:
1323 eslint: '>=9.0.0'
1324
1275 - '@svgdotjs/svg.draggable.js@3.0.6':
1276 - resolution: {integrity: sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==}
1277 - peerDependencies:
1278 - '@svgdotjs/svg.js': ^3.2.4
1279 -
1280 - '@svgdotjs/svg.filter.js@3.0.9':
1281 - resolution: {integrity: sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==}
1282 - engines: {node: '>= 0.8.0'}
1283 -
1284 - '@svgdotjs/svg.js@3.2.5':
1285 - resolution: {integrity: sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==}
1286 -
1287 - '@svgdotjs/svg.resize.js@2.0.5':
1288 - resolution: {integrity: sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==}
1289 - engines: {node: '>= 14.18'}
1290 - peerDependencies:
1291 - '@svgdotjs/svg.js': ^3.2.4
1292 - '@svgdotjs/svg.select.js': ^4.0.1
1293 -
1294 - '@svgdotjs/svg.select.js@4.0.3':
1295 - resolution: {integrity: sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==}
1296 - engines: {node: '>= 14.18'}
1297 - peerDependencies:
1298 - '@svgdotjs/svg.js': ^3.2.4
1299 -
1300 - '@tailwindcss/node@4.1.17':
1301 - resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
1325 + '@tailwindcss/node@4.1.18':
1326 + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
1327
1303 - '@tailwindcss/oxide-android-arm64@4.1.17':
1304 - resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==}
1328 + '@tailwindcss/oxide-android-arm64@4.1.18':
1329 + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
1330 engines: {node: '>= 10'}
1331 cpu: [arm64]
1332 os: [android]
1333
1309 - '@tailwindcss/oxide-darwin-arm64@4.1.17':
1310 - resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==}
1334 + '@tailwindcss/oxide-darwin-arm64@4.1.18':
1335 + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
1336 engines: {node: '>= 10'}
1337 cpu: [arm64]
1338 os: [darwin]
1339
1315 - '@tailwindcss/oxide-darwin-x64@4.1.17':
1316 - resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==}
1340 + '@tailwindcss/oxide-darwin-x64@4.1.18':
1341 + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
1342 engines: {node: '>= 10'}
1343 cpu: [x64]
1344 os: [darwin]
1345
1321 - '@tailwindcss/oxide-freebsd-x64@4.1.17':
1322 - resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==}
1346 + '@tailwindcss/oxide-freebsd-x64@4.1.18':
1347 + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
1348 engines: {node: '>= 10'}
1349 cpu: [x64]
1350 os: [freebsd]
1351
1327 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
1328 - resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==}
1352 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
1353 + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
1354 engines: {node: '>= 10'}
1355 cpu: [arm]
1356 os: [linux]
1357
1333 - '@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
1334 - resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==}
1358 + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
1359 + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
1360 engines: {node: '>= 10'}
1361 cpu: [arm64]
1362 os: [linux]
1363 + libc: [glibc]
1364
1339 - '@tailwindcss/oxide-linux-arm64-musl@4.1.17':
1340 - resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
1365 + '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
1366 + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
1367 engines: {node: '>= 10'}
1368 cpu: [arm64]
1369 os: [linux]
1370 + libc: [musl]
1371
1345 - '@tailwindcss/oxide-linux-x64-gnu@4.1.17':
1346 - resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
1372 + '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
1373 + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
1374 engines: {node: '>= 10'}
1375 cpu: [x64]
1376 os: [linux]
1377 + libc: [glibc]
1378
1351 - '@tailwindcss/oxide-linux-x64-musl@4.1.17':
1352 - resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
1379 + '@tailwindcss/oxide-linux-x64-musl@4.1.18':
1380 + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
1381 engines: {node: '>= 10'}
1382 cpu: [x64]
1383 os: [linux]
1384 + libc: [musl]
1385
1357 - '@tailwindcss/oxide-wasm32-wasi@4.1.17':
1358 - resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
1386 + '@tailwindcss/oxide-wasm32-wasi@4.1.18':
1387 + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
1388 engines: {node: '>=14.0.0'}
1389 cpu: [wasm32]
1390 bundledDependencies:
@@ -1366,24 +1395,24 @@ packages:
1395 - '@emnapi/wasi-threads'
1396 - tslib
1397
1369 - '@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
1370 - resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==}
1398 + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
1399 + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
1400 engines: {node: '>= 10'}
1401 cpu: [arm64]
1402 os: [win32]
1403
1375 - '@tailwindcss/oxide-win32-x64-msvc@4.1.17':
1376 - resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==}
1404 + '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
1405 + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
1406 engines: {node: '>= 10'}
1407 cpu: [x64]
1408 os: [win32]
1409
1381 - '@tailwindcss/oxide@4.1.17':
1382 - resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==}
1410 + '@tailwindcss/oxide@4.1.18':
1411 + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
1412 engines: {node: '>= 10'}
1413
1385 - '@tailwindcss/vite@4.1.17':
1386 - resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==}
1414 + '@tailwindcss/vite@4.1.18':
1415 + resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==}
1416 peerDependencies:
1417 vite: ^5.2.0 || ^6 || ^7
1418
@@ -1391,8 +1420,8 @@ packages:
1420 resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
1421 engines: {node: '>=10.13.0'}
1422
1394 - '@tsconfig/node20@20.1.8':
1395 - resolution: {integrity: sha512-Em+IdPfByIzWRRpqWL4Z7ArLHZGxmc36BxE3jCz9nBFSm+5aLaPMZyjwu4yetvyKXeogWcxik4L1jB5JTWfw7A==}
1423 + '@tsconfig/node20@20.1.9':
1424 + resolution: {integrity: sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==}
1425
1426 '@types/bytes@3.1.5':
1427 resolution: {integrity: sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ==}
@@ -1409,6 +1438,9 @@ packages:
1438 '@types/deep-eql@4.0.2':
1439 resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
1440
1441 + '@types/esrecurse@4.3.1':
1442 + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
1443 +
1444 '@types/estree@1.0.8':
1445 resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
1446
@@ -1430,8 +1462,8 @@ packages:
1462 '@types/jsonfile@6.1.4':
1463 resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
1464
1433 - '@types/katex@0.16.7':
1434 - resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
1465 + '@types/katex@0.16.8':
1466 + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==}
1467
1468 '@types/linkify-it@5.0.0':
1469 resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
@@ -1439,8 +1471,8 @@ packages:
1471 '@types/lodash-es@4.17.12':
1472 resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
1473
1442 - '@types/lodash@4.17.21':
1443 - resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==}
1474 + '@types/lodash@4.17.23':
1475 + resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==}
1476
1477 '@types/markdown-it@14.1.2':
1478 resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
@@ -1457,8 +1489,8 @@ packages:
1489 '@types/ms@2.1.0':
1490 resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
1491
1460 - '@types/node@24.10.1':
1461 - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
1492 + '@types/node@25.2.3':
1493 + resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==}
1494
1495 '@types/parse-json@4.0.2':
1496 resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -1490,121 +1522,84 @@ packages:
1522 '@types/yauzl@2.10.3':
1523 resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
1524
1493 - '@typescript-eslint/eslint-plugin@8.48.1':
1494 - resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==}
1525 + '@typescript-eslint/eslint-plugin@8.56.0':
1526 + resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==}
1527 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1528 peerDependencies:
1497 - '@typescript-eslint/parser': ^8.48.1
1498 - eslint: ^8.57.0 || ^9.0.0
1529 + '@typescript-eslint/parser': ^8.56.0
1530 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1531 typescript: '>=4.8.4 <6.0.0'
1532
1501 - '@typescript-eslint/parser@8.48.1':
1502 - resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==}
1533 + '@typescript-eslint/parser@8.56.0':
1534 + resolution: {integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==}
1535 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1536 peerDependencies:
1505 - eslint: ^8.57.0 || ^9.0.0
1537 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1538 typescript: '>=4.8.4 <6.0.0'
1539
1508 - '@typescript-eslint/project-service@8.46.2':
1509 - resolution: {integrity: sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==}
1540 + '@typescript-eslint/project-service@8.56.0':
1541 + resolution: {integrity: sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==}
1542 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1543 peerDependencies:
1544 typescript: '>=4.8.4 <6.0.0'
1545
1514 - '@typescript-eslint/project-service@8.48.1':
1515 - resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==}
1516 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1517 - peerDependencies:
1518 - typescript: '>=4.8.4 <6.0.0'
1519 -
1520 - '@typescript-eslint/scope-manager@8.46.2':
1521 - resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==}
1522 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1523 -
1524 - '@typescript-eslint/scope-manager@8.48.1':
1525 - resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==}
1546 + '@typescript-eslint/scope-manager@8.56.0':
1547 + resolution: {integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==}
1548 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1549
1528 - '@typescript-eslint/tsconfig-utils@8.46.2':
1529 - resolution: {integrity: sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==}
1550 + '@typescript-eslint/tsconfig-utils@8.56.0':
1551 + resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==}
1552 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1553 peerDependencies:
1554 typescript: '>=4.8.4 <6.0.0'
1555
1534 - '@typescript-eslint/tsconfig-utils@8.48.1':
1535 - resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==}
1556 + '@typescript-eslint/type-utils@8.56.0':
1557 + resolution: {integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==}
1558 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1559 peerDependencies:
1560 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1561 typescript: '>=4.8.4 <6.0.0'
1562
1540 - '@typescript-eslint/type-utils@8.48.1':
1541 - resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==}
1563 + '@typescript-eslint/types@8.56.0':
1564 + resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==}
1565 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1543 - peerDependencies:
1544 - eslint: ^8.57.0 || ^9.0.0
1545 - typescript: '>=4.8.4 <6.0.0'
1546 -
1547 - '@typescript-eslint/types@8.46.2':
1548 - resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==}
1549 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1550 -
1551 - '@typescript-eslint/types@8.48.1':
1552 - resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==}
1553 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1554 -
1555 - '@typescript-eslint/typescript-estree@8.46.2':
1556 - resolution: {integrity: sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==}
1557 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1558 - peerDependencies:
1559 - typescript: '>=4.8.4 <6.0.0'
1560 -
1561 - '@typescript-eslint/typescript-estree@8.48.1':
1562 - resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==}
1563 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1564 - peerDependencies:
1565 - typescript: '>=4.8.4 <6.0.0'
1566
1567 - '@typescript-eslint/utils@8.46.2':
1568 - resolution: {integrity: sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==}
1567 + '@typescript-eslint/typescript-estree@8.56.0':
1568 + resolution: {integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==}
1569 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1570 peerDependencies:
1571 - eslint: ^8.57.0 || ^9.0.0
1571 typescript: '>=4.8.4 <6.0.0'
1572
1574 - '@typescript-eslint/utils@8.48.1':
1575 - resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==}
1573 + '@typescript-eslint/utils@8.56.0':
1574 + resolution: {integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==}
1575 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1576 peerDependencies:
1578 - eslint: ^8.57.0 || ^9.0.0
1577 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1578 typescript: '>=4.8.4 <6.0.0'
1579
1581 - '@typescript-eslint/visitor-keys@8.46.2':
1582 - resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
1583 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1584 -
1585 - '@typescript-eslint/visitor-keys@8.48.1':
1586 - resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==}
1580 + '@typescript-eslint/visitor-keys@8.56.0':
1581 + resolution: {integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==}
1582 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1583
1584 '@ungap/structured-clone@1.3.0':
1585 resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
1586
1592 - '@vitejs/plugin-vue-jsx@5.1.2':
1593 - resolution: {integrity: sha512-3a2BOryRjG/Iih87x87YXz5c8nw27eSlHytvSKYfp8ZIsp5+FgFQoKeA7k2PnqWpjJrv6AoVTMnvmuKUXb771A==}
1587 + '@vitejs/plugin-vue-jsx@5.1.4':
1588 + resolution: {integrity: sha512-70LmoVk9riR7qc4W2CpjsbNMWTPnuZb9dpFKX1emru0yP57nsc9k8nhLA6U93ngQapv5VDIUq2JatNfLbBIkrA==}
1589 engines: {node: ^20.19.0 || >=22.12.0}
1590 peerDependencies:
1596 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0
1591 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
1592 vue: ^3.0.0
1593
1599 - '@vitejs/plugin-vue@6.0.2':
1600 - resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==}
1594 + '@vitejs/plugin-vue@6.0.4':
1595 + resolution: {integrity: sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==}
1596 engines: {node: ^20.19.0 || >=22.12.0}
1597 peerDependencies:
1603 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0
1598 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
1599 vue: ^3.2.25
1600
1606 - '@vitest/eslint-plugin@1.5.1':
1607 - resolution: {integrity: sha512-t49CNERe/YadnLn90NTTKJLKzs99xBkXElcoUTLodG6j1G0Q7jy3mXqqiHd3N5aryG2KkgOg4UAoGwgwSrZqKQ==}
1601 + '@vitest/eslint-plugin@1.6.9':
1602 + resolution: {integrity: sha512-9WfPx1OwJ19QLCSRLkqVO7//1WcWnK3fE/3fJhKMAmDe8+9G4rB47xCNIIeCq3FdEzkIoLTfDlwDlPBaUTMhow==}
1603 engines: {node: '>=18'}
1604 peerDependencies:
1605 eslint: '>=8.57.0'
@@ -1616,11 +1611,11 @@ packages:
1611 vitest:
1612 optional: true
1613
1619 - '@vitest/expect@4.0.15':
1620 - resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==}
1614 + '@vitest/expect@4.0.18':
1615 + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
1616
1622 - '@vitest/mocker@4.0.15':
1623 - resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==}
1617 + '@vitest/mocker@4.0.18':
1618 + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==}
1619 peerDependencies:
1620 msw: ^2.4.9
1621 vite: ^6.0.0 || ^7.0.0-0
@@ -1630,29 +1625,38 @@ packages:
1625 vite:
1626 optional: true
1627
1633 - '@vitest/pretty-format@4.0.15':
1634 - resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==}
1628 + '@vitest/pretty-format@4.0.18':
1629 + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
1630 +
1631 + '@vitest/runner@4.0.18':
1632 + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
1633
1636 - '@vitest/runner@4.0.15':
1637 - resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==}
1634 + '@vitest/snapshot@4.0.18':
1635 + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==}
1636
1639 - '@vitest/snapshot@4.0.15':
1640 - resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==}
1637 + '@vitest/spy@4.0.18':
1638 + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==}
1639
1642 - '@vitest/spy@4.0.15':
1643 - resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==}
1640 + '@vitest/utils@4.0.18':
1641 + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
1642
1645 - '@vitest/utils@4.0.15':
1646 - resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==}
1643 + '@volar/language-core@2.4.27':
1644 + resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==}
1645
1648 - '@volar/language-core@2.4.23':
1649 - resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==}
1646 + '@volar/source-map@2.4.27':
1647 + resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==}
1648
1651 - '@volar/source-map@2.4.23':
1652 - resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==}
1649 + '@volar/typescript@2.4.27':
1650 + resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==}
1651
1654 - '@volar/typescript@2.4.23':
1655 - resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==}
1652 + '@vue-macros/common@3.1.2':
1653 + resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==}
1654 + engines: {node: '>=20.19.0'}
1655 + peerDependencies:
1656 + vue: ^2.7.0 || ^3.2.25
1657 + peerDependenciesMeta:
1658 + vue:
1659 + optional: true
1660
1661 '@vue/babel-helper-vue-transform-on@1.5.0':
1662 resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==}
@@ -1686,86 +1690,69 @@ packages:
1690 peerDependencies:
1691 '@babel/core': ^7.0.0-0
1692
1689 - '@vue/compiler-core@3.5.22':
1690 - resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==}
1691 -
1692 - '@vue/compiler-core@3.5.25':
1693 - resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==}
1694 -
1695 - '@vue/compiler-dom@3.5.22':
1696 - resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==}
1693 + '@vue/compiler-core@3.5.28':
1694 + resolution: {integrity: sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==}
1695
1698 - '@vue/compiler-dom@3.5.25':
1699 - resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==}
1696 + '@vue/compiler-dom@3.5.28':
1697 + resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==}
1698
1701 - '@vue/compiler-sfc@3.5.22':
1702 - resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==}
1699 + '@vue/compiler-sfc@3.5.28':
1700 + resolution: {integrity: sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==}
1701
1704 - '@vue/compiler-sfc@3.5.25':
1705 - resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==}
1706 -
1707 - '@vue/compiler-ssr@3.5.22':
1708 - resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==}
1709 -
1710 - '@vue/compiler-ssr@3.5.25':
1711 - resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==}
1702 + '@vue/compiler-ssr@3.5.28':
1703 + resolution: {integrity: sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==}
1704
1705 '@vue/devtools-api@6.6.4':
1706 resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
1707
1716 - '@vue/devtools-api@7.7.7':
1717 - resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==}
1708 + '@vue/devtools-api@7.7.9':
1709 + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
1710 +
1711 + '@vue/devtools-api@8.0.6':
1712 + resolution: {integrity: sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA==}
1713
1719 - '@vue/devtools-core@8.0.5':
1720 - resolution: {integrity: sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==}
1714 + '@vue/devtools-core@8.0.6':
1715 + resolution: {integrity: sha512-fN7iVtpSQQdtMORWwVZ1JiIAKriinhD+lCHqPw9Rr252ae2TczILEmW0zcAZifPW8HfYcbFkn+h7Wv6kQQCayw==}
1716 peerDependencies:
1717 vue: ^3.0.0
1718
1724 - '@vue/devtools-kit@7.7.7':
1725 - resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==}
1719 + '@vue/devtools-kit@7.7.9':
1720 + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==}
1721
1727 - '@vue/devtools-kit@8.0.5':
1728 - resolution: {integrity: sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==}
1722 + '@vue/devtools-kit@8.0.6':
1723 + resolution: {integrity: sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw==}
1724
1730 - '@vue/devtools-shared@7.7.7':
1731 - resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==}
1725 + '@vue/devtools-shared@7.7.9':
1726 + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
1727
1733 - '@vue/devtools-shared@8.0.5':
1734 - resolution: {integrity: sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==}
1728 + '@vue/devtools-shared@8.0.6':
1729 + resolution: {integrity: sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg==}
1730
1736 - '@vue/language-core@3.1.5':
1737 - resolution: {integrity: sha512-FMcqyzWN+sYBeqRMWPGT2QY0mUasZMVIuHvmb5NT3eeqPrbHBYtCP8JWEUCDCgM+Zr62uuWY/qoeBrPrzfa78w==}
1738 - peerDependencies:
1739 - typescript: '*'
1740 - peerDependenciesMeta:
1741 - typescript:
1742 - optional: true
1731 + '@vue/language-core@3.2.4':
1732 + resolution: {integrity: sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==}
1733
1744 - '@vue/reactivity@3.5.25':
1745 - resolution: {integrity: sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==}
1734 + '@vue/reactivity@3.5.28':
1735 + resolution: {integrity: sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==}
1736
1747 - '@vue/runtime-core@3.5.25':
1748 - resolution: {integrity: sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==}
1737 + '@vue/runtime-core@3.5.28':
1738 + resolution: {integrity: sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==}
1739
1750 - '@vue/runtime-dom@3.5.25':
1751 - resolution: {integrity: sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==}
1740 + '@vue/runtime-dom@3.5.28':
1741 + resolution: {integrity: sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==}
1742
1753 - '@vue/server-renderer@3.5.25':
1754 - resolution: {integrity: sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==}
1743 + '@vue/server-renderer@3.5.28':
1744 + resolution: {integrity: sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==}
1745 peerDependencies:
1756 - vue: 3.5.25
1757 -
1758 - '@vue/shared@3.5.22':
1759 - resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==}
1746 + vue: 3.5.28
1747
1761 - '@vue/shared@3.5.25':
1762 - resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==}
1748 + '@vue/shared@3.5.28':
1749 + resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==}
1750
1751 '@vue/test-utils@2.4.6':
1752 resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
1753
1767 - '@vue/tsconfig@0.7.0':
1768 - resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==}
1754 + '@vue/tsconfig@0.8.1':
1755 + resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==}
1756 peerDependencies:
1757 typescript: 5.x
1758 vue: ^3.4.0
@@ -1780,16 +1767,16 @@ packages:
1767 peerDependencies:
1768 vue: ^3.5.0
1769
1783 - '@vueuse/core@14.1.0':
1784 - resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
1770 + '@vueuse/core@14.2.1':
1771 + resolution: {integrity: sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==}
1772 peerDependencies:
1773 vue: ^3.5.0
1774
1775 '@vueuse/metadata@13.9.0':
1776 resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
1777
1791 - '@vueuse/metadata@14.1.0':
1792 - resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
1778 + '@vueuse/metadata@14.2.1':
1779 + resolution: {integrity: sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==}
1780
1781 '@vueuse/motion@3.0.3':
1782 resolution: {integrity: sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==}
@@ -1801,8 +1788,8 @@ packages:
1788 peerDependencies:
1789 vue: ^3.5.0
1790
1804 - '@vueuse/shared@14.1.0':
1805 - resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
1791 + '@vueuse/shared@14.2.1':
1792 + resolution: {integrity: sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==}
1793 peerDependencies:
1794 vue: ^3.5.0
1795
@@ -1834,12 +1821,12 @@ packages:
1821 ajv@6.12.6:
1822 resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
1823
1837 - algoliasearch@5.42.0:
1838 - resolution: {integrity: sha512-X5+PtWc9EJIPafT/cj8ZG+6IU3cjRRnlHGtqMHK/9gsiupQbAyYlH5y7qt/FtsAhfX5AICHffZy69ZAsVrxWkQ==}
1824 + algoliasearch@5.48.1:
1825 + resolution: {integrity: sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==}
1826 engines: {node: '>= 14.0.0'}
1827
1841 - alien-signals@3.0.3:
1842 - resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==}
1828 + alien-signals@3.1.2:
1829 + resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
1830
1831 ansi-colors@4.1.3:
1832 resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
@@ -1869,8 +1856,8 @@ packages:
1856 resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
1857 engines: {node: '>=14'}
1858
1872 - apexcharts@5.3.5:
1873 - resolution: {integrity: sha512-I04DY/WBZbJgJD2uixeV5EzyiL+J5LgKQXEu8rctqAwyRmKv44aDVeofJoLdTJe3ao4r2KEQfCgtVzXn6pqirg==}
1859 + apexcharts@5.5.1:
1860 + resolution: {integrity: sha512-fIJSzME9V0Ksfzk4x8CGMcK6HGLaRonxL7V7+7gHl4alAQBm4O/7D/3bM4F/MLmOakhkOs/RZbwYwLJDbAFzGw==}
1861
1862 arch@2.2.0:
1863 resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
@@ -1911,6 +1898,14 @@ packages:
1898 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
1899 engines: {node: '>=12'}
1900
1901 + ast-kit@2.2.0:
1902 + resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==}
1903 + engines: {node: '>=20.19.0'}
1904 +
1905 + ast-walker-scope@0.8.3:
1906 + resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
1907 + engines: {node: '>=20.19.0'}
1908 +
1909 astral-regex@2.0.0:
1910 resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
1911 engines: {node: '>=8'}
@@ -1931,17 +1926,21 @@ packages:
1926 aws4@1.13.2:
1927 resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
1928
1934 - axios@1.13.2:
1935 - resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
1929 + axios@1.13.5:
1930 + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==}
1931
1932 balanced-match@1.0.2:
1933 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
1934
1935 + balanced-match@4.0.2:
1936 + resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
1937 + engines: {node: 20 || >=22}
1938 +
1939 base64-js@1.5.1:
1940 resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
1941
1943 - baseline-browser-mapping@2.8.21:
1944 - resolution: {integrity: sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==}
1942 + baseline-browser-mapping@2.9.19:
1943 + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
1944 hasBin: true
1945
1946 bcrypt-pbkdf@1.0.2:
@@ -1950,8 +1949,8 @@ packages:
1949 bidi-js@1.0.3:
1950 resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
1951
1953 - birpc@2.6.1:
1954 - resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==}
1952 + birpc@2.9.0:
1953 + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
1954
1955 blob-util@2.0.2:
1956 resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==}
@@ -1968,12 +1967,16 @@ packages:
1967 brace-expansion@2.0.2:
1968 resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
1969
1970 + brace-expansion@5.0.2:
1971 + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==}
1972 + engines: {node: 20 || >=22}
1973 +
1974 braces@3.0.3:
1975 resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
1976 engines: {node: '>=8'}
1977
1975 - browserslist@4.27.0:
1976 - resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
1978 + browserslist@4.28.1:
1979 + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
1980 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
1981 hasBin: true
1982
@@ -1995,10 +1998,10 @@ packages:
1998 resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
1999 engines: {node: '>= 0.8'}
2000
1998 - c12@3.3.1:
1999 - resolution: {integrity: sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ==}
2001 + c12@3.3.3:
2002 + resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==}
2003 peerDependencies:
2001 - magicast: ^0.3.5
2004 + magicast: '*'
2005 peerDependenciesMeta:
2006 magicast:
2007 optional: true
@@ -2030,8 +2033,8 @@ packages:
2033 resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
2034 engines: {node: '>=10'}
2035
2033 - caniuse-lite@1.0.30001752:
2034 - resolution: {integrity: sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==}
2036 + caniuse-lite@1.0.30001770:
2037 + resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==}
2038
2039 caseless@0.12.0:
2040 resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
@@ -2039,8 +2042,8 @@ packages:
2042 ccount@2.0.1:
2043 resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
2044
2042 - chai@6.2.1:
2043 - resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==}
2045 + chai@6.2.2:
2046 + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
2047 engines: {node: '>=18'}
2048
2049 chalk@4.1.2:
@@ -2067,13 +2070,20 @@ packages:
2070 resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
2071 engines: {node: '>= 14.16.0'}
2072
2070 - ci-info@4.3.1:
2071 - resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==}
2073 + chokidar@5.0.0:
2074 + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
2075 + engines: {node: '>= 20.19.0'}
2076 +
2077 + ci-info@4.4.0:
2078 + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
2079 engines: {node: '>=8'}
2080
2081 citty@0.1.6:
2082 resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
2083
2084 + citty@0.2.1:
2085 + resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==}
2086 +
2087 classnames@2.5.1:
2088 resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
2089
@@ -2147,6 +2157,10 @@ packages:
2157 resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
2158 engines: {node: '>= 12.0.0'}
2159
2160 + comment-parser@1.4.5:
2161 + resolution: {integrity: sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==}
2162 + engines: {node: '>= 12.0.0'}
2163 +
2164 common-tags@1.8.2:
2165 resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
2166 engines: {node: '>=4.0.0'}
@@ -2157,8 +2171,8 @@ packages:
2171 confbox@0.1.8:
2172 resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
2173
2160 - confbox@0.2.2:
2161 - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
2174 + confbox@0.2.4:
2175 + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
2176
2177 config-chain@1.1.13:
2178 resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
@@ -2174,8 +2188,8 @@ packages:
2188 resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
2189 engines: {node: '>=18'}
2190
2177 - core-js-compat@3.46.0:
2178 - resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==}
2191 + core-js-compat@3.48.0:
2192 + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==}
2193
2194 core-util-is@1.0.2:
2195 resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
@@ -2225,18 +2239,18 @@ packages:
2239 resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
2240 engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
2241
2228 - cssstyle@5.3.3:
2229 - resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==}
2242 + cssstyle@6.0.1:
2243 + resolution: {integrity: sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==}
2244 engines: {node: '>=20'}
2245
2246 csstype@3.0.11:
2247 resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==}
2248
2235 - csstype@3.1.3:
2236 - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
2249 + csstype@3.2.3:
2250 + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
2251
2238 - cypress@15.7.1:
2239 - resolution: {integrity: sha512-U3sYnJ+Cnpgr6IPycxsznTg//mGVXfPGeGV+om7VQCyp5XyVkhG4oPr3X3hTq1+OB0Om0O5DxusYmt7cbvwqMQ==}
2252 + cypress@15.10.0:
2253 + resolution: {integrity: sha512-OtUh7OMrfEjKoXydlAD1CfG2BvKxIqgWGY4/RMjrqQ3BKGBo5JFKoYNH+Tpcj4xKxWH4XK0Xri+9y8WkxhYbqQ==}
2254 engines: {node: ^20.1.0 || ^22.0.0 || >=24.0.0}
2255 hasBin: true
2256
@@ -2244,9 +2258,9 @@ packages:
2258 resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
2259 engines: {node: '>=0.10'}
2260
2247 - data-urls@6.0.0:
2248 - resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==}
2249 - engines: {node: '>=20'}
2261 + data-urls@7.0.0:
2262 + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
2263 + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
2264
2265 date-fns-tz@3.2.0:
2266 resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==}
@@ -2282,18 +2296,18 @@ packages:
2296 decimal.js@10.6.0:
2297 resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
2298
2285 - decode-named-character-reference@1.2.0:
2286 - resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
2299 + decode-named-character-reference@1.3.0:
2300 + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
2301
2302 deep-is@0.1.4:
2303 resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
2304
2291 - default-browser-id@5.0.0:
2292 - resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
2305 + default-browser-id@5.0.1:
2306 + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
2307 engines: {node: '>=18'}
2308
2295 - default-browser@5.2.1:
2296 - resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
2309 + default-browser@5.5.0:
2310 + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
2311 engines: {node: '>=18'}
2312
2313 define-lazy-prop@2.0.0:
@@ -2330,11 +2344,6 @@ packages:
2344 resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==}
2345 engines: {node: '>=0.10.0'}
2346
2333 - detect-libc@1.0.3:
2334 - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
2335 - engines: {node: '>=0.10'}
2336 - hasBin: true
2337 -
2347 detect-libc@2.1.2:
2348 resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
2349 engines: {node: '>=8'}
@@ -2349,6 +2358,10 @@ packages:
2358 resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==}
2359 engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
2360
2361 + diff-sequences@29.6.3:
2362 + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
2363 + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
2364 +
2365 dom-serializer@2.0.0:
2366 resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
2367
@@ -2362,8 +2375,8 @@ packages:
2375 domutils@3.2.2:
2376 resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
2377
2365 - dotenv@17.2.3:
2366 - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
2378 + dotenv@17.3.1:
2379 + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
2380 engines: {node: '>=12'}
2381
2382 dunder-proto@1.0.1:
@@ -2390,8 +2403,8 @@ packages:
2403 engines: {node: '>=14'}
2404 hasBin: true
2405
2393 - electron-to-chromium@1.5.244:
2394 - resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==}
2406 + electron-to-chromium@1.5.286:
2407 + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
2408
2409 emoji-regex@8.0.0:
2410 resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -2406,8 +2419,8 @@ packages:
2419 end-of-stream@1.4.5:
2420 resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
2421
2409 - enhanced-resolve@5.18.3:
2410 - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
2422 + enhanced-resolve@5.19.0:
2423 + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==}
2424 engines: {node: '>=10.13.0'}
2425
2426 enquirer@2.4.1:
@@ -2422,6 +2435,10 @@ packages:
2435 resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
2436 engines: {node: '>=0.12'}
2437
2438 + entities@7.0.1:
2439 + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
2440 + engines: {node: '>=0.12'}
2441 +
2442 error-ex@1.3.4:
2443 resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
2444
@@ -2450,8 +2467,8 @@ packages:
2467 resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
2468 engines: {node: '>= 0.4'}
2469
2453 - esbuild@0.25.11:
2454 - resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==}
2470 + esbuild@0.27.3:
2471 + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
2472 engines: {node: '>=18'}
2473 hasBin: true
2474
@@ -2488,8 +2505,8 @@ packages:
2505 peerDependencies:
2506 eslint: ^9.5.0
2507
2491 - eslint-flat-config-utils@2.1.4:
2492 - resolution: {integrity: sha512-bEnmU5gqzS+4O+id9vrbP43vByjF+8KOs+QuuV4OlqAuXmnRW2zfI/Rza1fQvdihQ5h4DUo0NqFAiViD4mSrzQ==}
2508 + eslint-flat-config-utils@3.0.1:
2509 + resolution: {integrity: sha512-VMA3u86bLzNAwD/7DkLtQ9lolgIOx2Sj0kTMMnBvrvEz7w0rQj4aGCR+lqsqtld63gKiLyT4BnQZ3gmGDXtvjg==}
2510
2511 eslint-json-compat-utils@0.2.1:
2512 resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==}
@@ -2507,13 +2524,13 @@ packages:
2524 peerDependencies:
2525 eslint: '*'
2526
2510 - eslint-plugin-antfu@3.1.1:
2511 - resolution: {integrity: sha512-7Q+NhwLfHJFvopI2HBZbSxWXngTwBLKxW1AGXLr2lEGxcEIK/AsDs8pn8fvIizl5aZjBbVbVK5ujmMpBe4Tvdg==}
2527 + eslint-plugin-antfu@3.2.2:
2528 + resolution: {integrity: sha512-Qzixht2Dmd/pMbb5EnKqw2V8TiWHbotPlsORO8a+IzCLFwE0RxK8a9k4DCTFPzBwyxJzH+0m2Mn8IUGeGQkyUw==}
2529 peerDependencies:
2530 eslint: '*'
2531
2515 - eslint-plugin-command@3.3.1:
2516 - resolution: {integrity: sha512-fBVTXQ2y48TVLT0+4A6PFINp7GcdIailHAXbvPBixE7x+YpYnNQhFZxTdvnb+aWk+COgNebQKen/7m4dmgyWAw==}
2532 + eslint-plugin-command@3.4.0:
2533 + resolution: {integrity: sha512-EW4eg/a7TKEhG0s5IEti72kh3YOTlnhfFNuctq5WnB1fst37/IHTd5OkD+vnlRf3opTvUcSRihAateP6bT5ZcA==}
2534 peerDependencies:
2535 eslint: '*'
2536
@@ -2523,30 +2540,26 @@ packages:
2540 peerDependencies:
2541 eslint: '>=8'
2542
2526 - eslint-plugin-import-lite@0.3.0:
2527 - resolution: {integrity: sha512-dkNBAL6jcoCsXZsQ/Tt2yXmMDoNt5NaBh/U7yvccjiK8cai6Ay+MK77bMykmqQA2bTF6lngaLCDij6MTO3KkvA==}
2543 + eslint-plugin-import-lite@0.5.1:
2544 + resolution: {integrity: sha512-J+EqremfzXlB1WA/SKQxdZ2yeXgtpCorbcN0wS1KOTLnlSYiGR62MvAtM0zNWy1oDMjL/Lqkqf92Ahs1V5lyUg==}
2545 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2546 peerDependencies:
2547 eslint: '>=9.0.0'
2531 - typescript: '>=4.5'
2532 - peerDependenciesMeta:
2533 - typescript:
2534 - optional: true
2548
2536 - eslint-plugin-jsdoc@61.4.1:
2537 - resolution: {integrity: sha512-3c1QW/bV25sJ1MsIvsvW+EtLtN6yZMduw7LVQNVt72y2/5BbV5Pg5b//TE5T48LRUxoEQGaZJejCmcj3wCxBzw==}
2538 - engines: {node: '>=20.11.0'}
2549 + eslint-plugin-jsdoc@62.5.5:
2550 + resolution: {integrity: sha512-aRp0KVW26imgPqn17oSDnJdA3tlu+6D/xI/pqWzK5qDPQbldQ1Hsg84dYAsFyvGJhI+u/sGvzgGjMjlKQtUG/Q==}
2551 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2552 peerDependencies:
2553 eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
2554
2542 - eslint-plugin-jsonc@2.21.0:
2543 - resolution: {integrity: sha512-HttlxdNG5ly3YjP1cFMP62R4qKLxJURfBZo2gnMY+yQojZxkLyOpY1H1KRTKBmvQeSG9pIpSGEhDjE17vvYosg==}
2555 + eslint-plugin-jsonc@2.21.1:
2556 + resolution: {integrity: sha512-dbNR5iEnQeORwsK2WZzr3QaMtFCY3kKJVMRHPzUpKzMhmVy2zIpVgFDpX8MNoIdoqz6KCpCfOJavhfiSbZbN+w==}
2557 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
2558 peerDependencies:
2559 eslint: '>=6.0.0'
2560
2548 - eslint-plugin-n@17.23.1:
2549 - resolution: {integrity: sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==}
2561 + eslint-plugin-n@17.24.0:
2562 + resolution: {integrity: sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==}
2563 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2564 peerDependencies:
2565 eslint: '>=8.23.0'
@@ -2555,51 +2568,51 @@ packages:
2568 resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==}
2569 engines: {node: '>=5.0.0'}
2570
2558 - eslint-plugin-perfectionist@4.15.1:
2559 - resolution: {integrity: sha512-MHF0cBoOG0XyBf7G0EAFCuJJu4I18wy0zAoT1OHfx2o6EOx1EFTIzr2HGeuZa1kDcusoX0xJ9V7oZmaeFd773Q==}
2560 - engines: {node: ^18.0.0 || >=20.0.0}
2571 + eslint-plugin-perfectionist@5.5.0:
2572 + resolution: {integrity: sha512-lZX2KUpwOQf7J27gAg/6vt8ugdPULOLmelM8oDJPMbaN7P2zNNeyS9yxGSmJcKX0SF9qR/962l9RWM2Z5jpPzg==}
2573 + engines: {node: ^20.0.0 || >=22.0.0}
2574 peerDependencies:
2575 eslint: '>=8.45.0'
2576
2564 - eslint-plugin-pnpm@1.4.1:
2565 - resolution: {integrity: sha512-BVb2/cDN8YmgW+lu0bDeCP7a8CzE8CiKfztd46Ide71qbqR5uKD9TaD1W9FSRf36Pa8noEVuH73CAQ6rdgSu0w==}
2577 + eslint-plugin-pnpm@1.5.0:
2578 + resolution: {integrity: sha512-ayMo1GvrQ/sF/bz1aOAiH0jv9eAqU2Z+a1ycoWz/uFFK5NxQDq49BDKQtBumcOUBf2VHyiTW4a8u+6KVqoIWzQ==}
2579 peerDependencies:
2580 eslint: ^9.0.0
2581
2569 - eslint-plugin-regexp@2.10.0:
2570 - resolution: {integrity: sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==}
2571 - engines: {node: ^18 || >=20}
2582 + eslint-plugin-regexp@3.0.0:
2583 + resolution: {integrity: sha512-iW7hgAV8NOG6E2dz+VeKpq67YLQ9jaajOKYpoOSic2/q8y9BMdXBKkSR9gcMtbqEhNQzdW41E3wWzvhp8ExYwQ==}
2584 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2585 peerDependencies:
2573 - eslint: '>=8.44.0'
2586 + eslint: '>=9.38.0'
2587
2575 - eslint-plugin-toml@0.12.0:
2576 - resolution: {integrity: sha512-+/wVObA9DVhwZB1nG83D2OAQRrcQZXy+drqUnFJKymqnmbnbfg/UPmEMCKrJNcEboUGxUjYrJlgy+/Y930mURQ==}
2577 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
2588 + eslint-plugin-toml@1.0.4:
2589 + resolution: {integrity: sha512-oGjx1+rwN2YCS3xPCpnKX3Ei5vJngU+8LpdzovfIhWr+CoLIOpl7T2+/E00zaDHNMhCnuENlBqRCmXVvzUePjQ==}
2590 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2591 peerDependencies:
2579 - eslint: '>=6.0.0'
2592 + eslint: '>=9.38.0'
2593
2581 - eslint-plugin-unicorn@62.0.0:
2582 - resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==}
2594 + eslint-plugin-unicorn@63.0.0:
2595 + resolution: {integrity: sha512-Iqecl9118uQEXYh7adylgEmGfkn5es3/mlQTLLkd4pXkIk9CTGrAbeUux+YljSa2ohXCBmQQ0+Ej1kZaFgcfkA==}
2596 engines: {node: ^20.10.0 || >=21.0.0}
2597 peerDependencies:
2598 eslint: '>=9.38.0'
2599
2587 - eslint-plugin-unused-imports@4.3.0:
2588 - resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==}
2600 + eslint-plugin-unused-imports@4.4.1:
2601 + resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==}
2602 peerDependencies:
2603 '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
2591 - eslint: ^9.0.0 || ^8.0.0
2604 + eslint: ^10.0.0 || ^9.0.0 || ^8.0.0
2605 peerDependenciesMeta:
2606 '@typescript-eslint/eslint-plugin':
2607 optional: true
2608
2596 - eslint-plugin-vue@10.6.2:
2597 - resolution: {integrity: sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==}
2609 + eslint-plugin-vue@10.8.0:
2610 + resolution: {integrity: sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==}
2611 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2612 peerDependencies:
2613 '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
2614 '@typescript-eslint/parser': ^7.0.0 || ^8.0.0
2602 - eslint: ^8.57.0 || ^9.0.0
2615 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
2616 vue-eslint-parser: ^10.0.0
2617 peerDependenciesMeta:
2618 '@stylistic/eslint-plugin':
@@ -2607,11 +2620,11 @@ packages:
2620 '@typescript-eslint/parser':
2621 optional: true
2622
2610 - eslint-plugin-yml@1.19.0:
2611 - resolution: {integrity: sha512-S+4GbcCWksFKAvFJtf0vpdiCkZZvDJCV4Zsi9ahmYkYOYcf+LRqqzvzkb/ST7vTYV6sFwXOvawzYyL/jFT2nQA==}
2612 - engines: {node: ^14.17.0 || >=16.0.0}
2623 + eslint-plugin-yml@3.1.2:
2624 + resolution: {integrity: sha512-n9lxbFrNlGDLOSyIrEYkkYr7icbULMh66wwkIEluisq0lXSu1qVEEXM0g8MM8UQbtd9t1HMgN6bC+DaOe5dWdQ==}
2625 + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
2626 peerDependencies:
2614 - eslint: '>=6.0.0'
2627 + eslint: '>=9.38.0'
2628
2629 eslint-processor-vue-blocks@2.0.0:
2630 resolution: {integrity: sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q==}
@@ -2619,9 +2632,9 @@ packages:
2632 '@vue/compiler-sfc': ^3.3.0
2633 eslint: '>=9.0.0'
2634
2622 - eslint-scope@8.4.0:
2623 - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
2624 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2635 + eslint-scope@9.1.0:
2636 + resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==}
2637 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2638
2639 eslint-visitor-keys@3.4.3:
2640 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
@@ -2631,9 +2644,13 @@ packages:
2644 resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
2645 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2646
2634 - eslint@9.39.1:
2635 - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==}
2636 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2647 + eslint-visitor-keys@5.0.0:
2648 + resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==}
2649 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2650 +
2651 + eslint@10.0.0:
2652 + resolution: {integrity: sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==}
2653 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2654 hasBin: true
2655 peerDependencies:
2656 jiti: '*'
@@ -2645,6 +2662,10 @@ packages:
2662 resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
2663 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2664
2665 + espree@11.1.0:
2666 + resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==}
2667 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2668 +
2669 espree@9.6.1:
2670 resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
2671 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2654,8 +2675,8 @@ packages:
2675 engines: {node: '>=4'}
2676 hasBin: true
2677
2657 - esquery@1.6.0:
2658 - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
2678 + esquery@1.7.0:
2679 + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
2680 engines: {node: '>=0.10'}
2681
2682 esrecurse@4.3.0:
@@ -2701,12 +2722,12 @@ packages:
2722 resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==}
2723 engines: {node: '>=0.10.0'}
2724
2704 - expect-type@1.2.2:
2705 - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
2725 + expect-type@1.3.0:
2726 + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
2727 engines: {node: '>=12.0.0'}
2728
2708 - exsolve@1.0.7:
2709 - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
2729 + exsolve@1.0.8:
2730 + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
2731
2732 extend@3.0.2:
2733 resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -2723,23 +2744,16 @@ packages:
2744 fast-deep-equal@3.1.3:
2745 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
2746
2726 - fast-glob@3.3.3:
2727 - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
2728 - engines: {node: '>=8.6.0'}
2729 -
2747 fast-json-stable-stringify@2.1.0:
2748 resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
2749
2750 fast-levenshtein@2.0.6:
2751 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
2752
2736 - fast-xml-parser@5.3.2:
2737 - resolution: {integrity: sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA==}
2753 + fast-xml-parser@5.3.6:
2754 + resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==}
2755 hasBin: true
2756
2740 - fastq@1.19.1:
2741 - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
2742 -
2757 fault@2.0.1:
2758 resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
2759
@@ -2809,8 +2823,8 @@ packages:
2823 forever-agent@0.6.1:
2824 resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==}
2825
2812 - form-data@4.0.4:
2813 - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
2826 + form-data@4.0.5:
2827 + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
2828 engines: {node: '>= 6'}
2829
2830 format@0.2.2:
@@ -2823,8 +2837,8 @@ packages:
2837 from@0.1.7:
2838 resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
2839
2826 - fs-extra@11.3.2:
2827 - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==}
2840 + fs-extra@11.3.3:
2841 + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==}
2842 engines: {node: '>=14.14'}
2843
2844 fs-extra@9.1.0:
@@ -2866,8 +2880,8 @@ packages:
2880 resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
2881 engines: {node: '>=10'}
2882
2869 - get-tsconfig@4.13.0:
2870 - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
2883 + get-tsconfig@4.13.6:
2884 + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
2885
2886 getpass@0.1.7:
2887 resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==}
@@ -2879,16 +2893,13 @@ packages:
2893 github-slugger@2.0.0:
2894 resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
2895
2882 - glob-parent@5.1.2:
2883 - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
2884 - engines: {node: '>= 6'}
2885 -
2896 glob-parent@6.0.2:
2897 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
2898 engines: {node: '>=10.13.0'}
2899
2890 - glob@10.4.5:
2891 - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
2900 + glob@10.5.0:
2901 + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
2902 + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
2903 hasBin: true
2904
2905 global-dirs@3.0.1:
@@ -2903,10 +2914,6 @@ packages:
2914 resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==}
2915 engines: {node: '>=0.10.0'}
2916
2906 - globals@14.0.0:
2907 - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
2908 - engines: {node: '>=18'}
2909 -
2917 globals@15.15.0:
2918 resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
2919 engines: {node: '>=18'}
@@ -2915,6 +2922,10 @@ packages:
2922 resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
2923 engines: {node: '>=18'}
2924
2925 + globals@17.3.0:
2926 + resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==}
2927 + engines: {node: '>=18'}
2928 +
2929 globrex@0.1.2:
2930 resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
2931
@@ -2971,9 +2982,9 @@ packages:
2982 hookable@5.5.3:
2983 resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
2984
2974 - html-encoding-sniffer@4.0.0:
2975 - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
2976 - engines: {node: '>=18'}
2985 + html-encoding-sniffer@6.0.0:
2986 + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
2987 + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
2988
2989 html-entities@2.6.0:
2990 resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==}
@@ -3001,10 +3012,6 @@ packages:
3012 resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
3013 engines: {node: '>=10.17.0'}
3014
3004 - iconv-lite@0.6.3:
3005 - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
3006 - engines: {node: '>=0.10.0'}
3007 -
3015 ieee754@1.2.1:
3016 resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
3017
@@ -3125,16 +3132,16 @@ packages:
3132 resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
3133 engines: {node: '>=8'}
3134
3128 - is-wsl@3.1.0:
3129 - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
3135 + is-wsl@3.1.1:
3136 + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
3137 engines: {node: '>=16'}
3138
3139 isexe@2.0.0:
3140 resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
3141
3135 - isexe@3.1.1:
3136 - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
3137 - engines: {node: '>=16'}
3142 + isexe@3.1.5:
3143 + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
3144 + engines: {node: '>=18'}
3145
3146 isstream@0.1.2:
3147 resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
@@ -3142,12 +3149,16 @@ packages:
3149 jackspeak@3.4.3:
3150 resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
3151
3152 + jackspeak@4.2.3:
3153 + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
3154 + engines: {node: 20 || >=22}
3155 +
3156 jiti@2.6.1:
3157 resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
3158 hasBin: true
3159
3149 - joi@18.0.1:
3150 - resolution: {integrity: sha512-IiQpRyypSnLisQf3PwuN2eIHAsAIGZIrLZkd4zdvIar2bDyhM91ubRjy8a3eYablXsh9BeI/c7dmPYHca5qtoA==}
3160 + joi@18.0.2:
3161 + resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==}
3162 engines: {node: '>= 20'}
3163
3164 jose@6.1.3:
@@ -3168,31 +3179,23 @@ packages:
3179 js-tokens@4.0.0:
3180 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
3181
3171 - js-yaml@3.14.1:
3172 - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
3173 - hasBin: true
3174 -
3175 - js-yaml@4.1.0:
3176 - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
3182 + js-yaml@3.14.2:
3183 + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
3184 hasBin: true
3185
3186 jsbn@0.1.1:
3187 resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
3188
3182 - jsdoc-type-pratt-parser@4.1.0:
3183 - resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
3184 - engines: {node: '>=12.0.0'}
3185 -
3186 - jsdoc-type-pratt-parser@4.8.0:
3187 - resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==}
3188 - engines: {node: '>=12.0.0'}
3189 + jsdoc-type-pratt-parser@7.0.0:
3190 + resolution: {integrity: sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==}
3191 + engines: {node: '>=20.0.0'}
3192
3190 - jsdoc-type-pratt-parser@6.10.0:
3191 - resolution: {integrity: sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==}
3193 + jsdoc-type-pratt-parser@7.1.1:
3194 + resolution: {integrity: sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==}
3195 engines: {node: '>=20.0.0'}
3196
3194 - jsdom@27.2.0:
3195 - resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==}
3197 + jsdom@28.1.0:
3198 + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==}
3199 engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
3200 peerDependencies:
3201 canvas: ^3.0.0
@@ -3232,8 +3235,8 @@ packages:
3235 engines: {node: '>=6'}
3236 hasBin: true
3237
3235 - jsonc-eslint-parser@2.4.1:
3236 - resolution: {integrity: sha512-uuPNLJkKN8NXAlZlQ6kmUF9qO+T6Kyd7oV4+/7yy8Jz6+MZNyhPq8EdLpdfnPVzUC8qSf1b4j1azKaGnFsjmsw==}
3238 + jsonc-eslint-parser@2.4.2:
3239 + resolution: {integrity: sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==}
3240 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
3241
3242 jsonfile@6.2.0:
@@ -3250,8 +3253,8 @@ packages:
3253 resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
3254 engines: {node: '>= 8'}
3255
3253 - knitwork@1.2.0:
3254 - resolution: {integrity: sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg==}
3256 + knitwork@1.3.0:
3257 + resolution: {integrity: sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==}
3258
3259 kolorist@1.8.0:
3260 resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
@@ -3299,24 +3302,28 @@ packages:
3302 engines: {node: '>= 12.0.0'}
3303 cpu: [arm64]
3304 os: [linux]
3305 + libc: [glibc]
3306
3307 lightningcss-linux-arm64-musl@1.30.2:
3308 resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
3309 engines: {node: '>= 12.0.0'}
3310 cpu: [arm64]
3311 os: [linux]
3312 + libc: [musl]
3313
3314 lightningcss-linux-x64-gnu@1.30.2:
3315 resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
3316 engines: {node: '>= 12.0.0'}
3317 cpu: [x64]
3318 os: [linux]
3319 + libc: [glibc]
3320
3321 lightningcss-linux-x64-musl@1.30.2:
3322 resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
3323 engines: {node: '>= 12.0.0'}
3324 cpu: [x64]
3325 os: [linux]
3326 + libc: [musl]
3327
3328 lightningcss-win32-arm64-msvc@1.30.2:
3329 resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
@@ -3357,11 +3364,8 @@ packages:
3364 resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
3365 engines: {node: '>=10'}
3366
3360 - lodash-es@4.17.21:
3361 - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
3362 -
3363 - lodash.merge@4.6.2:
3364 - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
3367 + lodash-es@4.17.23:
3368 + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==}
3369
3370 lodash.once@4.1.1:
3371 resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
@@ -3369,6 +3373,9 @@ packages:
3373 lodash@4.17.21:
3374 resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
3375
3376 + lodash@4.17.23:
3377 + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
3378 +
3379 log-symbols@4.1.0:
3380 resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
3381 engines: {node: '>=10'}
@@ -3383,8 +3390,8 @@ packages:
3390 lru-cache@10.4.3:
3391 resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
3392
3386 - lru-cache@11.2.2:
3387 - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==}
3393 + lru-cache@11.2.6:
3394 + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==}
3395 engines: {node: 20 || >=22}
3396
3397 lru-cache@5.1.1:
@@ -3394,14 +3401,18 @@ packages:
3401 resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
3402 hasBin: true
3403
3404 + magic-string-ast@1.0.3:
3405 + resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==}
3406 + engines: {node: '>=20.19.0'}
3407 +
3408 magic-string@0.30.21:
3409 resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
3410
3411 map-stream@0.1.0:
3412 resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
3413
3403 - markdown-it@14.1.0:
3404 - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
3414 + markdown-it@14.1.1:
3415 + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
3416 hasBin: true
3417
3418 markdown-table@3.0.4:
@@ -3441,8 +3452,8 @@ packages:
3452 mdast-util-phrasing@4.1.0:
3453 resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
3454
3444 - mdast-util-to-hast@13.2.0:
3445 - resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
3455 + mdast-util-to-hast@13.2.1:
3456 + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
3457
3458 mdast-util-to-markdown@2.1.2:
3459 resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
@@ -3469,10 +3480,6 @@ packages:
3480 merge-stream@2.0.0:
3481 resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
3482
3472 - merge2@1.4.1:
3473 - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
3474 - engines: {node: '>= 8'}
3475 -
3483 micromark-core-commonmark@2.0.3:
3484 resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
3485
@@ -3580,6 +3587,10 @@ packages:
3587 resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
3588 engines: {node: '>=18'}
3589
3590 + minimatch@10.2.1:
3591 + resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==}
3592 + engines: {node: 20 || >=22}
3593 +
3594 minimatch@3.1.2:
3595 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
3596
@@ -3674,9 +3685,9 @@ packages:
3685 nth-check@2.1.1:
3686 resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
3687
3677 - nypm@0.6.2:
3678 - resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==}
3679 - engines: {node: ^14.16.0 || >=16.10.0}
3688 + nypm@0.6.5:
3689 + resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==}
3690 + engines: {node: '>=18'}
3691 hasBin: true
3692
3693 object-deep-merge@2.0.0:
@@ -3742,8 +3753,8 @@ packages:
3753 package-json-from-dist@1.0.1:
3754 resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
3755
3745 - package-manager-detector@1.5.0:
3746 - resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==}
3756 + package-manager-detector@1.6.0:
3757 + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
3758
3759 parent-module@1.0.1:
3760 resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
@@ -3811,8 +3822,8 @@ packages:
3822 perfect-debounce@1.0.0:
3823 resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
3824
3814 - perfect-debounce@2.0.0:
3815 - resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==}
3825 + perfect-debounce@2.1.0:
3826 + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==}
3827
3828 performance-now@2.1.0:
3829 resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
@@ -3873,11 +3884,8 @@ packages:
3884 resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
3885 engines: {node: '>=4'}
3886
3876 - pnpm-workspace-yaml@1.3.0:
3877 - resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==}
3878 -
3879 - pnpm-workspace-yaml@1.4.1:
3880 - resolution: {integrity: sha512-k3TFyIjKf4bJnZ/jnqTfdytcsZfoglnb26kTXP3E8pYYaPyvvqxTd7tL4aJ5Wotc8Oi/+YADYwdkOu3snFS+Hg==}
3887 + pnpm-workspace-yaml@1.5.0:
3888 + resolution: {integrity: sha512-PxdyJuFvq5B0qm3s9PaH/xOtSxrcvpBRr+BblhucpWjs8c79d4b7/cXhyY4AyHOHCnqklCYZTjfl0bT/mFVTRw==}
3889
3890 popmotion@11.0.5:
3891 resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
@@ -3949,8 +3957,8 @@ packages:
3957 prettier-plugin-svelte:
3958 optional: true
3959
3952 - prettier@3.7.4:
3953 - resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
3960 + prettier@3.8.1:
3961 + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
3962 engines: {node: '>=14'}
3963 hasBin: true
3964
@@ -3990,19 +3998,22 @@ packages:
3998 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
3999 engines: {node: '>=6'}
4000
3993 - qs@6.14.0:
3994 - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
4001 + qs@6.14.2:
4002 + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
4003 engines: {node: '>=0.6'}
4004
4005 quansync@0.2.11:
4006 resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
4007
4000 - queue-microtask@1.2.3:
4001 - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
4008 + quansync@1.0.0:
4009 + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}
4010
4011 rc9@2.1.2:
4012 resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
4013
4014 + rc9@3.0.0:
4015 + resolution: {integrity: sha512-MGOue0VqscKWQ104udASX/3GYDcKyPI4j4F8gu/jHHzglpmy9a/anZK3PNe8ug6aZFl+9GxLtdhe3kVZuMaQbA==}
4016 +
4017 read-package-json-fast@4.0.0:
4018 resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
4019 engines: {node: ^18.17.0 || >=20.5.0}
@@ -4015,6 +4026,10 @@ packages:
4026 resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
4027 engines: {node: '>= 14.18.0'}
4028
4029 + readdirp@5.0.0:
4030 + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
4031 + engines: {node: '>= 20.19.0'}
4032 +
4033 refa@0.12.1:
4034 resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
4035 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -4025,8 +4040,8 @@ packages:
4040 regex-utilities@2.3.0:
4041 resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
4042
4028 - regex@6.0.1:
4029 - resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
4043 + regex@6.1.0:
4044 + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==}
4045
4046 regexp-ast-analysis@0.7.1:
4047 resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
@@ -4086,10 +4101,6 @@ packages:
4101 resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
4102 engines: {node: '>=18'}
4103
4089 - reusify@1.1.0:
4090 - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
4091 - engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
4092 -
4104 rfdc@1.4.1:
4105 resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
4106
@@ -4106,8 +4117,8 @@ packages:
4117 rollup:
4118 optional: true
4119
4109 - rollup@4.52.5:
4110 - resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
4120 + rollup@4.57.1:
4121 + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==}
4122 engines: {node: '>=18.0.0', npm: '>=8.0.0'}
4123 hasBin: true
4124
@@ -4115,9 +4126,6 @@ packages:
4126 resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
4127 engines: {node: '>=18'}
4128
4118 - run-parallel@1.2.0:
4119 - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
4120 -
4129 rxjs@7.8.2:
4130 resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
4131
@@ -4127,8 +4135,8 @@ packages:
4135 safer-buffer@2.1.2:
4136 resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
4137
4130 - sass@1.94.2:
4131 - resolution: {integrity: sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==}
4138 + sass@1.97.3:
4139 + resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==}
4140 engines: {node: '>=14.0.0'}
4141 hasBin: true
4142
@@ -4157,8 +4165,8 @@ packages:
4165 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
4166 hasBin: true
4167
4160 - semver@7.7.3:
4161 - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
4168 + semver@7.7.4:
4169 + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
4170 engines: {node: '>=10'}
4171 hasBin: true
4172
@@ -4174,8 +4182,8 @@ packages:
4182 resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
4183 engines: {node: '>= 0.4'}
4184
4177 - shiki@3.19.0:
4178 - resolution: {integrity: sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==}
4185 + shiki@3.22.0:
4186 + resolution: {integrity: sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==}
4187
4188 side-channel-list@1.0.0:
4189 resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
@@ -4297,12 +4305,8 @@ packages:
4305 resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==}
4306 engines: {node: '>=12'}
4307
4300 - strip-json-comments@3.1.1:
4301 - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
4302 - engines: {node: '>=8'}
4303 -
4304 - strnum@2.1.1:
4305 - resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==}
4308 + strnum@2.1.2:
4309 + resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==}
4310
4311 style-mod@4.1.3:
4312 resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
@@ -4310,8 +4314,8 @@ packages:
4314 style-value-types@5.1.2:
4315 resolution: {integrity: sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==}
4316
4313 - superjson@2.2.5:
4314 - resolution: {integrity: sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==}
4317 + superjson@2.2.6:
4318 + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
4319 engines: {node: '>=16'}
4320
4321 supports-color@7.2.0:
@@ -4334,12 +4338,12 @@ packages:
4338 symbol-tree@3.2.4:
4339 resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
4340
4337 - synckit@0.11.11:
4338 - resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
4341 + synckit@0.11.12:
4342 + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
4343 engines: {node: ^14.18.0 || >=16.0.0}
4344
4341 - systeminformation@5.27.7:
4342 - resolution: {integrity: sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==}
4345 + systeminformation@5.31.0:
4346 + resolution: {integrity: sha512-z5pjzvC8UnQJ/iu34z+mo3lAeMzTGdArjPQoG5uPyV5XY4BY+M6ZcRTl4XnZqudz6sP713LhWMKv6e0kGFGCgQ==}
4347 engines: {node: '>=8.0.0'}
4348 os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
4349 hasBin: true
@@ -4348,8 +4352,8 @@ packages:
4352 resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
4353 engines: {node: '>=20'}
4354
4351 - tailwindcss@4.1.17:
4352 - resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
4355 + tailwindcss@4.1.18:
4356 + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
4357
4358 tapable@2.3.0:
4359 resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
@@ -4375,9 +4379,6 @@ packages:
4379 tinybench@2.9.0:
4380 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
4381
4378 - tinyexec@1.0.1:
4379 - resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
4380 -
4382 tinyexec@1.0.2:
4383 resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
4384 engines: {node: '>=18'}
@@ -4393,15 +4394,15 @@ packages:
4394 tldts-core@6.1.86:
4395 resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
4396
4396 - tldts-core@7.0.17:
4397 - resolution: {integrity: sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==}
4397 + tldts-core@7.0.23:
4398 + resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==}
4399
4400 tldts@6.1.86:
4401 resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
4402 hasBin: true
4403
4403 - tldts@7.0.17:
4404 - resolution: {integrity: sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==}
4404 + tldts@7.0.23:
4405 + resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==}
4406 hasBin: true
4407
4408 tmp@0.2.5:
@@ -4416,9 +4417,9 @@ packages:
4417 resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==}
4418 engines: {node: '>=20'}
4419
4419 - toml-eslint-parser@0.10.0:
4420 - resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==}
4421 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
4420 + toml-eslint-parser@1.0.3:
4421 + resolution: {integrity: sha512-A5F0cM6+mDleacLIEUkmfpkBbnHJFV1d2rprHU2MXNk7mlxHq2zGojA+SRvQD1RoMo9gqjZPWEaKG4v1BQ48lw==}
4422 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
4423
4424 totalist@3.0.1:
4425 resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
@@ -4446,8 +4447,8 @@ packages:
4447 trim-lines@3.0.1:
4448 resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
4449
4449 - ts-api-utils@2.1.0:
4450 - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
4450 + ts-api-utils@2.4.0:
4451 + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
4452 engines: {node: '>=18.12'}
4453 peerDependencies:
4454 typescript: '>=4.8.4'
@@ -4484,8 +4485,8 @@ packages:
4485 resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
4486 engines: {node: '>=8'}
4487
4487 - type-fest@5.3.0:
4488 - resolution: {integrity: sha512-d9CwU93nN0IA1QL+GSNDdwLAu1Ew5ZjTwupvedwg3WdfoH6pIDvYQ2hV0Uc2nKBLPq7NB5apCx57MLS5qlmO5g==}
4488 + type-fest@5.4.4:
4489 + resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==}
4490 engines: {node: '>=20'}
4491
4492 typescript@5.9.3:
@@ -4496,21 +4497,25 @@ packages:
4497 uc.micro@2.1.0:
4498 resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
4499
4499 - ufo@1.6.1:
4500 - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
4500 + ufo@1.6.3:
4501 + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
4502
4502 - unconfig-core@7.4.1:
4503 - resolution: {integrity: sha512-Bp/bPZjV2Vl/fofoA2OYLSnw1Z0MOhCX7zHnVCYrazpfZvseBbGhwcNQMxsg185Mqh7VZQqK3C8hFG/Dyng+yA==}
4503 + unconfig-core@7.5.0:
4504 + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==}
4505
4505 - unconfig@7.4.1:
4506 - resolution: {integrity: sha512-uyQ7LElcGizrOGZyIq9KU+xkuEjcRf9IpmDTkCSYv5mEeZzrXSj6rb51C0L+WTedsmAoVxW9WKrLWhSwebIM9Q==}
4506 + unconfig@7.5.0:
4507 + resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==}
4508
4508 - unctx@2.4.1:
4509 - resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==}
4509 + unctx@2.5.0:
4510 + resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==}
4511
4512 undici-types@7.16.0:
4513 resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
4514
4515 + undici@7.22.0:
4516 + resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
4517 + engines: {node: '>=20.18.1'}
4518 +
4519 unist-util-is@6.0.1:
4520 resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
4521
@@ -4523,8 +4528,8 @@ packages:
4528 unist-util-visit-parents@6.0.2:
4529 resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
4530
4526 - unist-util-visit@5.0.0:
4527 - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
4531 + unist-util-visit@5.1.0:
4532 + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
4533
4534 universalify@2.0.1:
4535 resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
@@ -4534,10 +4539,14 @@ packages:
4539 resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
4540 engines: {node: '>=20.19.0'}
4541
4537 - unplugin@2.3.10:
4538 - resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==}
4542 + unplugin@2.3.11:
4543 + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
4544 engines: {node: '>=18.12.0'}
4545
4546 + unplugin@3.0.0:
4547 + resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==}
4548 + engines: {node: ^20.19.0 || >=22.12.0}
4549 +
4550 untildify@4.0.0:
4551 resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
4552 engines: {node: '>=8'}
@@ -4546,8 +4555,8 @@ packages:
4555 resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==}
4556 hasBin: true
4557
4549 - update-browserslist-db@1.1.4:
4550 - resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==}
4558 + update-browserslist-db@1.2.3:
4559 + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
4560 hasBin: true
4561 peerDependencies:
4562 browserslist: '>= 4.21.0'
@@ -4562,8 +4571,8 @@ packages:
4571 resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
4572 hasBin: true
4573
4565 - validator@13.15.23:
4566 - resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
4574 + validator@13.15.26:
4575 + resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==}
4576 engines: {node: '>= 0.10'}
4577
4578 vdirs@0.1.8:
@@ -4606,8 +4615,8 @@ packages:
4615 '@nuxt/kit':
4616 optional: true
4617
4609 - vite-plugin-vue-devtools@8.0.5:
4610 - resolution: {integrity: sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A==}
4618 + vite-plugin-vue-devtools@8.0.6:
4619 + resolution: {integrity: sha512-IiTCIJDb1ZliOT8fPbYXllyfgARzz1+R1r8RN9ScGIDzAB6o8bDME1a9JjrfdSJibL7i8DIPQH+pGv0U7haBeA==}
4620 engines: {node: '>=v14.21.3'}
4621 peerDependencies:
4622 vite: ^6.0.0 || ^7.0.0-0
@@ -4622,8 +4631,8 @@ packages:
4631 peerDependencies:
4632 vue: '>=3.2.13'
4633
4625 - vite@7.2.6:
4626 - resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==}
4634 + vite@7.3.1:
4635 + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
4636 engines: {node: ^20.19.0 || >=22.12.0}
4637 hasBin: true
4638 peerDependencies:
@@ -4662,18 +4671,18 @@ packages:
4671 yaml:
4672 optional: true
4673
4665 - vitest@4.0.15:
4666 - resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==}
4674 + vitest@4.0.18:
4675 + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==}
4676 engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
4677 hasBin: true
4678 peerDependencies:
4679 '@edge-runtime/vm': '*'
4680 '@opentelemetry/api': ^1.9.0
4681 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
4673 - '@vitest/browser-playwright': 4.0.15
4674 - '@vitest/browser-preview': 4.0.15
4675 - '@vitest/browser-webdriverio': 4.0.15
4676 - '@vitest/ui': 4.0.15
4682 + '@vitest/browser-playwright': 4.0.18
4683 + '@vitest/browser-preview': 4.0.18
4684 + '@vitest/browser-webdriverio': 4.0.18
4685 + '@vitest/ui': 4.0.18
4686 happy-dom: '*'
4687 jsdom: '*'
4688 peerDependenciesMeta:
@@ -4719,35 +4728,45 @@ packages:
4728 vue-component-type-helpers@2.2.12:
4729 resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
4730
4722 - vue-eslint-parser@10.2.0:
4723 - resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==}
4731 + vue-eslint-parser@10.4.0:
4732 + resolution: {integrity: sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==}
4733 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
4734 peerDependencies:
4726 - eslint: ^8.57.0 || ^9.0.0
4735 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
4736
4737 vue-highlight-words@3.0.1:
4738 resolution: {integrity: sha512-t6L0+KRaMzX5RVubWl6Sog+Aa+U0kHsbUlDIrln1VfajR2SEg+zQYfXov8UmX3MLNOfd6cv7MCM6LclqNlW4ww==}
4739 peerDependencies:
4740 vue: ^3.0.0
4741
4733 - vue-i18n@11.2.2:
4734 - resolution: {integrity: sha512-ULIKZyRluUPRCZmihVgUvpq8hJTtOqnbGZuv4Lz+byEKZq4mU0g92og414l6f/4ju+L5mORsiUuEPYrAuX2NJg==}
4742 + vue-i18n@11.2.8:
4743 + resolution: {integrity: sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==}
4744 engines: {node: '>= 16'}
4745 peerDependencies:
4746 vue: ^3.0.0
4747
4739 - vue-router@4.6.3:
4740 - resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==}
4748 + vue-router@5.0.2:
4749 + resolution: {integrity: sha512-YFhwaE5c5JcJpNB1arpkl4/GnO32wiUWRB+OEj1T0DlDxEZoOfbltl2xEwktNU/9o1sGcGburIXSpbLpPFe/6w==}
4750 peerDependencies:
4751 + '@pinia/colada': '>=0.21.2'
4752 + '@vue/compiler-sfc': ^3.5.17
4753 + pinia: ^3.0.4
4754 vue: ^3.5.0
4755 + peerDependenciesMeta:
4756 + '@pinia/colada':
4757 + optional: true
4758 + '@vue/compiler-sfc':
4759 + optional: true
4760 + pinia:
4761 + optional: true
4762
4763 vue-sjv@0.0.6:
4764 resolution: {integrity: sha512-mk0D/OjzTS/sAqMuEWllVbopzKIoluNdgXIe3OIfqSyl53xX71DmZbe/EAUuaxgilQ9OZiQa3Hq5LYr2+CMW/w==}
4765 peerDependencies:
4766 vue: ^3.3.4
4767
4749 - vue-tsc@3.1.5:
4750 - resolution: {integrity: sha512-L/G9IUjOWhBU0yun89rv8fKqmKC+T0HfhrFjlIml71WpfBv9eb4E9Bev8FMbyueBIU9vxQqbd+oOsVcDa5amGw==}
4768 + vue-tsc@3.2.4:
4769 + resolution: {integrity: sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==}
4770 hasBin: true
4771 peerDependencies:
4772 typescript: '>=5.0.0'
@@ -4764,8 +4783,8 @@ packages:
4783 peerDependencies:
4784 vue: ^3.2
4785
4767 - vue@3.5.25:
4768 - resolution: {integrity: sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==}
4786 + vue@3.5.28:
4787 + resolution: {integrity: sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==}
4788 peerDependencies:
4789 typescript: '*'
4790 peerDependenciesMeta:
@@ -4794,25 +4813,21 @@ packages:
4813 engines: {node: '>=20.0.0'}
4814 hasBin: true
4815
4797 - webidl-conversions@8.0.0:
4798 - resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==}
4816 + webidl-conversions@8.0.1:
4817 + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
4818 engines: {node: '>=20'}
4819
4820 webpack-virtual-modules@0.6.2:
4821 resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
4822
4804 - whatwg-encoding@3.1.1:
4805 - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
4806 - engines: {node: '>=18'}
4807 -
4808 - whatwg-mimetype@4.0.0:
4809 - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
4810 - engines: {node: '>=18'}
4811 -
4812 - whatwg-url@15.1.0:
4813 - resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
4823 + whatwg-mimetype@5.0.0:
4824 + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
4825 engines: {node: '>=20'}
4826
4827 + whatwg-url@16.0.0:
4828 + resolution: {integrity: sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==}
4829 + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
4830 +
4831 which@1.3.1:
4832 resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
4833 hasBin: true
@@ -4851,18 +4866,6 @@ packages:
4866 wrappy@1.0.2:
4867 resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
4868
4854 - ws@8.18.3:
4855 - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
4856 - engines: {node: '>=10.0.0'}
4857 - peerDependencies:
4858 - bufferutil: ^4.0.1
4859 - utf-8-validate: '>=5.0.2'
4860 - peerDependenciesMeta:
4861 - bufferutil:
4862 - optional: true
4863 - utf-8-validate:
4864 - optional: true
4865 -
4869 wsl-utils@0.1.0:
4870 resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
4871 engines: {node: '>=18'}
@@ -4894,19 +4897,14 @@ packages:
4897 yallist@3.1.1:
4898 resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
4899
4897 - yaml-eslint-parser@1.3.2:
4898 - resolution: {integrity: sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==}
4899 - engines: {node: ^14.17.0 || >=16.0.0}
4900 + yaml-eslint-parser@2.0.0:
4901 + resolution: {integrity: sha512-h0uDm97wvT2bokfwwTmY6kJ1hp6YDFL0nRHwNKz8s/VD1FH/vvZjAKoMUE+un0eaYBSG7/c6h+lJTP+31tjgTw==}
4902 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
4903
4904 yaml@1.10.2:
4905 resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
4906 engines: {node: '>= 6'}
4907
4905 - yaml@2.8.1:
4906 - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
4907 - engines: {node: '>= 14.6'}
4908 - hasBin: true
4909 -
4908 yaml@2.8.2:
4909 resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
4910 engines: {node: '>= 14.6'}
@@ -4943,135 +4941,135 @@ packages:
4941
4942 snapshots:
4943
4946 - '@acemir/cssom@0.9.24': {}
4944 + '@acemir/cssom@0.9.31': {}
4945
4946 '@ajoelp/json-to-formdata@1.5.0':
4947 dependencies:
4948 lodash: 4.17.21
4949
4952 - '@algolia/abtesting@1.8.0':
4950 + '@algolia/abtesting@1.14.1':
4951 dependencies:
4954 - '@algolia/client-common': 5.42.0
4955 - '@algolia/requester-browser-xhr': 5.42.0
4956 - '@algolia/requester-fetch': 5.42.0
4957 - '@algolia/requester-node-http': 5.42.0
4952 + '@algolia/client-common': 5.48.1
4953 + '@algolia/requester-browser-xhr': 5.48.1
4954 + '@algolia/requester-fetch': 5.48.1
4955 + '@algolia/requester-node-http': 5.48.1
4956
4959 - '@algolia/client-abtesting@5.42.0':
4957 + '@algolia/client-abtesting@5.48.1':
4958 dependencies:
4961 - '@algolia/client-common': 5.42.0
4962 - '@algolia/requester-browser-xhr': 5.42.0
4963 - '@algolia/requester-fetch': 5.42.0
4964 - '@algolia/requester-node-http': 5.42.0
4959 + '@algolia/client-common': 5.48.1
4960 + '@algolia/requester-browser-xhr': 5.48.1
4961 + '@algolia/requester-fetch': 5.48.1
4962 + '@algolia/requester-node-http': 5.48.1
4963
4966 - '@algolia/client-analytics@5.42.0':
4964 + '@algolia/client-analytics@5.48.1':
4965 dependencies:
4968 - '@algolia/client-common': 5.42.0
4969 - '@algolia/requester-browser-xhr': 5.42.0
4970 - '@algolia/requester-fetch': 5.42.0
4971 - '@algolia/requester-node-http': 5.42.0
4966 + '@algolia/client-common': 5.48.1
4967 + '@algolia/requester-browser-xhr': 5.48.1
4968 + '@algolia/requester-fetch': 5.48.1
4969 + '@algolia/requester-node-http': 5.48.1
4970
4973 - '@algolia/client-common@5.42.0': {}
4971 + '@algolia/client-common@5.48.1': {}
4972
4975 - '@algolia/client-insights@5.42.0':
4973 + '@algolia/client-insights@5.48.1':
4974 dependencies:
4977 - '@algolia/client-common': 5.42.0
4978 - '@algolia/requester-browser-xhr': 5.42.0
4979 - '@algolia/requester-fetch': 5.42.0
4980 - '@algolia/requester-node-http': 5.42.0
4975 + '@algolia/client-common': 5.48.1
4976 + '@algolia/requester-browser-xhr': 5.48.1
4977 + '@algolia/requester-fetch': 5.48.1
4978 + '@algolia/requester-node-http': 5.48.1
4979
4982 - '@algolia/client-personalization@5.42.0':
4980 + '@algolia/client-personalization@5.48.1':
4981 dependencies:
4984 - '@algolia/client-common': 5.42.0
4985 - '@algolia/requester-browser-xhr': 5.42.0
4986 - '@algolia/requester-fetch': 5.42.0
4987 - '@algolia/requester-node-http': 5.42.0
4982 + '@algolia/client-common': 5.48.1
4983 + '@algolia/requester-browser-xhr': 5.48.1
4984 + '@algolia/requester-fetch': 5.48.1
4985 + '@algolia/requester-node-http': 5.48.1
4986
4989 - '@algolia/client-query-suggestions@5.42.0':
4987 + '@algolia/client-query-suggestions@5.48.1':
4988 dependencies:
4991 - '@algolia/client-common': 5.42.0
4992 - '@algolia/requester-browser-xhr': 5.42.0
4993 - '@algolia/requester-fetch': 5.42.0
4994 - '@algolia/requester-node-http': 5.42.0
4989 + '@algolia/client-common': 5.48.1
4990 + '@algolia/requester-browser-xhr': 5.48.1
4991 + '@algolia/requester-fetch': 5.48.1
4992 + '@algolia/requester-node-http': 5.48.1
4993
4996 - '@algolia/client-search@5.42.0':
4994 + '@algolia/client-search@5.48.1':
4995 dependencies:
4998 - '@algolia/client-common': 5.42.0
4999 - '@algolia/requester-browser-xhr': 5.42.0
5000 - '@algolia/requester-fetch': 5.42.0
5001 - '@algolia/requester-node-http': 5.42.0
4996 + '@algolia/client-common': 5.48.1
4997 + '@algolia/requester-browser-xhr': 5.48.1
4998 + '@algolia/requester-fetch': 5.48.1
4999 + '@algolia/requester-node-http': 5.48.1
5000
5003 - '@algolia/ingestion@1.42.0':
5001 + '@algolia/ingestion@1.48.1':
5002 dependencies:
5005 - '@algolia/client-common': 5.42.0
5006 - '@algolia/requester-browser-xhr': 5.42.0
5007 - '@algolia/requester-fetch': 5.42.0
5008 - '@algolia/requester-node-http': 5.42.0
5003 + '@algolia/client-common': 5.48.1
5004 + '@algolia/requester-browser-xhr': 5.48.1
5005 + '@algolia/requester-fetch': 5.48.1
5006 + '@algolia/requester-node-http': 5.48.1
5007
5010 - '@algolia/monitoring@1.42.0':
5008 + '@algolia/monitoring@1.48.1':
5009 dependencies:
5012 - '@algolia/client-common': 5.42.0
5013 - '@algolia/requester-browser-xhr': 5.42.0
5014 - '@algolia/requester-fetch': 5.42.0
5015 - '@algolia/requester-node-http': 5.42.0
5010 + '@algolia/client-common': 5.48.1
5011 + '@algolia/requester-browser-xhr': 5.48.1
5012 + '@algolia/requester-fetch': 5.48.1
5013 + '@algolia/requester-node-http': 5.48.1
5014
5017 - '@algolia/recommend@5.42.0':
5015 + '@algolia/recommend@5.48.1':
5016 dependencies:
5019 - '@algolia/client-common': 5.42.0
5020 - '@algolia/requester-browser-xhr': 5.42.0
5021 - '@algolia/requester-fetch': 5.42.0
5022 - '@algolia/requester-node-http': 5.42.0
5017 + '@algolia/client-common': 5.48.1
5018 + '@algolia/requester-browser-xhr': 5.48.1
5019 + '@algolia/requester-fetch': 5.48.1
5020 + '@algolia/requester-node-http': 5.48.1
5021
5024 - '@algolia/requester-browser-xhr@5.42.0':
5022 + '@algolia/requester-browser-xhr@5.48.1':
5023 dependencies:
5026 - '@algolia/client-common': 5.42.0
5024 + '@algolia/client-common': 5.48.1
5025
5028 - '@algolia/requester-fetch@5.42.0':
5026 + '@algolia/requester-fetch@5.48.1':
5027 dependencies:
5030 - '@algolia/client-common': 5.42.0
5028 + '@algolia/client-common': 5.48.1
5029
5032 - '@algolia/requester-node-http@5.42.0':
5030 + '@algolia/requester-node-http@5.48.1':
5031 dependencies:
5034 - '@algolia/client-common': 5.42.0
5032 + '@algolia/client-common': 5.48.1
5033
5036 - '@antfu/eslint-config@6.4.2(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.15(@types/node@24.10.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))':
5034 + '@antfu/eslint-config@7.4.3(@vue/compiler-sfc@3.5.28)(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))':
5035 dependencies:
5036 '@antfu/install-pkg': 1.1.0
5039 - '@clack/prompts': 0.11.0
5040 - '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.39.1(jiti@2.6.1))
5037 + '@clack/prompts': 1.0.1
5038 + '@eslint-community/eslint-plugin-eslint-comments': 4.6.0(eslint@10.0.0(jiti@2.6.1))
5039 '@eslint/markdown': 7.5.1
5042 - '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@2.6.1))
5043 - '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
5044 - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
5045 - '@vitest/eslint-plugin': 1.5.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.15(@types/node@24.10.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(sass@1.94.2)(yaml@2.8.2))
5040 + '@stylistic/eslint-plugin': 5.8.0(eslint@10.0.0(jiti@2.6.1))
5041 + '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5042 + '@typescript-eslint/parser': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5043 + '@vitest/eslint-plugin': 1.6.9(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
5044 ansis: 4.2.0
5045 cac: 6.7.14
5048 - eslint: 9.39.1(jiti@2.6.1)
5049 - eslint-config-flat-gitignore: 2.1.0(eslint@9.39.1(jiti@2.6.1))
5050 - eslint-flat-config-utils: 2.1.4
5051 - eslint-merge-processors: 2.0.0(eslint@9.39.1(jiti@2.6.1))
5052 - eslint-plugin-antfu: 3.1.1(eslint@9.39.1(jiti@2.6.1))
5053 - eslint-plugin-command: 3.3.1(eslint@9.39.1(jiti@2.6.1))
5054 - eslint-plugin-import-lite: 0.3.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
5055 - eslint-plugin-jsdoc: 61.4.1(eslint@9.39.1(jiti@2.6.1))
5056 - eslint-plugin-jsonc: 2.21.0(eslint@9.39.1(jiti@2.6.1))
5057 - eslint-plugin-n: 17.23.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
5046 + eslint: 10.0.0(jiti@2.6.1)
5047 + eslint-config-flat-gitignore: 2.1.0(eslint@10.0.0(jiti@2.6.1))
5048 + eslint-flat-config-utils: 3.0.1
5049 + eslint-merge-processors: 2.0.0(eslint@10.0.0(jiti@2.6.1))
5050 + eslint-plugin-antfu: 3.2.2(eslint@10.0.0(jiti@2.6.1))
5051 + eslint-plugin-command: 3.4.0(eslint@10.0.0(jiti@2.6.1))
5052 + eslint-plugin-import-lite: 0.5.1(eslint@10.0.0(jiti@2.6.1))
5053 + eslint-plugin-jsdoc: 62.5.5(eslint@10.0.0(jiti@2.6.1))
5054 + eslint-plugin-jsonc: 2.21.1(eslint@10.0.0(jiti@2.6.1))
5055 + eslint-plugin-n: 17.24.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5056 eslint-plugin-no-only-tests: 3.3.0
5059 - eslint-plugin-perfectionist: 4.15.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
5060 - eslint-plugin-pnpm: 1.4.1(eslint@9.39.1(jiti@2.6.1))
5061 - eslint-plugin-regexp: 2.10.0(eslint@9.39.1(jiti@2.6.1))
5062 - eslint-plugin-toml: 0.12.0(eslint@9.39.1(jiti@2.6.1))
5063 - eslint-plugin-unicorn: 62.0.0(eslint@9.39.1(jiti@2.6.1))
5064 - eslint-plugin-unused-imports: 4.3.0(@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))
5065 - eslint-plugin-vue: 10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1)))
5066 - eslint-plugin-yml: 1.19.0(eslint@9.39.1(jiti@2.6.1))
5067 - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@2.6.1))
5068 - globals: 16.5.0
5069 - jsonc-eslint-parser: 2.4.1
5057 + eslint-plugin-perfectionist: 5.5.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5058 + eslint-plugin-pnpm: 1.5.0(eslint@10.0.0(jiti@2.6.1))
5059 + eslint-plugin-regexp: 3.0.0(eslint@10.0.0(jiti@2.6.1))
5060 + eslint-plugin-toml: 1.0.4(eslint@10.0.0(jiti@2.6.1))
5061 + eslint-plugin-unicorn: 63.0.0(eslint@10.0.0(jiti@2.6.1))
5062 + eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))
5063 + eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.8.0(eslint@10.0.0(jiti@2.6.1)))(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.0.0(jiti@2.6.1)))
5064 + eslint-plugin-yml: 3.1.2(eslint@10.0.0(jiti@2.6.1))
5065 + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.28)(eslint@10.0.0(jiti@2.6.1))
5066 + globals: 17.3.0
5067 + jsonc-eslint-parser: 2.4.2
5068 local-pkg: 1.1.2
5069 parse-gitignore: 2.0.0
5072 - toml-eslint-parser: 0.10.0
5073 - vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@2.6.1))
5074 - yaml-eslint-parser: 1.3.2
5070 + toml-eslint-parser: 1.0.3
5071 + vue-eslint-parser: 10.4.0(eslint@10.0.0(jiti@2.6.1))
5072 + yaml-eslint-parser: 2.0.0
5073 transitivePeerDependencies:
5074 - '@eslint/json'
5075 - '@vue/compiler-sfc'
@@ -5081,54 +5079,54 @@ snapshots:
5079
5080 '@antfu/install-pkg@1.1.0':
5081 dependencies:
5084 - package-manager-detector: 1.5.0
5082 + package-manager-detector: 1.6.0
5083 tinyexec: 1.0.2
5084
5085 '@antfu/ni@27.0.1':
5086 dependencies:
5087 ansis: 4.2.0
5088 fzf: 0.5.2
5091 - package-manager-detector: 1.5.0
5089 + package-manager-detector: 1.6.0
5090 tinyexec: 1.0.2
5091 tinyglobby: 0.2.15
5092
5095 - '@asamuzakjp/css-color@4.0.5':
5093 + '@asamuzakjp/css-color@4.1.2':
5094 dependencies:
5097 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
5098 - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
5099 - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
5100 - '@csstools/css-tokenizer': 3.0.4
5101 - lru-cache: 11.2.2
5095 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
5096 + '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
5097 + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
5098 + '@csstools/css-tokenizer': 4.0.0
5099 + lru-cache: 11.2.6
5100
5103 - '@asamuzakjp/dom-selector@6.7.4':
5101 + '@asamuzakjp/dom-selector@6.8.1':
5102 dependencies:
5103 '@asamuzakjp/nwsapi': 2.3.9
5104 bidi-js: 1.0.3
5105 css-tree: 3.1.0
5106 is-potential-custom-element-name: 1.0.1
5109 - lru-cache: 11.2.2
5107 + lru-cache: 11.2.6
5108
5109 '@asamuzakjp/nwsapi@2.3.9': {}
5110
5113 - '@babel/code-frame@7.27.1':
5111 + '@babel/code-frame@7.29.0':
5112 dependencies:
5113 '@babel/helper-validator-identifier': 7.28.5
5114 js-tokens: 4.0.0
5115 picocolors: 1.1.1
5116
5119 - '@babel/compat-data@7.28.5': {}
5117 + '@babel/compat-data@7.29.0': {}
5118
5121 - '@babel/core@7.28.5':
5119 + '@babel/core@7.29.0':
5120 dependencies:
5123 - '@babel/code-frame': 7.27.1
5124 - '@babel/generator': 7.28.5
5125 - '@babel/helper-compilation-targets': 7.27.2
5126 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
5127 - '@babel/helpers': 7.28.4
5128 - '@babel/parser': 7.28.5
5129 - '@babel/template': 7.27.2
5130 - '@babel/traverse': 7.28.5
5131 - '@babel/types': 7.28.5
5121 + '@babel/code-frame': 7.29.0
5122 + '@babel/generator': 7.29.1
5123 + '@babel/helper-compilation-targets': 7.28.6
5124 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
5125 + '@babel/helpers': 7.28.6
5126 + '@babel/parser': 7.29.0
5127 + '@babel/template': 7.28.6
5128 + '@babel/traverse': 7.29.0
5129 + '@babel/types': 7.29.0
5130 '@jridgewell/remapping': 2.3.5
5131 convert-source-map: 2.0.0
5132 debug: 4.4.3(supports-color@8.1.1)
@@ -5138,35 +5136,35 @@ snapshots:
5136 transitivePeerDependencies:
5137 - supports-color
5138
5141 - '@babel/generator@7.28.5':
5139 + '@babel/generator@7.29.1':
5140 dependencies:
5143 - '@babel/parser': 7.28.5
5144 - '@babel/types': 7.28.5
5141 + '@babel/parser': 7.29.0
5142 + '@babel/types': 7.29.0
5143 '@jridgewell/gen-mapping': 0.3.13
5144 '@jridgewell/trace-mapping': 0.3.31
5145 jsesc: 3.1.0
5146
5147 '@babel/helper-annotate-as-pure@7.27.3':
5148 dependencies:
5151 - '@babel/types': 7.28.5
5149 + '@babel/types': 7.29.0
5150
5153 - '@babel/helper-compilation-targets@7.27.2':
5151 + '@babel/helper-compilation-targets@7.28.6':
5152 dependencies:
5155 - '@babel/compat-data': 7.28.5
5153 + '@babel/compat-data': 7.29.0
5154 '@babel/helper-validator-option': 7.27.1
5157 - browserslist: 4.27.0
5155 + browserslist: 4.28.1
5156 lru-cache: 5.1.1
5157 semver: 6.3.1
5158
5161 - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)':
5159 + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)':
5160 dependencies:
5163 - '@babel/core': 7.28.5
5161 + '@babel/core': 7.29.0
5162 '@babel/helper-annotate-as-pure': 7.27.3
5163 '@babel/helper-member-expression-to-functions': 7.28.5
5164 '@babel/helper-optimise-call-expression': 7.27.1
5167 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5)
5165 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
5166 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
5169 - '@babel/traverse': 7.28.5
5167 + '@babel/traverse': 7.29.0
5168 semver: 6.3.1
5169 transitivePeerDependencies:
5170 - supports-color
@@ -5175,46 +5173,46 @@ snapshots:
5173
5174 '@babel/helper-member-expression-to-functions@7.28.5':
5175 dependencies:
5178 - '@babel/traverse': 7.28.5
5179 - '@babel/types': 7.28.5
5176 + '@babel/traverse': 7.29.0
5177 + '@babel/types': 7.29.0
5178 transitivePeerDependencies:
5179 - supports-color
5180
5183 - '@babel/helper-module-imports@7.27.1':
5181 + '@babel/helper-module-imports@7.28.6':
5182 dependencies:
5185 - '@babel/traverse': 7.28.5
5186 - '@babel/types': 7.28.5
5183 + '@babel/traverse': 7.29.0
5184 + '@babel/types': 7.29.0
5185 transitivePeerDependencies:
5186 - supports-color
5187
5190 - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
5188 + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
5189 dependencies:
5192 - '@babel/core': 7.28.5
5193 - '@babel/helper-module-imports': 7.27.1
5190 + '@babel/core': 7.29.0
5191 + '@babel/helper-module-imports': 7.28.6
5192 '@babel/helper-validator-identifier': 7.28.5
5195 - '@babel/traverse': 7.28.5
5193 + '@babel/traverse': 7.29.0
5194 transitivePeerDependencies:
5195 - supports-color
5196
5197 '@babel/helper-optimise-call-expression@7.27.1':
5198 dependencies:
5201 - '@babel/types': 7.28.5
5199 + '@babel/types': 7.29.0
5200
5203 - '@babel/helper-plugin-utils@7.27.1': {}
5201 + '@babel/helper-plugin-utils@7.28.6': {}
5202
5205 - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)':
5203 + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)':
5204 dependencies:
5207 - '@babel/core': 7.28.5
5205 + '@babel/core': 7.29.0
5206 '@babel/helper-member-expression-to-functions': 7.28.5
5207 '@babel/helper-optimise-call-expression': 7.27.1
5210 - '@babel/traverse': 7.28.5
5208 + '@babel/traverse': 7.29.0
5209 transitivePeerDependencies:
5210 - supports-color
5211
5212 '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
5213 dependencies:
5216 - '@babel/traverse': 7.28.5
5217 - '@babel/types': 7.28.5
5214 + '@babel/traverse': 7.29.0
5215 + '@babel/types': 7.29.0
5216 transitivePeerDependencies:
5217 - supports-color
5218
@@ -5224,162 +5222,166 @@ snapshots:
5222
5223 '@babel/helper-validator-option@7.27.1': {}
5224
5227 - '@babel/helpers@7.28.4':
5225 + '@babel/helpers@7.28.6':
5226 dependencies:
5229 - '@babel/template': 7.27.2
5230 - '@babel/types': 7.28.5
5227 + '@babel/template': 7.28.6
5228 + '@babel/types': 7.29.0
5229
5232 - '@babel/parser@7.28.5':
5230 + '@babel/parser@7.29.0':
5231 dependencies:
5234 - '@babel/types': 7.28.5
5232 + '@babel/types': 7.29.0
5233
5236 - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.5)':
5234 + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)':
5235 dependencies:
5238 - '@babel/core': 7.28.5
5239 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
5240 - '@babel/helper-plugin-utils': 7.27.1
5241 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.5)
5236 + '@babel/core': 7.29.0
5237 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
5238 + '@babel/helper-plugin-utils': 7.28.6
5239 + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0)
5240 transitivePeerDependencies:
5241 - supports-color
5242
5245 - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)':
5243 + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)':
5244 dependencies:
5247 - '@babel/core': 7.28.5
5248 - '@babel/helper-plugin-utils': 7.27.1
5245 + '@babel/core': 7.29.0
5246 + '@babel/helper-plugin-utils': 7.28.6
5247
5250 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)':
5248 + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)':
5249 dependencies:
5252 - '@babel/core': 7.28.5
5253 - '@babel/helper-plugin-utils': 7.27.1
5250 + '@babel/core': 7.29.0
5251 + '@babel/helper-plugin-utils': 7.28.6
5252
5255 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)':
5253 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)':
5254 dependencies:
5257 - '@babel/core': 7.28.5
5258 - '@babel/helper-plugin-utils': 7.27.1
5255 + '@babel/core': 7.29.0
5256 + '@babel/helper-plugin-utils': 7.28.6
5257
5260 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)':
5258 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
5259 dependencies:
5262 - '@babel/core': 7.28.5
5263 - '@babel/helper-plugin-utils': 7.27.1
5260 + '@babel/core': 7.29.0
5261 + '@babel/helper-plugin-utils': 7.28.6
5262
5265 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)':
5263 + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
5264 dependencies:
5267 - '@babel/core': 7.28.5
5268 - '@babel/helper-plugin-utils': 7.27.1
5265 + '@babel/core': 7.29.0
5266 + '@babel/helper-plugin-utils': 7.28.6
5267
5270 - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)':
5268 + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)':
5269 dependencies:
5272 - '@babel/core': 7.28.5
5270 + '@babel/core': 7.29.0
5271 '@babel/helper-annotate-as-pure': 7.27.3
5274 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5)
5275 - '@babel/helper-plugin-utils': 7.27.1
5272 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
5273 + '@babel/helper-plugin-utils': 7.28.6
5274 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
5277 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5)
5275 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
5276 transitivePeerDependencies:
5277 - supports-color
5278
5281 - '@babel/template@7.27.2':
5279 + '@babel/template@7.28.6':
5280 dependencies:
5283 - '@babel/code-frame': 7.27.1
5284 - '@babel/parser': 7.28.5
5285 - '@babel/types': 7.28.5
5281 + '@babel/code-frame': 7.29.0
5282 + '@babel/parser': 7.29.0
5283 + '@babel/types': 7.29.0
5284
5287 - '@babel/traverse@7.28.5':
5285 + '@babel/traverse@7.29.0':
5286 dependencies:
5289 - '@babel/code-frame': 7.27.1
5290 - '@babel/generator': 7.28.5
5287 + '@babel/code-frame': 7.29.0
5288 + '@babel/generator': 7.29.1
5289 '@babel/helper-globals': 7.28.0
5292 - '@babel/parser': 7.28.5
5293 - '@babel/template': 7.27.2
5294 - '@babel/types': 7.28.5
5290 + '@babel/parser': 7.29.0
5291 + '@babel/template': 7.28.6
5292 + '@babel/types': 7.29.0
5293 debug: 4.4.3(supports-color@8.1.1)
5294 transitivePeerDependencies:
5295 - supports-color
5296
5299 - '@babel/types@7.28.5':
5297 + '@babel/types@7.29.0':
5298 dependencies:
5299 '@babel/helper-string-parser': 7.27.1
5300 '@babel/helper-validator-identifier': 7.28.5
5301
5304 - '@clack/core@0.5.0':
5302 + '@bramus/specificity@2.4.2':
5303 + dependencies:
5304 + css-tree: 3.1.0
5305 +
5306 + '@clack/core@1.0.1':
5307 dependencies:
5308 picocolors: 1.1.1
5309 sisteransi: 1.0.5
5310
5309 - '@clack/prompts@0.11.0':
5311 + '@clack/prompts@1.0.1':
5312 dependencies:
5311 - '@clack/core': 0.5.0
5313 + '@clack/core': 1.0.1
5314 picocolors: 1.1.1
5315 sisteransi: 1.0.5
5316
5315 - '@codemirror/autocomplete@6.19.1':
5317 + '@codemirror/autocomplete@6.20.0':
5318 dependencies:
5317 - '@codemirror/language': 6.11.3
5318 - '@codemirror/state': 6.5.2
5319 - '@codemirror/view': 6.38.8
5320 - '@lezer/common': 1.3.0
5319 + '@codemirror/language': 6.12.1
5320 + '@codemirror/state': 6.5.4
5321 + '@codemirror/view': 6.39.14
5322 + '@lezer/common': 1.5.1
5323
5322 - '@codemirror/commands@6.10.0':
5324 + '@codemirror/commands@6.10.2':
5325 dependencies:
5324 - '@codemirror/language': 6.11.3
5325 - '@codemirror/state': 6.5.2
5326 - '@codemirror/view': 6.38.8
5327 - '@lezer/common': 1.3.0
5326 + '@codemirror/language': 6.12.1
5327 + '@codemirror/state': 6.5.4
5328 + '@codemirror/view': 6.39.14
5329 + '@lezer/common': 1.5.1
5330
5331 '@codemirror/lang-javascript@6.2.4':
5332 dependencies:
5331 - '@codemirror/autocomplete': 6.19.1
5332 - '@codemirror/language': 6.11.3
5333 - '@codemirror/lint': 6.9.2
5334 - '@codemirror/state': 6.5.2
5335 - '@codemirror/view': 6.38.8
5336 - '@lezer/common': 1.3.0
5333 + '@codemirror/autocomplete': 6.20.0
5334 + '@codemirror/language': 6.12.1
5335 + '@codemirror/lint': 6.9.4
5336 + '@codemirror/state': 6.5.4
5337 + '@codemirror/view': 6.39.14
5338 + '@lezer/common': 1.5.1
5339 '@lezer/javascript': 1.5.4
5340
5341 '@codemirror/lang-xml@6.1.0':
5342 dependencies:
5341 - '@codemirror/autocomplete': 6.19.1
5342 - '@codemirror/language': 6.11.3
5343 - '@codemirror/state': 6.5.2
5344 - '@codemirror/view': 6.38.8
5345 - '@lezer/common': 1.3.0
5343 + '@codemirror/autocomplete': 6.20.0
5344 + '@codemirror/language': 6.12.1
5345 + '@codemirror/state': 6.5.4
5346 + '@codemirror/view': 6.39.14
5347 + '@lezer/common': 1.5.1
5348 '@lezer/xml': 1.0.6
5349
5348 - '@codemirror/language@6.11.3':
5350 + '@codemirror/language@6.12.1':
5351 dependencies:
5350 - '@codemirror/state': 6.5.2
5351 - '@codemirror/view': 6.38.8
5352 - '@lezer/common': 1.3.0
5352 + '@codemirror/state': 6.5.4
5353 + '@codemirror/view': 6.39.14
5354 + '@lezer/common': 1.5.1
5355 '@lezer/highlight': 1.2.3
5354 - '@lezer/lr': 1.4.2
5356 + '@lezer/lr': 1.4.8
5357 style-mod: 4.1.3
5358
5357 - '@codemirror/lint@6.9.2':
5359 + '@codemirror/lint@6.9.4':
5360 dependencies:
5359 - '@codemirror/state': 6.5.2
5360 - '@codemirror/view': 6.38.8
5361 + '@codemirror/state': 6.5.4
5362 + '@codemirror/view': 6.39.14
5363 crelt: 1.0.6
5364
5363 - '@codemirror/search@6.5.11':
5365 + '@codemirror/search@6.6.0':
5366 dependencies:
5365 - '@codemirror/state': 6.5.2
5366 - '@codemirror/view': 6.38.8
5367 + '@codemirror/state': 6.5.4
5368 + '@codemirror/view': 6.39.14
5369 crelt: 1.0.6
5370
5369 - '@codemirror/state@6.5.2':
5371 + '@codemirror/state@6.5.4':
5372 dependencies:
5373 '@marijn/find-cluster-break': 1.0.2
5374
5375 '@codemirror/theme-one-dark@6.1.3':
5376 dependencies:
5375 - '@codemirror/language': 6.11.3
5376 - '@codemirror/state': 6.5.2
5377 - '@codemirror/view': 6.38.8
5377 + '@codemirror/language': 6.12.1
5378 + '@codemirror/state': 6.5.4
5379 + '@codemirror/view': 6.39.14
5380 '@lezer/highlight': 1.2.3
5381
5380 - '@codemirror/view@6.38.8':
5382 + '@codemirror/view@6.39.14':
5383 dependencies:
5382 - '@codemirror/state': 6.5.2
5384 + '@codemirror/state': 6.5.4
5385 crelt: 1.0.6
5386 style-mod: 4.1.3
5387 w3c-keyname: 2.2.8
@@ -5388,33 +5390,33 @@ snapshots:
5390 dependencies:
5391 css-render: 0.15.14
5392
5391 - '@css-render/vue3-ssr@0.15.14(vue@3.5.25(typescript@5.9.3))':
5393 + '@css-render/vue3-ssr@0.15.14(vue@3.5.28(typescript@5.9.3))':
5394 dependencies:
5393 - vue: 3.5.25(typescript@5.9.3)
5395 + vue: 3.5.28(typescript@5.9.3)
5396
5395 - '@csstools/color-helpers@5.1.0': {}
5397 + '@csstools/color-helpers@6.0.1': {}
5398
5397 - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
5399 + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
5400 dependencies:
5399 - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
5400 - '@csstools/css-tokenizer': 3.0.4
5401 + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
5402 + '@csstools/css-tokenizer': 4.0.0
5403
5402 - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
5404 + '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
5405 dependencies:
5404 - '@csstools/color-helpers': 5.1.0
5405 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
5406 - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
5407 - '@csstools/css-tokenizer': 3.0.4
5406 + '@csstools/color-helpers': 6.0.1
5407 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
5408 + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
5409 + '@csstools/css-tokenizer': 4.0.0
5410
5409 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
5411 + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
5412 dependencies:
5411 - '@csstools/css-tokenizer': 3.0.4
5413 + '@csstools/css-tokenizer': 4.0.0
5414
5413 - '@csstools/css-syntax-patches-for-csstree@1.0.15': {}
5415 + '@csstools/css-syntax-patches-for-csstree@1.0.27': {}
5416
5415 - '@csstools/css-tokenizer@3.0.4': {}
5417 + '@csstools/css-tokenizer@4.0.0': {}
5418
5417 - '@cypress/request@3.0.9':
5419 + '@cypress/request@3.0.10':
5420 dependencies:
5421 aws-sign2: 0.7.0
5422 aws4: 1.13.2
@@ -5422,14 +5424,14 @@ snapshots:
5424 combined-stream: 1.0.8
5425 extend: 3.0.2
5426 forever-agent: 0.6.1
5425 - form-data: 4.0.4
5427 + form-data: 4.0.5
5428 http-signature: 1.4.0
5429 is-typedarray: 1.0.0
5430 isstream: 0.1.2
5431 json-stringify-safe: 5.0.1
5432 mime-types: 2.1.35
5433 performance-now: 2.1.0
5432 - qs: 6.14.0
5434 + qs: 6.14.2
5435 safe-buffer: 5.2.1
5436 tough-cookie: 5.1.2
5437 tunnel-agent: 0.6.0
@@ -5444,152 +5446,140 @@ snapshots:
5446
5447 '@emotion/hash@0.8.0': {}
5448
5447 - '@es-joy/jsdoccomment@0.50.2':
5449 + '@es-joy/jsdoccomment@0.78.0':
5450 dependencies:
5451 '@types/estree': 1.0.8
5450 - '@typescript-eslint/types': 8.46.2
5452 + '@typescript-eslint/types': 8.56.0
5453 comment-parser: 1.4.1
5452 - esquery: 1.6.0
5453 - jsdoc-type-pratt-parser: 4.1.0
5454 + esquery: 1.7.0
5455 + jsdoc-type-pratt-parser: 7.0.0
5456
5455 - '@es-joy/jsdoccomment@0.76.0':
5457 + '@es-joy/jsdoccomment@0.84.0':
5458 dependencies:
5459 '@types/estree': 1.0.8
5458 - '@typescript-eslint/types': 8.46.2
5459 - comment-parser: 1.4.1
5460 - esquery: 1.6.0
5461 - jsdoc-type-pratt-parser: 6.10.0
5460 + '@typescript-eslint/types': 8.56.0
5461 + comment-parser: 1.4.5
5462 + esquery: 1.7.0
5463 + jsdoc-type-pratt-parser: 7.1.1
5464
5465 '@es-joy/resolve.exports@1.2.0': {}
5466
5465 - '@esbuild/aix-ppc64@0.25.11':
5467 + '@esbuild/aix-ppc64@0.27.3':
5468 optional: true
5469
5468 - '@esbuild/android-arm64@0.25.11':
5470 + '@esbuild/android-arm64@0.27.3':
5471 optional: true
5472
5471 - '@esbuild/android-arm@0.25.11':
5473 + '@esbuild/android-arm@0.27.3':
5474 optional: true
5475
5474 - '@esbuild/android-x64@0.25.11':
5476 + '@esbuild/android-x64@0.27.3':
5477 optional: true
5478
5477 - '@esbuild/darwin-arm64@0.25.11':
5479 + '@esbuild/darwin-arm64@0.27.3':
5480 optional: true
5481
5480 - '@esbuild/darwin-x64@0.25.11':
5482 + '@esbuild/darwin-x64@0.27.3':
5483 optional: true
5484
5483 - '@esbuild/freebsd-arm64@0.25.11':
5485 + '@esbuild/freebsd-arm64@0.27.3':
5486 optional: true
5487
5486 - '@esbuild/freebsd-x64@0.25.11':
5488 + '@esbuild/freebsd-x64@0.27.3':
5489 optional: true
5490
5489 - '@esbuild/linux-arm64@0.25.11':
5491 + '@esbuild/linux-arm64@0.27.3':
5492 optional: true
5493
5492 - '@esbuild/linux-arm@0.25.11':
5494 + '@esbuild/linux-arm@0.27.3':
5495 optional: true
5496
5495 - '@esbuild/linux-ia32@0.25.11':
5497 + '@esbuild/linux-ia32@0.27.3':
5498 optional: true
5499
5498 - '@esbuild/linux-loong64@0.25.11':
5500 + '@esbuild/linux-loong64@0.27.3':
5501 optional: true
5502
5501 - '@esbuild/linux-mips64el@0.25.11':
5503 + '@esbuild/linux-mips64el@0.27.3':
5504 optional: true
5505
5504 - '@esbuild/linux-ppc64@0.25.11':
5506 + '@esbuild/linux-ppc64@0.27.3':
5507 optional: true
5508
5507 - '@esbuild/linux-riscv64@0.25.11':
5509 + '@esbuild/linux-riscv64@0.27.3':
5510 optional: true
5511
5510 - '@esbuild/linux-s390x@0.25.11':
5512 + '@esbuild/linux-s390x@0.27.3':
5513 optional: true
5514
5513 - '@esbuild/linux-x64@0.25.11':
5515 + '@esbuild/linux-x64@0.27.3':
5516 optional: true
5517
5516 - '@esbuild/netbsd-arm64@0.25.11':
5518 + '@esbuild/netbsd-arm64@0.27.3':
5519 optional: true
5520
5519 - '@esbuild/netbsd-x64@0.25.11':
5521 + '@esbuild/netbsd-x64@0.27.3':
5522 optional: true
5523
5522 - '@esbuild/openbsd-arm64@0.25.11':
5524 + '@esbuild/openbsd-arm64@0.27.3':
5525 optional: true
5526
5525 - '@esbuild/openbsd-x64@0.25.11':
5527 + '@esbuild/openbsd-x64@0.27.3':
5528 optional: true
5529
5528 - '@esbuild/openharmony-arm64@0.25.11':
5530 + '@esbuild/openharmony-arm64@0.27.3':
5531 optional: true
5532
5531 - '@esbuild/sunos-x64@0.25.11':
5533 + '@esbuild/sunos-x64@0.27.3':
5534 optional: true
5535
5534 - '@esbuild/win32-arm64@0.25.11':
5536 + '@esbuild/win32-arm64@0.27.3':
5537 optional: true
5538
5537 - '@esbuild/win32-ia32@0.25.11':
5539 + '@esbuild/win32-ia32@0.27.3':
5540 optional: true
5541
5540 - '@esbuild/win32-x64@0.25.11':
5542 + '@esbuild/win32-x64@0.27.3':
5543 optional: true
5544
5543 - '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.39.1(jiti@2.6.1))':
5545 + '@eslint-community/eslint-plugin-eslint-comments@4.6.0(eslint@10.0.0(jiti@2.6.1))':
5546 dependencies:
5547 escape-string-regexp: 4.0.0
5546 - eslint: 9.39.1(jiti@2.6.1)
5547 - ignore: 5.3.2
5548 + eslint: 10.0.0(jiti@2.6.1)
5549 + ignore: 7.0.5
5550
5549 - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))':
5551 + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.0(jiti@2.6.1))':
5552 dependencies:
5551 - eslint: 9.39.1(jiti@2.6.1)
5553 + eslint: 10.0.0(jiti@2.6.1)
5554 eslint-visitor-keys: 3.4.3
5555
5556 '@eslint-community/regexpp@4.12.2': {}
5557
5556 - '@eslint/compat@1.4.1(eslint@9.39.1(jiti@2.6.1))':
5558 + '@eslint/compat@1.4.1(eslint@10.0.0(jiti@2.6.1))':
5559 dependencies:
5560 '@eslint/core': 0.17.0
5561 optionalDependencies:
5560 - eslint: 9.39.1(jiti@2.6.1)
5562 + eslint: 10.0.0(jiti@2.6.1)
5563
5562 - '@eslint/config-array@0.21.1':
5564 + '@eslint/config-array@0.23.1':
5565 dependencies:
5564 - '@eslint/object-schema': 2.1.7
5566 + '@eslint/object-schema': 3.0.1
5567 debug: 4.4.3(supports-color@8.1.1)
5566 - minimatch: 3.1.2
5568 + minimatch: 10.2.1
5569 transitivePeerDependencies:
5570 - supports-color
5571
5570 - '@eslint/config-helpers@0.4.2':
5572 + '@eslint/config-helpers@0.5.2':
5573 dependencies:
5572 - '@eslint/core': 0.17.0
5574 + '@eslint/core': 1.1.0
5575
5576 '@eslint/core@0.17.0':
5577 dependencies:
5578 '@types/json-schema': 7.0.15
5579
5578 - '@eslint/eslintrc@3.3.1':
5580 + '@eslint/core@1.1.0':
5581 dependencies:
5580 - ajv: 6.12.6
5581 - debug: 4.4.3(supports-color@8.1.1)
5582 - espree: 10.4.0
5583 - globals: 14.0.0
5584 - ignore: 5.3.2
5585 - import-fresh: 3.3.1
5586 - js-yaml: 4.1.0
5587 - minimatch: 3.1.2
5588 - strip-json-comments: 3.1.1
5589 - transitivePeerDependencies:
5590 - - supports-color
5591 -
5592 - '@eslint/js@9.39.1': {}
5582 + '@types/json-schema': 7.0.15
5583
5584 '@eslint/markdown@7.5.1':
5585 dependencies:
@@ -5605,17 +5595,24 @@ snapshots:
5595 transitivePeerDependencies:
5596 - supports-color
5597
5608 - '@eslint/object-schema@2.1.7': {}
5598 + '@eslint/object-schema@3.0.1': {}
5599
5600 '@eslint/plugin-kit@0.4.1':
5601 dependencies:
5602 '@eslint/core': 0.17.0
5603 levn: 0.4.1
5604
5615 - '@f3ve/vue-markdown-it@0.2.3(vue@3.5.25(typescript@5.9.3))':
5605 + '@eslint/plugin-kit@0.6.0':
5606 dependencies:
5617 - markdown-it: 14.1.0
5618 - vue: 3.5.25(typescript@5.9.3)
5607 + '@eslint/core': 1.1.0
5608 + levn: 0.4.1
5609 +
5610 + '@exodus/bytes@1.14.1': {}
5611 +
5612 + '@f3ve/vue-markdown-it@0.2.3(vue@3.5.28(typescript@5.9.3))':
5613 + dependencies:
5614 + markdown-it: 14.1.1
5615 + vue: 3.5.28(typescript@5.9.3)
5616
5617 '@fontsource/jetbrains-mono@5.2.8': {}
5618
@@ -5633,7 +5630,7 @@ snapshots:
5630
5631 '@hapi/pinpoint@2.0.1': {}
5632
5636 - '@hapi/tlds@1.1.4': {}
5633 + '@hapi/tlds@1.1.5': {}
5634
5635 '@hapi/topo@6.0.2':
5636 dependencies:
@@ -5652,22 +5649,22 @@ snapshots:
5649
5650 '@iconify/types@2.0.0': {}
5651
5655 - '@iconify/vue@5.0.0(vue@3.5.25(typescript@5.9.3))':
5652 + '@iconify/vue@5.0.0(vue@3.5.28(typescript@5.9.3))':
5653 dependencies:
5654 '@iconify/types': 2.0.0
5658 - vue: 3.5.25(typescript@5.9.3)
5655 + vue: 3.5.28(typescript@5.9.3)
5656
5660 - '@intlify/core-base@11.2.2':
5657 + '@intlify/core-base@11.2.8':
5658 dependencies:
5662 - '@intlify/message-compiler': 11.2.2
5663 - '@intlify/shared': 11.2.2
5659 + '@intlify/message-compiler': 11.2.8
5660 + '@intlify/shared': 11.2.8
5661
5665 - '@intlify/message-compiler@11.2.2':
5662 + '@intlify/message-compiler@11.2.8':
5663 dependencies:
5667 - '@intlify/shared': 11.2.2
5664 + '@intlify/shared': 11.2.8
5665 source-map-js: 1.2.1
5666
5670 - '@intlify/shared@11.2.2': {}
5667 + '@intlify/shared@11.2.8': {}
5668
5669 '@isaacs/cliui@8.0.2':
5670 dependencies:
@@ -5678,6 +5675,8 @@ snapshots:
5675 wrap-ansi: 8.1.0
5676 wrap-ansi-cjs: wrap-ansi@7.0.0
5677
5678 + '@isaacs/cliui@9.0.0': {}
5679 +
5680 '@jridgewell/gen-mapping@0.3.13':
5681 dependencies:
5682 '@jridgewell/sourcemap-codec': 1.5.5
@@ -5699,66 +5698,54 @@ snapshots:
5698
5699 '@juggle/resize-observer@3.4.0': {}
5700
5702 - '@lezer/common@1.3.0': {}
5701 + '@lezer/common@1.5.1': {}
5702
5703 '@lezer/highlight@1.2.3':
5704 dependencies:
5706 - '@lezer/common': 1.3.0
5705 + '@lezer/common': 1.5.1
5706
5707 '@lezer/javascript@1.5.4':
5708 dependencies:
5710 - '@lezer/common': 1.3.0
5709 + '@lezer/common': 1.5.1
5710 '@lezer/highlight': 1.2.3
5712 - '@lezer/lr': 1.4.2
5711 + '@lezer/lr': 1.4.8
5712
5714 - '@lezer/lr@1.4.2':
5713 + '@lezer/lr@1.4.8':
5714 dependencies:
5716 - '@lezer/common': 1.3.0
5715 + '@lezer/common': 1.5.1
5716
5717 '@lezer/xml@1.0.6':
5718 dependencies:
5720 - '@lezer/common': 1.3.0
5719 + '@lezer/common': 1.5.1
5720 '@lezer/highlight': 1.2.3
5722 - '@lezer/lr': 1.4.2
5721 + '@lezer/lr': 1.4.8
5722
5723 '@marijn/find-cluster-break@1.0.2': {}
5724
5725 '@microsoft/fetch-event-source@2.0.1': {}
5726
5728 - '@nodelib/fs.scandir@2.1.5':
5727 + '@nuxt/kit@3.21.1':
5728 dependencies:
5730 - '@nodelib/fs.stat': 2.0.5
5731 - run-parallel: 1.2.0
5732 -
5733 - '@nodelib/fs.stat@2.0.5': {}
5734 -
5735 - '@nodelib/fs.walk@1.2.8':
5736 - dependencies:
5737 - '@nodelib/fs.scandir': 2.1.5
5738 - fastq: 1.19.1
5739 -
5740 - '@nuxt/kit@3.20.0':
5741 - dependencies:
5742 - c12: 3.3.1
5729 + c12: 3.3.3
5730 consola: 3.4.2
5731 defu: 6.1.4
5732 destr: 2.0.5
5733 errx: 0.1.0
5747 - exsolve: 1.0.7
5734 + exsolve: 1.0.8
5735 ignore: 7.0.5
5736 jiti: 2.6.1
5737 klona: 2.0.6
5751 - knitwork: 1.2.0
5738 + knitwork: 1.3.0
5739 mlly: 1.8.0
5740 ohash: 2.0.11
5741 pathe: 2.0.3
5742 pkg-types: 2.3.0
5756 - rc9: 2.1.2
5743 + rc9: 3.0.0
5744 scule: 1.3.0
5758 - semver: 7.7.3
5745 + semver: 7.7.4
5746 tinyglobby: 0.2.15
5760 - ufo: 1.6.1
5761 - unctx: 2.4.1
5747 + ufo: 1.6.3
5748 + unctx: 2.5.0
5749 untyped: 2.0.0
5750 transitivePeerDependencies:
5751 - magicast
@@ -5766,65 +5753,65 @@ snapshots:
5753
5754 '@one-ini/wasm@0.1.1': {}
5755
5769 - '@parcel/watcher-android-arm64@2.5.1':
5756 + '@parcel/watcher-android-arm64@2.5.6':
5757 optional: true
5758
5772 - '@parcel/watcher-darwin-arm64@2.5.1':
5759 + '@parcel/watcher-darwin-arm64@2.5.6':
5760 optional: true
5761
5775 - '@parcel/watcher-darwin-x64@2.5.1':
5762 + '@parcel/watcher-darwin-x64@2.5.6':
5763 optional: true
5764
5778 - '@parcel/watcher-freebsd-x64@2.5.1':
5765 + '@parcel/watcher-freebsd-x64@2.5.6':
5766 optional: true
5767
5781 - '@parcel/watcher-linux-arm-glibc@2.5.1':
5768 + '@parcel/watcher-linux-arm-glibc@2.5.6':
5769 optional: true
5770
5784 - '@parcel/watcher-linux-arm-musl@2.5.1':
5771 + '@parcel/watcher-linux-arm-musl@2.5.6':
5772 optional: true
5773
5787 - '@parcel/watcher-linux-arm64-glibc@2.5.1':
5774 + '@parcel/watcher-linux-arm64-glibc@2.5.6':
5775 optional: true
5776
5790 - '@parcel/watcher-linux-arm64-musl@2.5.1':
5777 + '@parcel/watcher-linux-arm64-musl@2.5.6':
5778 optional: true
5779
5793 - '@parcel/watcher-linux-x64-glibc@2.5.1':
5780 + '@parcel/watcher-linux-x64-glibc@2.5.6':
5781 optional: true
5782
5796 - '@parcel/watcher-linux-x64-musl@2.5.1':
5783 + '@parcel/watcher-linux-x64-musl@2.5.6':
5784 optional: true
5785
5799 - '@parcel/watcher-win32-arm64@2.5.1':
5786 + '@parcel/watcher-win32-arm64@2.5.6':
5787 optional: true
5788
5802 - '@parcel/watcher-win32-ia32@2.5.1':
5789 + '@parcel/watcher-win32-ia32@2.5.6':
5790 optional: true
5791
5805 - '@parcel/watcher-win32-x64@2.5.1':
5792 + '@parcel/watcher-win32-x64@2.5.6':
5793 optional: true
5794
5808 - '@parcel/watcher@2.5.1':
5795 + '@parcel/watcher@2.5.6':
5796 dependencies:
5810 - detect-libc: 1.0.3
5797 + detect-libc: 2.1.2
5798 is-glob: 4.0.3
5812 - micromatch: 4.0.8
5799 node-addon-api: 7.1.1
5800 + picomatch: 4.0.3
5801 optionalDependencies:
5815 - '@parcel/watcher-android-arm64': 2.5.1
5816 - '@parcel/watcher-darwin-arm64': 2.5.1
5817 - '@parcel/watcher-darwin-x64': 2.5.1
5818 - '@parcel/watcher-freebsd-x64': 2.5.1
5819 - '@parcel/watcher-linux-arm-glibc': 2.5.1
5820 - '@parcel/watcher-linux-arm-musl': 2.5.1
5821 - '@parcel/watcher-linux-arm64-glibc': 2.5.1
5822 - '@parcel/watcher-linux-arm64-musl': 2.5.1
5823 - '@parcel/watcher-linux-x64-glibc': 2.5.1
5824 - '@parcel/watcher-linux-x64-musl': 2.5.1
5825 - '@parcel/watcher-win32-arm64': 2.5.1
5826 - '@parcel/watcher-win32-ia32': 2.5.1
5827 - '@parcel/watcher-win32-x64': 2.5.1
5802 + '@parcel/watcher-android-arm64': 2.5.6
5803 + '@parcel/watcher-darwin-arm64': 2.5.6
5804 + '@parcel/watcher-darwin-x64': 2.5.6
5805 + '@parcel/watcher-freebsd-x64': 2.5.6
5806 + '@parcel/watcher-linux-arm-glibc': 2.5.6
5807 + '@parcel/watcher-linux-arm-musl': 2.5.6
5808 + '@parcel/watcher-linux-arm64-glibc': 2.5.6
5809 + '@parcel/watcher-linux-arm64-musl': 2.5.6
5810 + '@parcel/watcher-linux-x64-glibc': 2.5.6
5811 + '@parcel/watcher-linux-x64-musl': 2.5.6
5812 + '@parcel/watcher-win32-arm64': 2.5.6
5813 + '@parcel/watcher-win32-ia32': 2.5.6
5814 + '@parcel/watcher-win32-x64': 2.5.6
5815 optional: true
5816
5817 '@pkgjs/parseargs@0.11.0':
@@ -5834,112 +5821,121 @@ snapshots:
5821
5822 '@polka/url@1.0.0-next.29': {}
5823
5837 - '@quansync/fs@0.1.5':
5824 + '@quansync/fs@1.0.0':
5825 dependencies:
5839 - quansync: 0.2.11
5826 + quansync: 1.0.0
5827 +
5828 + '@rolldown/pluginutils@1.0.0-rc.2': {}
5829
5841 - '@rolldown/pluginutils@1.0.0-beta.50': {}
5830 + '@rolldown/pluginutils@1.0.0-rc.4': {}
5831
5843 - '@rolldown/pluginutils@1.0.0-beta.52': {}
5832 + '@rollup/rollup-android-arm-eabi@4.57.1':
5833 + optional: true
5834 +
5835 + '@rollup/rollup-android-arm64@4.57.1':
5836 + optional: true
5837
5845 - '@rollup/rollup-android-arm-eabi@4.52.5':
5838 + '@rollup/rollup-darwin-arm64@4.57.1':
5839 optional: true
5840
5848 - '@rollup/rollup-android-arm64@4.52.5':
5841 + '@rollup/rollup-darwin-x64@4.57.1':
5842 optional: true
5843
5851 - '@rollup/rollup-darwin-arm64@4.52.5':
5844 + '@rollup/rollup-freebsd-arm64@4.57.1':
5845 optional: true
5846
5854 - '@rollup/rollup-darwin-x64@4.52.5':
5847 + '@rollup/rollup-freebsd-x64@4.57.1':
5848 optional: true
5849
5857 - '@rollup/rollup-freebsd-arm64@4.52.5':
5850 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1':
5851 optional: true
5852
5860 - '@rollup/rollup-freebsd-x64@4.52.5':
5853 + '@rollup/rollup-linux-arm-musleabihf@4.57.1':
5854 optional: true
5855
5863 - '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
5856 + '@rollup/rollup-linux-arm64-gnu@4.57.1':
5857 optional: true
5858
5866 - '@rollup/rollup-linux-arm-musleabihf@4.52.5':
5859 + '@rollup/rollup-linux-arm64-musl@4.57.1':
5860 optional: true
5861
5869 - '@rollup/rollup-linux-arm64-gnu@4.52.5':
5862 + '@rollup/rollup-linux-loong64-gnu@4.57.1':
5863 optional: true
5864
5872 - '@rollup/rollup-linux-arm64-musl@4.52.5':
5865 + '@rollup/rollup-linux-loong64-musl@4.57.1':
5866 optional: true
5867
5875 - '@rollup/rollup-linux-loong64-gnu@4.52.5':
5868 + '@rollup/rollup-linux-ppc64-gnu@4.57.1':
5869 optional: true
5870
5878 - '@rollup/rollup-linux-ppc64-gnu@4.52.5':
5871 + '@rollup/rollup-linux-ppc64-musl@4.57.1':
5872 optional: true
5873
5881 - '@rollup/rollup-linux-riscv64-gnu@4.52.5':
5874 + '@rollup/rollup-linux-riscv64-gnu@4.57.1':
5875 optional: true
5876
5884 - '@rollup/rollup-linux-riscv64-musl@4.52.5':
5877 + '@rollup/rollup-linux-riscv64-musl@4.57.1':
5878 optional: true
5879
5887 - '@rollup/rollup-linux-s390x-gnu@4.52.5':
5880 + '@rollup/rollup-linux-s390x-gnu@4.57.1':
5881 optional: true
5882
5890 - '@rollup/rollup-linux-x64-gnu@4.52.5':
5883 + '@rollup/rollup-linux-x64-gnu@4.57.1':
5884 optional: true
5885
5893 - '@rollup/rollup-linux-x64-musl@4.52.5':
5886 + '@rollup/rollup-linux-x64-musl@4.57.1':
5887 optional: true
5888
5896 - '@rollup/rollup-openharmony-arm64@4.52.5':
5889 + '@rollup/rollup-openbsd-x64@4.57.1':
5890 optional: true
5891
5899 - '@rollup/rollup-win32-arm64-msvc@4.52.5':
5892 + '@rollup/rollup-openharmony-arm64@4.57.1':
5893 optional: true
5894
5902 - '@rollup/rollup-win32-ia32-msvc@4.52.5':
5895 + '@rollup/rollup-win32-arm64-msvc@4.57.1':
5896 optional: true
5897
5905 - '@rollup/rollup-win32-x64-gnu@4.52.5':
5898 + '@rollup/rollup-win32-ia32-msvc@4.57.1':
5899 optional: true
5900
5908 - '@rollup/rollup-win32-x64-msvc@4.52.5':
5901 + '@rollup/rollup-win32-x64-gnu@4.57.1':
5902 optional: true
5903
5911 - '@shikijs/core@3.19.0':
5904 + '@rollup/rollup-win32-x64-msvc@4.57.1':
5905 + optional: true
5906 +
5907 + '@shikijs/core@3.22.0':
5908 dependencies:
5913 - '@shikijs/types': 3.19.0
5909 + '@shikijs/types': 3.22.0
5910 '@shikijs/vscode-textmate': 10.0.2
5911 '@types/hast': 3.0.4
5912 hast-util-to-html: 9.0.5
5913
5918 - '@shikijs/engine-javascript@3.19.0':
5914 + '@shikijs/engine-javascript@3.22.0':
5915 dependencies:
5920 - '@shikijs/types': 3.19.0
5916 + '@shikijs/types': 3.22.0
5917 '@shikijs/vscode-textmate': 10.0.2
5918 oniguruma-to-es: 4.3.4
5919
5924 - '@shikijs/engine-oniguruma@3.19.0':
5920 + '@shikijs/engine-oniguruma@3.22.0':
5921 dependencies:
5926 - '@shikijs/types': 3.19.0
5922 + '@shikijs/types': 3.22.0
5923 '@shikijs/vscode-textmate': 10.0.2
5924
5929 - '@shikijs/langs@3.19.0':
5925 + '@shikijs/langs@3.22.0':
5926 dependencies:
5931 - '@shikijs/types': 3.19.0
5927 + '@shikijs/types': 3.22.0
5928
5933 - '@shikijs/markdown-it@3.19.0':
5929 + '@shikijs/markdown-it@3.22.0':
5930 dependencies:
5935 - markdown-it: 14.1.0
5936 - shiki: 3.19.0
5931 + markdown-it: 14.1.1
5932 + shiki: 3.22.0
5933
5938 - '@shikijs/themes@3.19.0':
5934 + '@shikijs/themes@3.22.0':
5935 dependencies:
5940 - '@shikijs/types': 3.19.0
5936 + '@shikijs/types': 3.22.0
5937
5942 - '@shikijs/types@3.19.0':
5938 + '@shikijs/types@3.22.0':
5939 dependencies:
5940 '@shikijs/vscode-textmate': 10.0.2
5941 '@types/hast': 3.0.4
@@ -5950,110 +5946,91 @@ snapshots:
5946
5947 '@singulio/app-auth-search@0.0.3':
5948 dependencies:
5953 - algoliasearch: 5.42.0
5949 + algoliasearch: 5.48.1
5950
5955 - '@standard-schema/spec@1.0.0': {}
5951 + '@standard-schema/spec@1.1.0': {}
5952
5957 - '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1))':
5953 + '@stylistic/eslint-plugin@5.8.0(eslint@10.0.0(jiti@2.6.1))':
5954 dependencies:
5959 - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
5960 - '@typescript-eslint/types': 8.48.1
5961 - eslint: 9.39.1(jiti@2.6.1)
5955 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
5956 + '@typescript-eslint/types': 8.56.0
5957 + eslint: 10.0.0(jiti@2.6.1)
5958 eslint-visitor-keys: 4.2.1
5959 espree: 10.4.0
5960 estraverse: 5.3.0
5961 picomatch: 4.0.3
5962
5967 - '@svgdotjs/svg.draggable.js@3.0.6(@svgdotjs/svg.js@3.2.5)':
5968 - dependencies:
5969 - '@svgdotjs/svg.js': 3.2.5
5970 -
5971 - '@svgdotjs/svg.filter.js@3.0.9':
5972 - dependencies:
5973 - '@svgdotjs/svg.js': 3.2.5
5974 -
5975 - '@svgdotjs/svg.js@3.2.5': {}
5976 -
5977 - '@svgdotjs/svg.resize.js@2.0.5(@svgdotjs/svg.js@3.2.5)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5))':
5978 - dependencies:
5979 - '@svgdotjs/svg.js': 3.2.5
5980 - '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.5)

This file is too large to show in full.

frontend/src/api/endpoints/agents.ts
+15 -14
@@ -4,8 +4,8 @@ import type {
4 AgentSca,
5 AgentVulnerabilities,
6 BulkDeleteAgentRequest,
7 - BulkDeleteAgentsResponse,
8 - BulkDeleteFilterRequest,
7 + BulkDeleteAgentsResponse,
8 + BulkDeleteFilterRequest,
9 OutdatedVelociraptorAgents,
10 OutdatedWazuhAgents,
11 ScaPolicyResult
@@ -19,6 +19,7 @@ export interface AgentPayload {
19
20 export type VulnerabilitySeverityType = "Low" | "Medium" | "High" | "Critical" | "All"
21
22 +// TODO: refactor
23 export default {
24 getAgents(agentId?: string) {
25 return HttpClient.get<FlaskBaseResponse & { agents: Agent[] }>(`/agents${agentId ? `/${agentId}` : ""}`)
@@ -36,18 +37,18 @@ export default {
37 * Bulk delete agents by their IDs
38 * @param agentIds - Array of agent IDs to delete
39 */
39 - bulkDeleteAgents(agentIds: string[]) {
40 - const payload: BulkDeleteAgentRequest = { agent_ids: agentIds }
41 - return HttpClient.post<BulkDeleteAgentsResponse>(`/agents/bulk/delete`, payload)
42 - },
43 - /**
44 - * Bulk delete agents based on filter conditions
45 - * At least one filter must be specified
46 - * @param filters - Filter conditions (customer_code, status, disconnected_days)
47 - */
48 - bulkDeleteAgentsByFilter(filters: BulkDeleteFilterRequest) {
49 - return HttpClient.post<BulkDeleteAgentsResponse>(`/agents/bulk/delete/filter`, filters)
50 - },
40 + bulkDeleteAgents(agentIds: string[]) {
41 + const payload: BulkDeleteAgentRequest = { agent_ids: agentIds }
42 + return HttpClient.post<BulkDeleteAgentsResponse>(`/agents/bulk/delete`, payload)
43 + },
44 + /**
45 + * Bulk delete agents based on filter conditions
46 + * At least one filter must be specified
47 + * @param filters - Filter conditions (customer_code, status, disconnected_days)
48 + */
49 + bulkDeleteAgentsByFilter(filters: BulkDeleteFilterRequest) {
50 + return HttpClient.post<BulkDeleteAgentsResponse>(`/agents/bulk/delete/filter`, filters)
51 + },
52 syncAgents() {
53 return HttpClient.post<FlaskBaseResponse>(`/agents/sync`)
54 },
frontend/src/api/endpoints/artifacts.ts
+72 -72
@@ -1,100 +1,100 @@
1 import type {
2 - Artifact,
3 - CollectResult,
4 - CommandResult,
5 - FileCollection,
6 - MatchingParameter,
7 - QuarantineResult,
8 - Recommendation
2 + Artifact,
3 + CollectResult,
4 + CommandResult,
5 + FileCollection,
6 + MatchingParameter,
7 + QuarantineResult,
8 + Recommendation
9 } from "@/types/artifacts.d"
10 import type { OsTypesFull, OsTypesLower } from "@/types/common.d"
11 import type { FlaskBaseResponse } from "@/types/flask.d"
12 import { HttpClient } from "../httpClient"
13
14 export interface ArtifactsQuery {
15 - os?: OsTypesLower
16 - hostname?: string
15 + os?: OsTypesLower
16 + hostname?: string
17 }
18
19 export interface CollectRequest {
20 - hostname: string
21 - velociraptor_id?: string
22 - artifact_name: string
23 - data_store_only?: boolean
24 - parameters?: {
25 - env?: {
26 - key: string
27 - value: string
28 - }[]
29 - }
20 + hostname: string
21 + velociraptor_id?: string
22 + artifact_name: string
23 + data_store_only?: boolean
24 + parameters?: {
25 + env?: {
26 + key: string
27 + value: string
28 + }[]
29 + }
30 }
31
32 export interface CommandRequest {
33 - hostname: string
34 - velociraptor_id?: string
35 - command: string
36 - artifact_name: "Windows.System.PowerShell" | "Windows.System.CmdShell" | "Linux.Sys.BashShell"
33 + hostname: string
34 + velociraptor_id?: string
35 + command: string
36 + artifact_name: "Windows.System.PowerShell" | "Windows.System.CmdShell" | "Linux.Sys.BashShell"
37 }
38
39 export interface QuarantineRequest {
40 - hostname: string
41 - velociraptor_id?: string
42 - action: "quarantine" | "remove_quarantine"
43 - artifact_name: "Windows.Remediation.Quarantine" | "Linux.Remediation.Quarantine"
40 + hostname: string
41 + velociraptor_id?: string
42 + action: "quarantine" | "remove_quarantine"
43 + artifact_name: "Windows.Remediation.Quarantine" | "Linux.Remediation.Quarantine"
44 }
45
46 export interface ArtifactRecommendationRequest {
47 - os: OsTypesFull
48 - prompt: string | object
47 + os: OsTypesFull
48 + prompt: string | object
49 }
50
51 export interface FileCollectionByAgentRequest {
52 - file: string
53 - root_disk: string
52 + file: string
53 + root_disk: string
54 }
55
56 export default {
57 - getAll(filters?: ArtifactsQuery) {
58 - let url = "/artifacts"
57 + getAll(filters?: ArtifactsQuery) {
58 + let url = "/artifacts"
59
60 - if (filters?.os) {
61 - url = `/artifacts/${filters.os}`
62 - }
63 - if (filters?.hostname) {
64 - url = `/artifacts/hostname/${filters.hostname}`
65 - }
60 + if (filters?.os) {
61 + url = `/artifacts/${filters.os}`
62 + }
63 + if (filters?.hostname) {
64 + url = `/artifacts/hostname/${filters.hostname}`
65 + }
66
67 - return HttpClient.get<FlaskBaseResponse & { artifacts: Artifact[] }>(url)
68 - },
69 - getByName(artifactName: string) {
70 - return HttpClient.get<FlaskBaseResponse & { artifacts: Artifact[] }>(`/artifacts/artifact/${artifactName}`)
71 - },
72 - collect(payload: CollectRequest) {
73 - return HttpClient.post<FlaskBaseResponse & { results: CollectResult[] }>(`/artifacts/collect`, payload)
74 - },
75 - command(payload: CommandRequest) {
76 - return HttpClient.post<FlaskBaseResponse & { results: CommandResult[] }>(`/artifacts/command`, payload)
77 - },
78 - quarantine(payload: QuarantineRequest) {
79 - return HttpClient.post<FlaskBaseResponse & { results: QuarantineResult[] }>(`/artifacts/quarantine`, payload)
80 - },
81 - getArtifactRecommendation(payload: ArtifactRecommendationRequest) {
82 - return HttpClient.post<FlaskBaseResponse & { recommendations: Recommendation[] }>(
83 - `/artifacts/velociraptor-artifact-recommendation`,
84 - payload
85 - )
86 - },
87 - getParameters(artifactName: string, parameterPrefix: string) {
88 - return HttpClient.get<
89 - FlaskBaseResponse & {
90 - artifact_name: string
91 - parameter_prefix: string
92 - matching_parameters: MatchingParameter[]
93 - total_matches: number
94 - }
95 - >(`/artifacts/artifact/${artifactName}/parameters/${parameterPrefix}`)
96 - },
97 - collectFileByAgentId(agentId: string, payload: FileCollectionByAgentRequest) {
98 - return HttpClient.post<FlaskBaseResponse & FileCollection>(`/artifacts/collect/file/agent/${agentId}`, payload)
99 - }
67 + return HttpClient.get<FlaskBaseResponse & { artifacts: Artifact[] }>(url)
68 + },
69 + getByName(artifactName: string) {
70 + return HttpClient.get<FlaskBaseResponse & { artifacts: Artifact[] }>(`/artifacts/artifact/${artifactName}`)
71 + },
72 + collect(payload: CollectRequest) {
73 + return HttpClient.post<FlaskBaseResponse & { results: CollectResult[] }>(`/artifacts/collect`, payload)
74 + },
75 + command(payload: CommandRequest) {
76 + return HttpClient.post<FlaskBaseResponse & { results: CommandResult[] }>(`/artifacts/command`, payload)
77 + },
78 + quarantine(payload: QuarantineRequest) {
79 + return HttpClient.post<FlaskBaseResponse & { results: QuarantineResult[] }>(`/artifacts/quarantine`, payload)
80 + },
81 + getArtifactRecommendation(payload: ArtifactRecommendationRequest) {
82 + return HttpClient.post<FlaskBaseResponse & { recommendations: Recommendation[] }>(
83 + `/artifacts/velociraptor-artifact-recommendation`,
84 + payload
85 + )
86 + },
87 + getParameters(artifactName: string, parameterPrefix: string) {
88 + return HttpClient.get<
89 + FlaskBaseResponse & {
90 + artifact_name: string
91 + parameter_prefix: string
92 + matching_parameters: MatchingParameter[]
93 + total_matches: number
94 + }
95 + >(`/artifacts/artifact/${artifactName}/parameters/${parameterPrefix}`)
96 + },
97 + collectFileByAgentId(agentId: string, payload: FileCollectionByAgentRequest) {
98 + return HttpClient.post<FlaskBaseResponse & FileCollection>(`/artifacts/collect/file/agent/${agentId}`, payload)
99 + }
100 }
frontend/src/api/endpoints/githubAudit.ts
+211 -220
@@ -1,228 +1,219 @@
1 import type {
2 - AvailableChecksResponse,
3 - DeleteResponse,
4 - GitHubAuditBaselineCreate,
5 - GitHubAuditBaselineResponse,
6 - GitHubAuditConfigCreate,
7 - GitHubAuditConfigResponse,
8 - GitHubAuditConfigUpdate,
9 - GitHubAuditExclusionCreate,
10 - GitHubAuditExclusionResponse,
11 - GitHubAuditExclusionUpdate,
12 - GitHubAuditReportListResponse,
13 - GitHubAuditReportResponse,
14 - GitHubAuditRequest,
15 - GitHubAuditResponse,
16 - GitHubAuditSummaryResponse
2 + AvailableChecksResponse,
3 + DeleteResponse,
4 + GitHubAuditBaselineCreate,
5 + GitHubAuditBaselineResponse,
6 + GitHubAuditConfigCreate,
7 + GitHubAuditConfigResponse,
8 + GitHubAuditConfigUpdate,
9 + GitHubAuditExclusionCreate,
10 + GitHubAuditExclusionResponse,
11 + GitHubAuditExclusionUpdate,
12 + GitHubAuditReportListResponse,
13 + GitHubAuditReportResponse,
14 + GitHubAuditRequest,
15 + GitHubAuditResponse,
16 + GitHubAuditSummaryResponse
17 } from "@/types/githubAudit.d"
18 import { HttpClient } from "../httpClient"
19
20 const BASE_PATH = "/github-audit"
21
22 +// TODO: refactor
23 export default {
23 - // ==================== Configuration Endpoints ====================
24 -
25 - /**
26 - * Create a new GitHub Audit configuration
27 - */
28 - createConfig(config: GitHubAuditConfigCreate, signal?: AbortSignal) {
29 - return HttpClient.post<GitHubAuditConfigResponse>(`${BASE_PATH}/config`, config, { signal })
30 - },
31 -
32 - /**
33 - * Get all GitHub Audit configurations
34 - */
35 - getConfigs(customerCode?: string, signal?: AbortSignal) {
36 - const params = customerCode ? { customer_code: customerCode } : undefined
37 - return HttpClient.get<GitHubAuditConfigResponse>(`${BASE_PATH}/config`, { params, signal })
38 - },
39 -
40 - /**
41 - * Get a specific GitHub Audit configuration by ID
42 - */
43 - getConfig(configId: number, signal?: AbortSignal) {
44 - return HttpClient.get<GitHubAuditConfigResponse>(`${BASE_PATH}/config/${configId}`, {
45 - signal
46 - })
47 - },
48 -
49 - /**
50 - * Update a GitHub Audit configuration
51 - */
52 - updateConfig(configId: number, config: GitHubAuditConfigUpdate, signal?: AbortSignal) {
53 - return HttpClient.put<GitHubAuditConfigResponse>(`${BASE_PATH}/config/${configId}`, config, {
54 - signal
55 - })
56 - },
57 -
58 - /**
59 - * Delete a GitHub Audit configuration
60 - */
61 - deleteConfig(configId: number, signal?: AbortSignal) {
62 - return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/config/${configId}`, { signal })
63 - },
64 -
65 - // ==================== Audit Execution Endpoints ====================
66 -
67 - /**
68 - * Run a GitHub audit using a saved configuration
69 - */
70 - runAuditFromConfig(configId: number, signal?: AbortSignal) {
71 - return HttpClient.post<GitHubAuditResponse>(`${BASE_PATH}/config/${configId}/audit`, {}, { signal })
72 - },
73 -
74 - /**
75 - * Run a one-time GitHub audit without saving config
76 - */
77 - runAuditAdhoc(request: GitHubAuditRequest, githubToken: string, signal?: AbortSignal) {
78 - return HttpClient.post<GitHubAuditResponse>(`${BASE_PATH}/audit`, request, {
79 - params: { github_token: githubToken },
80 - signal
81 - })
82 - },
83 -
84 - /**
85 - * Run a GitHub audit from config and return summary only
86 - */
87 - runAuditSummaryFromConfig(configId: number, signal?: AbortSignal) {
88 - return HttpClient.post<GitHubAuditSummaryResponse>(
89 - `${BASE_PATH}/config/${configId}/audit/summary`,
90 - {},
91 - { signal }
92 - )
93 - },
94 -
95 - // ==================== Report Endpoints ====================
96 -
97 - /**
98 - * Get list of GitHub audit reports with optional filters
99 - */
100 - getReports(
101 - options?: {
102 - customerCode?: string
103 - configId?: number
104 - organization?: string
105 - status?: string
106 - limit?: number
107 - offset?: number
108 - },
109 - signal?: AbortSignal
110 - ) {
111 - const params: Record<string, string | number> = {}
112 - if (options?.customerCode) params.customer_code = options.customerCode
113 - if (options?.configId) params.config_id = options.configId
114 - if (options?.organization) params.organization = options.organization
115 - if (options?.status) params.status = options.status
116 - if (options?.limit) params.limit = options.limit
117 - if (options?.offset) params.offset = options.offset
118 -
119 - return HttpClient.get<GitHubAuditReportListResponse>(`${BASE_PATH}/reports`, {
120 - params,
121 - signal
122 - })
123 - },
124 -
125 - /**
126 - * Get a specific GitHub audit report with full details
127 - */
128 - getReport(reportId: number, signal?: AbortSignal) {
129 - return HttpClient.get<GitHubAuditReportResponse>(`${BASE_PATH}/reports/${reportId}`, {
130 - signal
131 - })
132 - },
133 -
134 - /**
135 - * Delete a GitHub audit report
136 - */
137 - deleteReport(reportId: number, signal?: AbortSignal) {
138 - return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/reports/${reportId}`, { signal })
139 - },
140 -
141 - // ==================== Exclusion Endpoints ====================
142 -
143 - /**
144 - * Create an exclusion rule for a specific check
145 - */
146 - createExclusion(configId: number, exclusion: GitHubAuditExclusionCreate, signal?: AbortSignal) {
147 - return HttpClient.post<GitHubAuditExclusionResponse>(
148 - `${BASE_PATH}/config/${configId}/exclusions`,
149 - exclusion,
150 - { signal }
151 - )
152 - },
153 -
154 - /**
155 - * Get all exclusion rules for a configuration
156 - */
157 - getExclusions(configId: number, includeExpired?: boolean, signal?: AbortSignal) {
158 - const params = includeExpired !== undefined ? { include_expired: includeExpired } : undefined
159 - return HttpClient.get<GitHubAuditExclusionResponse>(
160 - `${BASE_PATH}/config/${configId}/exclusions`,
161 - { params, signal }
162 - )
163 - },
164 -
165 - /**
166 - * Update an exclusion rule
167 - */
168 - updateExclusion(
169 - exclusionId: number,
170 - exclusion: GitHubAuditExclusionUpdate,
171 - signal?: AbortSignal
172 - ) {
173 - return HttpClient.put<GitHubAuditExclusionResponse>(
174 - `${BASE_PATH}/exclusions/${exclusionId}`,
175 - exclusion,
176 - { signal }
177 - )
178 - },
179 -
180 - /**
181 - * Delete an exclusion rule
182 - */
183 - deleteExclusion(exclusionId: number, signal?: AbortSignal) {
184 - return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/exclusions/${exclusionId}`, {
185 - signal
186 - })
187 - },
188 -
189 - // ==================== Baseline Endpoints ====================
190 -
191 - /**
192 - * Create a baseline from a previous audit report
193 - */
194 - createBaseline(configId: number, baseline: GitHubAuditBaselineCreate, signal?: AbortSignal) {
195 - return HttpClient.post<GitHubAuditBaselineResponse>(
196 - `${BASE_PATH}/config/${configId}/baselines`,
197 - baseline,
198 - { signal }
199 - )
200 - },
201 -
202 - /**
203 - * Get all baselines for a configuration
204 - */
205 - getBaselines(configId: number, activeOnly?: boolean, signal?: AbortSignal) {
206 - const params = activeOnly !== undefined ? { active_only: activeOnly } : undefined
207 - return HttpClient.get<GitHubAuditBaselineResponse>(
208 - `${BASE_PATH}/config/${configId}/baselines`,
209 - { params, signal }
210 - )
211 - },
212 -
213 - /**
214 - * Delete a baseline
215 - */
216 - deleteBaseline(baselineId: number, signal?: AbortSignal) {
217 - return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/baselines/${baselineId}`, { signal })
218 - },
219 -
220 - // ==================== Reference Endpoints ====================
221 -
222 - /**
223 - * Get list of all available audit checks
224 - */
225 - getAvailableChecks(signal?: AbortSignal) {
226 - return HttpClient.get<AvailableChecksResponse>(`${BASE_PATH}/checks`, { signal })
227 - }
24 + // ==================== Configuration Endpoints ====================
25 +
26 + /**
27 + * Create a new GitHub Audit configuration
28 + */
29 + createConfig(config: GitHubAuditConfigCreate, signal?: AbortSignal) {
30 + return HttpClient.post<GitHubAuditConfigResponse>(`${BASE_PATH}/config`, config, { signal })
31 + },
32 +
33 + /**
34 + * Get all GitHub Audit configurations
35 + */
36 + getConfigs(customerCode?: string, signal?: AbortSignal) {
37 + const params = customerCode ? { customer_code: customerCode } : undefined
38 + return HttpClient.get<GitHubAuditConfigResponse>(`${BASE_PATH}/config`, { params, signal })
39 + },
40 +
41 + /**
42 + * Get a specific GitHub Audit configuration by ID
43 + */
44 + getConfig(configId: number, signal?: AbortSignal) {
45 + return HttpClient.get<GitHubAuditConfigResponse>(`${BASE_PATH}/config/${configId}`, {
46 + signal
47 + })
48 + },
49 +
50 + /**
51 + * Update a GitHub Audit configuration
52 + */
53 + updateConfig(configId: number, config: GitHubAuditConfigUpdate, signal?: AbortSignal) {
54 + return HttpClient.put<GitHubAuditConfigResponse>(`${BASE_PATH}/config/${configId}`, config, {
55 + signal
56 + })
57 + },
58 +
59 + /**
60 + * Delete a GitHub Audit configuration
61 + */
62 + deleteConfig(configId: number, signal?: AbortSignal) {
63 + return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/config/${configId}`, { signal })
64 + },
65 +
66 + // ==================== Audit Execution Endpoints ====================
67 +
68 + /**
69 + * Run a GitHub audit using a saved configuration
70 + */
71 + runAuditFromConfig(configId: number, signal?: AbortSignal) {
72 + return HttpClient.post<GitHubAuditResponse>(`${BASE_PATH}/config/${configId}/audit`, {}, { signal })
73 + },
74 +
75 + /**
76 + * Run a one-time GitHub audit without saving config
77 + */
78 + runAuditAdhoc(request: GitHubAuditRequest, githubToken: string, signal?: AbortSignal) {
79 + return HttpClient.post<GitHubAuditResponse>(`${BASE_PATH}/audit`, request, {
80 + params: { github_token: githubToken },
81 + signal
82 + })
83 + },
84 +
85 + /**
86 + * Run a GitHub audit from config and return summary only
87 + */
88 + runAuditSummaryFromConfig(configId: number, signal?: AbortSignal) {
89 + return HttpClient.post<GitHubAuditSummaryResponse>(
90 + `${BASE_PATH}/config/${configId}/audit/summary`,
91 + {},
92 + { signal }
93 + )
94 + },
95 +
96 + // ==================== Report Endpoints ====================
97 +
98 + /**
99 + * Get list of GitHub audit reports with optional filters
100 + */
101 + getReports(
102 + options?: {
103 + customerCode?: string
104 + configId?: number
105 + organization?: string
106 + status?: string
107 + limit?: number
108 + offset?: number
109 + },
110 + signal?: AbortSignal
111 + ) {
112 + const params: Record<string, string | number> = {}
113 + if (options?.customerCode) params.customer_code = options.customerCode
114 + if (options?.configId) params.config_id = options.configId
115 + if (options?.organization) params.organization = options.organization
116 + if (options?.status) params.status = options.status
117 + if (options?.limit) params.limit = options.limit
118 + if (options?.offset) params.offset = options.offset
119 +
120 + return HttpClient.get<GitHubAuditReportListResponse>(`${BASE_PATH}/reports`, {
121 + params,
122 + signal
123 + })
124 + },
125 +
126 + /**
127 + * Get a specific GitHub audit report with full details
128 + */
129 + getReport(reportId: number, signal?: AbortSignal) {
130 + return HttpClient.get<GitHubAuditReportResponse>(`${BASE_PATH}/reports/${reportId}`, {
131 + signal
132 + })
133 + },
134 +
135 + /**
136 + * Delete a GitHub audit report
137 + */
138 + deleteReport(reportId: number, signal?: AbortSignal) {
139 + return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/reports/${reportId}`, { signal })
140 + },
141 +
142 + // ==================== Exclusion Endpoints ====================
143 +
144 + /**
145 + * Create an exclusion rule for a specific check
146 + */
147 + createExclusion(configId: number, exclusion: GitHubAuditExclusionCreate, signal?: AbortSignal) {
148 + return HttpClient.post<GitHubAuditExclusionResponse>(`${BASE_PATH}/config/${configId}/exclusions`, exclusion, {
149 + signal
150 + })
151 + },
152 +
153 + /**
154 + * Get all exclusion rules for a configuration
155 + */
156 + getExclusions(configId: number, includeExpired?: boolean, signal?: AbortSignal) {
157 + const params = includeExpired !== undefined ? { include_expired: includeExpired } : undefined
158 + return HttpClient.get<GitHubAuditExclusionResponse>(`${BASE_PATH}/config/${configId}/exclusions`, {
159 + params,
160 + signal
161 + })
162 + },
163 +
164 + /**
165 + * Update an exclusion rule
166 + */
167 + updateExclusion(exclusionId: number, exclusion: GitHubAuditExclusionUpdate, signal?: AbortSignal) {
168 + return HttpClient.put<GitHubAuditExclusionResponse>(`${BASE_PATH}/exclusions/${exclusionId}`, exclusion, {
169 + signal
170 + })
171 + },
172 +
173 + /**
174 + * Delete an exclusion rule
175 + */
176 + deleteExclusion(exclusionId: number, signal?: AbortSignal) {
177 + return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/exclusions/${exclusionId}`, {
178 + signal
179 + })
180 + },
181 +
182 + // ==================== Baseline Endpoints ====================
183 +
184 + /**
185 + * Create a baseline from a previous audit report
186 + */
187 + createBaseline(configId: number, baseline: GitHubAuditBaselineCreate, signal?: AbortSignal) {
188 + return HttpClient.post<GitHubAuditBaselineResponse>(`${BASE_PATH}/config/${configId}/baselines`, baseline, {
189 + signal
190 + })
191 + },
192 +
193 + /**
194 + * Get all baselines for a configuration
195 + */
196 + getBaselines(configId: number, activeOnly?: boolean, signal?: AbortSignal) {
197 + const params = activeOnly !== undefined ? { active_only: activeOnly } : undefined
198 + return HttpClient.get<GitHubAuditBaselineResponse>(`${BASE_PATH}/config/${configId}/baselines`, {
199 + params,
200 + signal
201 + })
202 + },
203 +
204 + /**
205 + * Delete a baseline
206 + */
207 + deleteBaseline(baselineId: number, signal?: AbortSignal) {
208 + return HttpClient.delete<DeleteResponse>(`${BASE_PATH}/baselines/${baselineId}`, { signal })
209 + },
210 +
211 + // ==================== Reference Endpoints ====================
212 +
213 + /**
214 + * Get list of all available audit checks
215 + */
216 + getAvailableChecks(signal?: AbortSignal) {
217 + return HttpClient.get<AvailableChecksResponse>(`${BASE_PATH}/checks`, { signal })
218 + }
219 }
frontend/src/api/endpoints/incidentManagement/alerts.ts
+9 -8
@@ -38,6 +38,7 @@ export interface AlertIocPayload {
38 ioc_description: string
39 }
40
41 +// TODO: refactor
42 export default {
43 getAlertsList(args: Partial<AlertsQuery>, signal?: AbortSignal) {
44 let url = `/incidents/db_operations/alerts`
@@ -178,14 +179,14 @@ export default {
179 )
180 },
181 deleteAlertsByTitle(titleFilter: string) {
181 - return HttpClient.delete<
182 - FlaskBaseResponse & {
183 - deleted_alert_ids: number[]
184 - not_deleted_alert_ids: number[]
185 - message: string
186 - }
187 - >(`/incidents/db_operations/alerts/by-title/${encodeURIComponent(titleFilter)}`)
188 - },
182 + return HttpClient.delete<
183 + FlaskBaseResponse & {
184 + deleted_alert_ids: number[]
185 + not_deleted_alert_ids: number[]
186 + message: string
187 + }
188 + >(`/incidents/db_operations/alerts/by-title/${encodeURIComponent(titleFilter)}`)
189 + },
190 getAlertContext(alertContextId: number) {
191 return HttpClient.get<FlaskBaseResponse & { alert_context: AlertContext }>(
192 `/incidents/db_operations/alert/context/${alertContextId}`
frontend/src/api/endpoints/networkConnectors.ts
+89 -88
@@ -3,117 +3,118 @@ import type { CustomerNetworkConnector, NetworkConnector } from "@/types/network
3 import { HttpClient } from "../httpClient"
4
5 export interface NewNetworkConnector {
6 - customer_code: string
7 - customer_name: string
8 - network_connector_name: string
9 - network_connector_auth_keys: {
10 - auth_key_name: string
11 - auth_value: string
12 - }[]
6 + customer_code: string
7 + customer_name: string
8 + network_connector_name: string
9 + network_connector_auth_keys: {
10 + auth_key_name: string
11 + auth_value: string
12 + }[]
13 }
14
15 export interface NewNetworkConnectorPayload extends NewNetworkConnector {
16 - network_connector_config: {
17 - auth_type: string
18 - config_key: string
19 - config_value: string
20 - }
16 + network_connector_config: {
17 + auth_type: string
18 + config_key: string
19 + config_value: string
20 + }
21 }
22
23 export interface FortinetProvision {
24 - tcp_enabled: boolean
25 - udp_enabled: boolean
26 - hot_data_retention: number
27 - index_replicas: number
24 + tcp_enabled: boolean
25 + udp_enabled: boolean
26 + hot_data_retention: number
27 + index_replicas: number
28 }
29
30 export interface FortinetProvisionPayload extends FortinetProvision {
31 - customer_code: string
32 - integration_name: string
31 + customer_code: string
32 + integration_name: string
33 }
34
35 export interface SonicwallProvision {
36 - tcp_enabled: boolean
37 - hot_data_retention: number
38 - index_replicas: number
36 + tcp_enabled: boolean
37 + hot_data_retention: number
38 + index_replicas: number
39 }
40
41 export interface SonicwallProvisionPayload extends SonicwallProvision {
42 - customer_code: string
43 - integration_name: string
42 + customer_code: string
43 + integration_name: string
44 }
45
46 export interface SentinelOneProvision {
47 - tls_enabled: boolean
48 - hot_data_retention: number
49 - index_replicas: number
47 + tls_enabled: boolean
48 + hot_data_retention: number
49 + index_replicas: number
50 }
51
52 export interface SentinelOneProvisionPayload extends SentinelOneProvision {
53 - customer_code: string
54 - integration_name: string
53 + customer_code: string
54 + integration_name: string
55 }
56
57 +// TODO: refactor
58 export default {
58 - // #region Network Connector
59 - getAvailableNetworkConnectors() {
60 - return HttpClient.get<FlaskBaseResponse & { network_connector_keys: NetworkConnector[] }>(
61 - `/network_connectors/available_network_connectors`
62 - )
63 - },
64 - getCustomerNetworkConnectors(customerCode: string) {
65 - return HttpClient.get<FlaskBaseResponse & { available_network_connectors: CustomerNetworkConnector[] }>(
66 - `/network_connectors/customer_network_connectors/${customerCode}`
67 - )
68 - },
69 - createNetworkConnector(props: NewNetworkConnector) {
70 - const payload: NewNetworkConnectorPayload = {
71 - ...props,
72 - network_connector_config: {
73 - auth_type: "Fortinet",
74 - config_key: "firewall",
75 - config_value: "not applicable"
76 - }
77 - }
78 - return HttpClient.post<FlaskBaseResponse>(`/network_connectors/create_network_connector`, payload)
79 - },
80 - deleteNetworkConnector(customerCode: string, networkConnectorName: string) {
81 - return HttpClient.delete<FlaskBaseResponse>(`/network_connectors/delete_network_connector`, {
82 - data: { customer_code: customerCode, network_connector_name: networkConnectorName }
83 - })
84 - },
85 - decommissionNetworkConnector(customerCode: string, networkConnectorName: string) {
86 - return HttpClient.post<FlaskBaseResponse>(`/stack_decommissioning/graylog/decommission/network_connector`, {
87 - customer_code: customerCode,
88 - network_connector: networkConnectorName
89 - })
90 - },
91 - // #endregion
59 + // #region Network Connector
60 + getAvailableNetworkConnectors() {
61 + return HttpClient.get<FlaskBaseResponse & { network_connector_keys: NetworkConnector[] }>(
62 + `/network_connectors/available_network_connectors`
63 + )
64 + },
65 + getCustomerNetworkConnectors(customerCode: string) {
66 + return HttpClient.get<FlaskBaseResponse & { available_network_connectors: CustomerNetworkConnector[] }>(
67 + `/network_connectors/customer_network_connectors/${customerCode}`
68 + )
69 + },
70 + createNetworkConnector(props: NewNetworkConnector) {
71 + const payload: NewNetworkConnectorPayload = {
72 + ...props,
73 + network_connector_config: {
74 + auth_type: "Fortinet",
75 + config_key: "firewall",
76 + config_value: "not applicable"
77 + }
78 + }
79 + return HttpClient.post<FlaskBaseResponse>(`/network_connectors/create_network_connector`, payload)
80 + },
81 + deleteNetworkConnector(customerCode: string, networkConnectorName: string) {
82 + return HttpClient.delete<FlaskBaseResponse>(`/network_connectors/delete_network_connector`, {
83 + data: { customer_code: customerCode, network_connector_name: networkConnectorName }
84 + })
85 + },
86 + decommissionNetworkConnector(customerCode: string, networkConnectorName: string) {
87 + return HttpClient.post<FlaskBaseResponse>(`/stack_decommissioning/graylog/decommission/network_connector`, {
88 + customer_code: customerCode,
89 + network_connector: networkConnectorName
90 + })
91 + },
92 + // #endregion
93
93 - // #region Provision
94 - fortinetProvision(customerCode: string, networkConnectorName: string, props: FortinetProvision) {
95 - const payload: FortinetProvisionPayload = {
96 - ...props,
97 - customer_code: customerCode,
98 - integration_name: networkConnectorName
99 - }
100 - return HttpClient.post<FlaskBaseResponse>(`/stack_provisioning/graylog/provision/fortinet`, payload)
101 - },
102 - sonicwallProvision(customerCode: string, networkConnectorName: string, props: SonicwallProvision) {
103 - const payload: SonicwallProvisionPayload = {
104 - ...props,
105 - customer_code: customerCode,
106 - integration_name: networkConnectorName
107 - }
108 - return HttpClient.post<FlaskBaseResponse>(`/stack_provisioning/graylog/provision/sonicwall`, payload)
109 - },
110 - sentineloneProvision(customerCode: string, networkConnectorName: string, props: SentinelOneProvision) {
111 - const payload: SentinelOneProvisionPayload = {
112 - ...props,
113 - customer_code: customerCode,
114 - integration_name: networkConnectorName
115 - }
116 - return HttpClient.post<FlaskBaseResponse>(`/stack_provisioning/graylog/provision/sentinelone`, payload)
117 - }
118 - // #endregion
94 + // #region Provision
95 + fortinetProvision(customerCode: string, networkConnectorName: string, props: FortinetProvision) {
96 + const payload: FortinetProvisionPayload = {
97 + ...props,
98 + customer_code: customerCode,
99 + integration_name: networkConnectorName
100 + }
101 + return HttpClient.post<FlaskBaseResponse>(`/stack_provisioning/graylog/provision/fortinet`, payload)
102 + },
103 + sonicwallProvision(customerCode: string, networkConnectorName: string, props: SonicwallProvision) {
104 + const payload: SonicwallProvisionPayload = {
105 + ...props,
106 + customer_code: customerCode,
107 + integration_name: networkConnectorName
108 + }
109 + return HttpClient.post<FlaskBaseResponse>(`/stack_provisioning/graylog/provision/sonicwall`, payload)
110 + },
111 + sentineloneProvision(customerCode: string, networkConnectorName: string, props: SentinelOneProvision) {
112 + const payload: SentinelOneProvisionPayload = {
113 + ...props,
114 + customer_code: customerCode,
115 + integration_name: networkConnectorName
116 + }
117 + return HttpClient.post<FlaskBaseResponse>(`/stack_provisioning/graylog/provision/sentinelone`, payload)
118 + }
119 + // #endregion
120 }
frontend/src/api/endpoints/patchTuesday.ts
+79 -78
@@ -1,91 +1,92 @@
1 import type {
2 - AvailableCyclesResponse,
3 - PatchTuesdayPriorityQuery,
4 - PatchTuesdayQuery,
5 - PatchTuesdayResponse,
6 - PatchTuesdaySearchQuery,
7 - PatchTuesdaySummaryQuery,
8 - PatchTuesdaySummaryResponse
2 + AvailableCyclesResponse,
3 + PatchTuesdayPriorityQuery,
4 + PatchTuesdayQuery,
5 + PatchTuesdayResponse,
6 + PatchTuesdaySearchQuery,
7 + PatchTuesdaySummaryQuery,
8 + PatchTuesdaySummaryResponse
9 } from "@/types/patchTuesday.d"
10 import { HttpClient } from "../httpClient"
11
12 const BASE_PATH = "/patch-tuesday"
13
14 +// TODO: refactor
15 export default {
15 - /**
16 - * Get full Patch Tuesday data for a specific cycle
17 - * Includes all CVE x product records with EPSS and KEV enrichment
18 - */
19 - getPatchTuesday(query?: PatchTuesdayQuery, signal?: AbortSignal) {
20 - return HttpClient.get<PatchTuesdayResponse>(BASE_PATH, {
21 - params: {
22 - cycle: query?.cycle,
23 - include_epss: query?.include_epss !== false,
24 - include_kev: query?.include_kev !== false
25 - },
26 - signal
27 - })
28 - },
16 + /**
17 + * Get full Patch Tuesday data for a specific cycle
18 + * Includes all CVE x product records with EPSS and KEV enrichment
19 + */
20 + getPatchTuesday(query?: PatchTuesdayQuery, signal?: AbortSignal) {
21 + return HttpClient.get<PatchTuesdayResponse>(BASE_PATH, {
22 + params: {
23 + cycle: query?.cycle,
24 + include_epss: query?.include_epss !== false,
25 + include_kev: query?.include_kev !== false
26 + },
27 + signal
28 + })
29 + },
30
30 - /**
31 - * Get Patch Tuesday summary with top prioritized items
32 - * Lighter endpoint suitable for dashboards
33 - */
34 - getSummary(query?: PatchTuesdaySummaryQuery, signal?: AbortSignal) {
35 - return HttpClient.get<PatchTuesdaySummaryResponse>(`${BASE_PATH}/summary`, {
36 - params: {
37 - cycle: query?.cycle,
38 - include_epss: query?.include_epss !== false,
39 - include_kev: query?.include_kev !== false,
40 - top_n: query?.top_n || 25
41 - },
42 - signal
43 - })
44 - },
31 + /**
32 + * Get Patch Tuesday summary with top prioritized items
33 + * Lighter endpoint suitable for dashboards
34 + */
35 + getSummary(query?: PatchTuesdaySummaryQuery, signal?: AbortSignal) {
36 + return HttpClient.get<PatchTuesdaySummaryResponse>(`${BASE_PATH}/summary`, {
37 + params: {
38 + cycle: query?.cycle,
39 + include_epss: query?.include_epss !== false,
40 + include_kev: query?.include_kev !== false,
41 + top_n: query?.top_n || 25
42 + },
43 + signal
44 + })
45 + },
46
46 - /**
47 - * Get available Patch Tuesday cycles
48 - * Returns list of recent cycles and next Patch Tuesday date
49 - */
50 - getCycles(signal?: AbortSignal) {
51 - return HttpClient.get<AvailableCyclesResponse>(`${BASE_PATH}/cycles`, { signal })
52 - },
47 + /**
48 + * Get available Patch Tuesday cycles
49 + * Returns list of recent cycles and next Patch Tuesday date
50 + */
51 + getCycles(signal?: AbortSignal) {
52 + return HttpClient.get<AvailableCyclesResponse>(`${BASE_PATH}/cycles`, { signal })
53 + },
54
54 - /**
55 - * Search for specific CVEs in Patch Tuesday data
56 - */
57 - searchCVEs(query: PatchTuesdaySearchQuery, signal?: AbortSignal) {
58 - return HttpClient.get<PatchTuesdayResponse>(`${BASE_PATH}/search`, {
59 - params: {
60 - cve_ids: query.cve_ids,
61 - cycle: query.cycle
62 - },
63 - signal
64 - })
65 - },
55 + /**
56 + * Search for specific CVEs in Patch Tuesday data
57 + */
58 + searchCVEs(query: PatchTuesdaySearchQuery, signal?: AbortSignal) {
59 + return HttpClient.get<PatchTuesdayResponse>(`${BASE_PATH}/search`, {
60 + params: {
61 + cve_ids: query.cve_ids,
62 + cycle: query.cycle
63 + },
64 + signal
65 + })
66 + },
67
67 - /**
68 - * Get vulnerabilities filtered by priority level (P0, P1, P2, P3)
69 - */
70 - getByPriority(query: PatchTuesdayPriorityQuery, signal?: AbortSignal) {
71 - return HttpClient.get<PatchTuesdayResponse>(`${BASE_PATH}/priority/${query.priority_level}`, {
72 - params: {
73 - cycle: query.cycle,
74 - include_epss: query.include_epss !== false,
75 - include_kev: query.include_kev !== false
76 - },
77 - signal
78 - })
79 - },
68 + /**
69 + * Get vulnerabilities filtered by priority level (P0, P1, P2, P3)
70 + */
71 + getByPriority(query: PatchTuesdayPriorityQuery, signal?: AbortSignal) {
72 + return HttpClient.get<PatchTuesdayResponse>(`${BASE_PATH}/priority/${query.priority_level}`, {
73 + params: {
74 + cycle: query.cycle,
75 + include_epss: query.include_epss !== false,
76 + include_kev: query.include_kev !== false
77 + },
78 + signal
79 + })
80 + },
81
81 - /**
82 - * Get only CISA KEV (Known Exploited Vulnerabilities) items
83 - * These are actively exploited and should be prioritized immediately
84 - */
85 - getKEVItems(cycle?: string, signal?: AbortSignal) {
86 - return HttpClient.get<PatchTuesdayResponse>(`${BASE_PATH}/kev`, {
87 - params: { cycle },
88 - signal
89 - })
90 - }
82 + /**
83 + * Get only CISA KEV (Known Exploited Vulnerabilities) items
84 + * These are actively exploited and should be prioritized immediately
85 + */
86 + getKEVItems(cycle?: string, signal?: AbortSignal) {
87 + return HttpClient.get<PatchTuesdayResponse>(`${BASE_PATH}/kev`, {
88 + params: { cycle },
89 + signal
90 + })
91 + }
92 }
frontend/src/api/endpoints/snapshots.ts
+58 -57
@@ -1,63 +1,64 @@
1 import type {
2 - CreateSnapshotRequest,
3 - CreateSnapshotResponse,
4 - RestoreSnapshotRequest,
5 - RestoreSnapshotResponse,
6 - SnapshotListResponse,
7 - SnapshotRepositoryListResponse,
8 - SnapshotScheduleCreate,
9 - SnapshotScheduleListResponse,
10 - SnapshotScheduleOperationResponse,
11 - SnapshotScheduleUpdate,
12 - SnapshotStatusResponse
2 + CreateSnapshotRequest,
3 + CreateSnapshotResponse,
4 + RestoreSnapshotRequest,
5 + RestoreSnapshotResponse,
6 + SnapshotListResponse,
7 + SnapshotRepositoryListResponse,
8 + SnapshotScheduleCreate,
9 + SnapshotScheduleListResponse,
10 + SnapshotScheduleOperationResponse,
11 + SnapshotScheduleUpdate,
12 + SnapshotStatusResponse
13 } from "@/types/snapshots.d"
14 import { HttpClient } from "../httpClient"
15
16 +// TODO: refactor
17 export default {
17 - // Repository endpoints
18 - getRepositories() {
19 - return HttpClient.get<SnapshotRepositoryListResponse>("/snapshots/repositories")
20 - },
21 -
22 - // Snapshot endpoints
23 - getSnapshotStatus(repository?: string, snapshot?: string) {
24 - const params = new URLSearchParams()
25 - if (repository) params.append("repository", repository)
26 - if (snapshot) params.append("snapshot", snapshot)
27 - const queryString = params.toString()
28 - return HttpClient.get<SnapshotStatusResponse>(`/snapshots/status${queryString ? `?${queryString}` : ""}`)
29 - },
30 -
31 - listSnapshots(repository: string) {
32 - return HttpClient.get<SnapshotListResponse>(`/snapshots/repositories/${repository}/snapshots`)
33 - },
34 -
35 - createSnapshot(request: CreateSnapshotRequest) {
36 - return HttpClient.post<CreateSnapshotResponse>("/snapshots/create", request)
37 - },
38 -
39 - restoreSnapshot(request: RestoreSnapshotRequest) {
40 - return HttpClient.post<RestoreSnapshotResponse>("/snapshots/restore", request)
41 - },
42 -
43 - // Schedule endpoints
44 - getSchedules(enabledOnly: boolean = false) {
45 - return HttpClient.get<SnapshotScheduleListResponse>(`/snapshots/schedules?enabled_only=${enabledOnly}`)
46 - },
47 -
48 - getSchedule(scheduleId: number) {
49 - return HttpClient.get<SnapshotScheduleOperationResponse>(`/snapshots/schedules/${scheduleId}`)
50 - },
51 -
52 - createSchedule(request: SnapshotScheduleCreate) {
53 - return HttpClient.post<SnapshotScheduleOperationResponse>("/snapshots/schedules", request)
54 - },
55 -
56 - updateSchedule(scheduleId: number, request: SnapshotScheduleUpdate) {
57 - return HttpClient.put<SnapshotScheduleOperationResponse>(`/snapshots/schedules/${scheduleId}`, request)
58 - },
59 -
60 - deleteSchedule(scheduleId: number) {
61 - return HttpClient.delete<SnapshotScheduleOperationResponse>(`/snapshots/schedules/${scheduleId}`)
62 - }
18 + // Repository endpoints
19 + getRepositories() {
20 + return HttpClient.get<SnapshotRepositoryListResponse>("/snapshots/repositories")
21 + },
22 +
23 + // Snapshot endpoints
24 + getSnapshotStatus(repository?: string, snapshot?: string) {
25 + const params = new URLSearchParams()
26 + if (repository) params.append("repository", repository)
27 + if (snapshot) params.append("snapshot", snapshot)
28 + const queryString = params.toString()
29 + return HttpClient.get<SnapshotStatusResponse>(`/snapshots/status${queryString ? `?${queryString}` : ""}`)
30 + },
31 +
32 + listSnapshots(repository: string) {
33 + return HttpClient.get<SnapshotListResponse>(`/snapshots/repositories/${repository}/snapshots`)
34 + },
35 +
36 + createSnapshot(request: CreateSnapshotRequest) {
37 + return HttpClient.post<CreateSnapshotResponse>("/snapshots/create", request)
38 + },
39 +
40 + restoreSnapshot(request: RestoreSnapshotRequest) {
41 + return HttpClient.post<RestoreSnapshotResponse>("/snapshots/restore", request)
42 + },
43 +
44 + // Schedule endpoints
45 + getSchedules(enabledOnly: boolean = false) {
46 + return HttpClient.get<SnapshotScheduleListResponse>(`/snapshots/schedules?enabled_only=${enabledOnly}`)
47 + },
48 +
49 + getSchedule(scheduleId: number) {
50 + return HttpClient.get<SnapshotScheduleOperationResponse>(`/snapshots/schedules/${scheduleId}`)
51 + },
52 +
53 + createSchedule(request: SnapshotScheduleCreate) {
54 + return HttpClient.post<SnapshotScheduleOperationResponse>("/snapshots/schedules", request)
55 + },
56 +
57 + updateSchedule(scheduleId: number, request: SnapshotScheduleUpdate) {
58 + return HttpClient.put<SnapshotScheduleOperationResponse>(`/snapshots/schedules/${scheduleId}`, request)
59 + },
60 +
61 + deleteSchedule(scheduleId: number) {
62 + return HttpClient.delete<SnapshotScheduleOperationResponse>(`/snapshots/schedules/${scheduleId}`)
63 + }
64 }
frontend/src/api/endpoints/tagRbac.ts
+60 -51
@@ -1,56 +1,65 @@
1 import type { FlaskBaseResponse } from "@/types/flask.d"
2 -import type {
3 - AvailableTagsResponse,
4 - TagAccessSettings,
5 - TagAccessSettingsResponse,
6 - UserTagsResponse
7 -} from "@/types/incidentManagement/tags.d"
2 +import type { AlertTag, EffectiveAccessResponse, TagAccessSettings, TagAccessSettingsItem } from "@/types/tags"
3 import { HttpClient } from "../httpClient"
4
5 export default {
11 - // Get tag RBAC settings
12 - getSettings() {
13 - return HttpClient.get<TagAccessSettingsResponse>("/incidents/tag_access/settings")
14 - },
15 -
16 - // Update tag RBAC settings
17 - updateSettings(settings: TagAccessSettings) {
18 - return HttpClient.put<TagAccessSettingsResponse>("/incidents/tag_access/settings", settings)
19 - },
20 -
21 - // Get all available alert tags
22 - getAvailableTags() {
23 - return HttpClient.get<AvailableTagsResponse>("/incidents/tag_access/tags")
24 - },
25 -
26 - // Get tags assigned to a user (matches: GET /user/{user_id})
27 - getUserTags(userId: number) {
28 - return HttpClient.get<UserTagsResponse>(`/incidents/tag_access/user/${userId}`)
29 - },
30 -
31 - // Assign tags to a user (matches: PUT /user/{user_id})
32 - assignUserTags(userId: number, tagIds: number[]) {
33 - return HttpClient.put<UserTagsResponse>(`/incidents/tag_access/user/${userId}`, {
34 - tag_ids: tagIds
35 - })
36 - },
37 -
38 - // Add tags to a user (matches: POST /user/{user_id}/add)
39 - addUserTags(userId: number, tagIds: number[]) {
40 - return HttpClient.post<UserTagsResponse>(`/incidents/tag_access/user/${userId}/add`, {
41 - tag_ids: tagIds
42 - })
43 - },
44 -
45 - // Remove tags from a user (matches: POST /user/{user_id}/remove)
46 - removeUserTags(userId: number, tagIds: number[]) {
47 - return HttpClient.post<UserTagsResponse>(`/incidents/tag_access/user/${userId}/remove`, {
48 - tag_ids: tagIds
49 - })
50 - },
51 -
52 - // Get current user's effective access
53 - getMyEffectiveAccess() {
54 - return HttpClient.get<FlaskBaseResponse>("/incidents/tag_access/me")
55 - }
6 + // Get tag RBAC settings
7 + getSettings() {
8 + return HttpClient.get<FlaskBaseResponse & { settings: TagAccessSettingsItem }>("/incidents/tag_access/settings")
9 + },
10 +
11 + // Update tag RBAC settings
12 + updateSettings(settings: TagAccessSettings) {
13 + return HttpClient.put<FlaskBaseResponse & { settings: TagAccessSettingsItem }>(
14 + "/incidents/tag_access/settings",
15 + settings
16 + )
17 + },
18 +
19 + // Get all available alert tags
20 + getAvailableTags() {
21 + return HttpClient.get<FlaskBaseResponse & { tags: AlertTag[] }>("/incidents/tag_access/tags")
22 + },
23 +
24 + // Get tags assigned to a user (matches: GET /user/{user_id})
25 + getUserTags(userId: number) {
26 + return HttpClient.get<FlaskBaseResponse & { user_id: number; username: string; accessible_tags: AlertTag[] }>(
27 + `/incidents/tag_access/user/${userId}`
28 + )
29 + },
30 +
31 + // Assign tags to a user (matches: PUT /user/{user_id})
32 + assignUserTags(userId: number, tagIds: number[]) {
33 + return HttpClient.put<FlaskBaseResponse & { user_id: number; username: string; accessible_tags: AlertTag[] }>(
34 + `/incidents/tag_access/user/${userId}`,
35 + {
36 + tag_ids: tagIds
37 + }
38 + )
39 + },
40 +
41 + // Add tags to a user (matches: POST /user/{user_id}/add)
42 + addUserTags(userId: number, tagIds: number[]) {
43 + return HttpClient.post<FlaskBaseResponse & { user_id: number; username: string; accessible_tags: AlertTag[] }>(
44 + `/incidents/tag_access/user/${userId}/add`,
45 + {
46 + tag_ids: tagIds
47 + }
48 + )
49 + },
50 +
51 + // Remove tags from a user (matches: POST /user/{user_id}/remove)
52 + removeUserTags(userId: number, tagIds: number[]) {
53 + return HttpClient.post<FlaskBaseResponse & { user_id: number; username: string; accessible_tags: AlertTag[] }>(
54 + `/incidents/tag_access/user/${userId}/remove`,
55 + {
56 + tag_ids: tagIds
57 + }
58 + )
59 + },
60 +
61 + // Get current user's effective access
62 + getMyEffectiveAccess() {
63 + return HttpClient.get<FlaskBaseResponse & EffectiveAccessResponse>("/incidents/tag_access/me")
64 + }
65 }
frontend/src/api/endpoints/vulnerabilities.ts
+67 -66
@@ -1,78 +1,79 @@
1 import type {
2 - VulnerabilityReportDeleteResponse,
3 - VulnerabilityReportGenerateRequest,
4 - VulnerabilityReportGenerateResponse,
5 - VulnerabilityReportListResponse,
6 - VulnerabilitySearchQuery,
2 + VulnerabilityReportDeleteResponse,
3 + VulnerabilityReportGenerateRequest,
4 + VulnerabilityReportGenerateResponse,
5 + VulnerabilityReportListResponse,
6 + VulnerabilitySearchQuery,
7 VulnerabilitySearchResponse
8 } from "@/types/vulnerabilities.d"
9 import { HttpClient } from "../httpClient"
10
11 +// TODO: refactor
12 export default {
12 - /**
13 - * Search vulnerabilities directly from Wazuh indexer with filtering and pagination
14 - */
15 - searchVulnerabilities(query?: VulnerabilitySearchQuery, signal?: AbortSignal) {
16 - return HttpClient.get<VulnerabilitySearchResponse>(`/vulnerabilities/search`, {
17 - params: {
18 - customer_code: query?.customer_code,
19 - agent_name: query?.agent_name,
20 - severity: query?.severity,
21 - cve_id: query?.cve_id,
22 - package_name: query?.package_name,
23 - page: query?.page || 1,
24 - page_size: query?.page_size || 50,
25 - include_epss: query?.include_epss !== false
26 - },
27 - signal
28 - })
29 - },
13 + /**
14 + * Search vulnerabilities directly from Wazuh indexer with filtering and pagination
15 + */
16 + searchVulnerabilities(query?: VulnerabilitySearchQuery, signal?: AbortSignal) {
17 + return HttpClient.get<VulnerabilitySearchResponse>(`/vulnerabilities/search`, {
18 + params: {
19 + customer_code: query?.customer_code,
20 + agent_name: query?.agent_name,
21 + severity: query?.severity,
22 + cve_id: query?.cve_id,
23 + package_name: query?.package_name,
24 + page: query?.page || 1,
25 + page_size: query?.page_size || 50,
26 + include_epss: query?.include_epss !== false
27 + },
28 + signal
29 + })
30 + },
31
31 - /**
32 - * Generate a vulnerability report (synchronous)
33 - */
34 - generateReport(request: VulnerabilityReportGenerateRequest) {
35 - return HttpClient.post<VulnerabilityReportGenerateResponse>(`/vulnerabilities/reports/generate`, request)
36 - },
32 + /**
33 + * Generate a vulnerability report (synchronous)
34 + */
35 + generateReport(request: VulnerabilityReportGenerateRequest) {
36 + return HttpClient.post<VulnerabilityReportGenerateResponse>(`/vulnerabilities/reports/generate`, request)
37 + },
38
38 - /**
39 - * Generate a vulnerability report (background task)
40 - */
41 - generateReportBackground(request: VulnerabilityReportGenerateRequest) {
42 - return HttpClient.post<{
43 - success: boolean
44 - message: string
45 - report_id: number
46 - report_name: string
47 - customer_code: string
48 - status: string
49 - check_status_url: string
50 - download_url: string
51 - }>(`/vulnerabilities/reports/generate/background`, request)
52 - },
39 + /**
40 + * Generate a vulnerability report (background task)
41 + */
42 + generateReportBackground(request: VulnerabilityReportGenerateRequest) {
43 + return HttpClient.post<{
44 + success: boolean
45 + message: string
46 + report_id: number
47 + report_name: string
48 + customer_code: string
49 + status: string
50 + check_status_url: string
51 + download_url: string
52 + }>(`/vulnerabilities/reports/generate/background`, request)
53 + },
54
54 - /**
55 - * List all vulnerability reports
56 - */
57 - listReports(customer_code?: string) {
58 - return HttpClient.get<VulnerabilityReportListResponse>(`/vulnerabilities/reports`, {
59 - params: customer_code ? { customer_code } : undefined
60 - })
61 - },
55 + /**
56 + * List all vulnerability reports
57 + */
58 + listReports(customer_code?: string) {
59 + return HttpClient.get<VulnerabilityReportListResponse>(`/vulnerabilities/reports`, {
60 + params: customer_code ? { customer_code } : undefined
61 + })
62 + },
63
63 - /**
64 - * Download a vulnerability report
65 - */
66 - downloadReport(reportId: number) {
67 - return HttpClient.get<Blob>(`/vulnerabilities/reports/${reportId}/download`, {
68 - responseType: "blob"
69 - })
70 - },
64 + /**
65 + * Download a vulnerability report
66 + */
67 + downloadReport(reportId: number) {
68 + return HttpClient.get<Blob>(`/vulnerabilities/reports/${reportId}/download`, {
69 + responseType: "blob"
70 + })
71 + },
72
72 - /**
73 - * Delete a vulnerability report
74 - */
75 - deleteReport(reportId: number) {
76 - return HttpClient.delete<VulnerabilityReportDeleteResponse>(`/vulnerabilities/reports/${reportId}`)
77 - }
73 + /**
74 + * Delete a vulnerability report
75 + */
76 + deleteReport(reportId: number) {
77 + return HttpClient.delete<VulnerabilityReportDeleteResponse>(`/vulnerabilities/reports/${reportId}`)
78 + }
79 }
frontend/src/api/endpoints/wazuh/indices.ts
+13 -12
@@ -2,6 +2,7 @@ import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { ClusterHealth, IndexAllocation, IndexShard, IndexStats } from "@/types/indices.d"
3 import { HttpClient } from "../../httpClient"
4
5 +// TODO: refactor
6 export default {
7 getAllocation() {
8 return HttpClient.get<FlaskBaseResponse & { node_allocation: IndexAllocation[] }>("/wazuh_indexer/allocation")
@@ -16,16 +17,16 @@ export default {
17 return HttpClient.get<FlaskBaseResponse & { cluster_health: ClusterHealth }>("/wazuh_indexer/health")
18 },
19 getIndicesSizePerCustomer() {
19 - return HttpClient.get<{
20 - customer_sizes: {
21 - customer: string
22 - total_size_bytes: number
23 - total_size_human: string
24 - index_count: number
25 - indices: string[]
26 - }[]
27 - message: string
28 - success: boolean
29 - }>(`/wazuh_indexer/indices/size-per-customer`)
30 -}
20 + return HttpClient.get<{
21 + customer_sizes: {
22 + customer: string
23 + total_size_bytes: number
24 + total_size_human: string
25 + index_count: number
26 + indices: string[]
27 + }[]
28 + message: string
29 + success: boolean
30 + }>(`/wazuh_indexer/indices/size-per-customer`)
31 + }
32 }
frontend/src/app-layouts/VerticalNav/SidebarFooter.vue
+1 -1
@@ -42,7 +42,7 @@ const menuOptions = ref([
42 name: "Logout"
43 }
44 },
45 - { default: () => "logout" }
45 + { default: () => "Logout" }
46 ),
47 key: "Logout",
48 icon: renderIcon(LogoutIcon)
frontend/src/app-layouts/common/GlobalListener.vue
+2
@@ -5,9 +5,11 @@
5 <script setup lang="ts">
6 import { useMessage, useNotification } from "naive-ui"
7 import { useGlobalActions } from "@/composables/useGlobalActions"
8 +import { useLoadingBarSetup } from "@/composables/useLoadingBarSetup"
9
10 const message = useMessage()
11 const notification = useNotification()
12
13 useGlobalActions().init({ message, notification })
14 +useLoadingBarSetup()
15 </script>
frontend/src/app-layouts/common/Navbar/items.tsx
+474 -473
@@ -20,67 +20,68 @@ const ReportCreationIcon = "carbon:report-data"
20 const SchedulerIcon = "material-symbols:autoplay"
21 const CustomerPortalIcon = "streamline-ultimate:coding-apps-website-apps-browser"
22
23 +// TODO: refactor
24 export default function getItems(): MenuMixedOption[] {
24 - return [
25 - {
26 - label: () =>
27 - h(
28 - RouterLink,
29 - {
30 - to: {
31 - name: "Overview"
32 - }
33 - },
34 - { default: () => "Overview" }
35 - ),
36 - key: "Overview",
37 - icon: renderIcon(OverviewIcon)
38 - },
39 - {
40 - label: "Incident Management",
41 - key: "IncidentManagement",
42 - icon: renderIcon(IncidentManagementIcon),
43 - children: [
44 - {
45 - label: () =>
46 - h(
47 - RouterLink,
48 - {
49 - to: {
50 - name: "IncidentManagement-Sources"
51 - }
52 - },
53 - { default: () => "Sources" }
54 - ),
55 - key: "IncidentManagement-Sources"
56 - },
57 - {
58 - label: () =>
59 - h(
60 - RouterLink,
61 - {
62 - to: {
63 - name: "IncidentManagement-Alerts"
64 - }
65 - },
66 - { default: () => "Alerts" }
67 - ),
68 - key: "IncidentManagement-Alerts"
69 - },
70 - {
71 - label: () =>
72 - h(
73 - RouterLink,
74 - {
75 - to: {
76 - name: "IncidentManagement-Cases"
77 - }
78 - },
79 - { default: () => "Cases" }
80 - ),
81 - key: "IncidentManagement-Cases"
82 - }
83 - /*
25 + return [
26 + {
27 + label: () =>
28 + h(
29 + RouterLink,
30 + {
31 + to: {
32 + name: "Overview"
33 + }
34 + },
35 + { default: () => "Overview" }
36 + ),
37 + key: "Overview",
38 + icon: renderIcon(OverviewIcon)
39 + },
40 + {
41 + label: "Incident Management",
42 + key: "IncidentManagement",
43 + icon: renderIcon(IncidentManagementIcon),
44 + children: [
45 + {
46 + label: () =>
47 + h(
48 + RouterLink,
49 + {
50 + to: {
51 + name: "IncidentManagement-Sources"
52 + }
53 + },
54 + { default: () => "Sources" }
55 + ),
56 + key: "IncidentManagement-Sources"
57 + },
58 + {
59 + label: () =>
60 + h(
61 + RouterLink,
62 + {
63 + to: {
64 + name: "IncidentManagement-Alerts"
65 + }
66 + },
67 + { default: () => "Alerts" }
68 + ),
69 + key: "IncidentManagement-Alerts"
70 + },
71 + {
72 + label: () =>
73 + h(
74 + RouterLink,
75 + {
76 + to: {
77 + name: "IncidentManagement-Cases"
78 + }
79 + },
80 + { default: () => "Cases" }
81 + ),
82 + key: "IncidentManagement-Cases"
83 + }
84 + /*
85 {
86 label: () =>
87 h(
@@ -95,419 +96,419 @@ export default function getItems(): MenuMixedOption[] {
96 key: "IncidentManagement-Sigma"
97 }
98 */
98 - ]
99 - },
100 - {
101 - label: "Alerts",
102 - key: "Alerts",
103 - icon: renderIcon(AlertsIcon),
104 - children: [
105 - {
106 - label: () =>
107 - h(
108 - RouterLink,
109 - {
110 - to: {
111 - name: "Alerts-SIEM"
112 - }
113 - },
114 - { default: () => "SIEM" }
115 - ),
116 - key: "Alerts-SIEM"
117 - },
118 - {
119 - label: () =>
120 - h(
121 - RouterLink,
122 - {
123 - to: {
124 - name: "Alerts-Mitre"
125 - }
126 - },
127 - { default: () => "MITRE ATT&CK" }
128 - ),
129 - key: "Alerts-Mitre"
130 - },
131 - {
132 - label: () =>
133 - h(
134 - RouterLink,
135 - {
136 - to: {
137 - name: "Alerts-AtomicRedTeam"
138 - }
139 - },
140 - { default: () => "Atomic Red Team" }
141 - ),
142 - key: "Alerts-AtomicRedTeam"
143 - }
144 - ]
145 - },
146 - {
147 - label: () =>
148 - h(
149 - RouterLink,
150 - {
151 - to: {
152 - name: "Artifacts"
153 - }
154 - },
155 - { default: () => "Artifacts" }
156 - ),
157 - key: "Artifacts",
158 - icon: renderIcon(ArtifactsIcon)
159 - },
160 - {
161 - label: () =>
162 - h(
163 - RouterLink,
164 - {
165 - to: {
166 - name: "Customers"
167 - }
168 - },
169 - { default: () => "Customers" }
170 - ),
171 - key: "Customers",
172 - icon: renderIcon(CustomersIcon)
173 - },
174 - {
175 - label: "Agents",
176 - key: "Agents",
177 - icon: renderIcon(AgentsIcon),
178 - children: [
179 - {
180 - label: () =>
181 - h(
182 - RouterLink,
183 - {
184 - to: {
185 - name: "Agents"
186 - }
187 - },
188 - { default: () => "Agents list" }
189 - ),
190 - key: "Agents"
191 - },
192 - {
193 - label: () =>
194 - h(
195 - RouterLink,
196 - {
197 - to: {
198 - name: "Groups"
199 - }
200 - },
201 - { default: () => "Groups" }
202 - ),
203 - key: "Groups"
204 - },
205 - {
206 - label: () =>
207 - h(
208 - RouterLink,
209 - {
210 - to: {
211 - name: "SysmonConfig"
212 - }
213 - },
214 - { default: () => "Sysmon Config" }
215 - ),
216 - key: "SysmonConfig"
217 - },
218 - {
219 - label: () =>
220 - h(
221 - RouterLink,
222 - {
223 - to: {
224 - name: "DetectionRules"
225 - }
226 - },
227 - { default: () => "Detection Rules" }
228 - ),
229 - key: "DetectionRules"
230 - },
231 - {
232 - label: () =>
233 - h(
234 - RouterLink,
235 - {
236 - to: {
237 - name: "CopilotActions"
238 - }
239 - },
240 - { default: () => "CoPilot Actions" }
241 - ),
242 - key: "CopilotActions"
243 - },
244 - {
245 - label: () =>
246 - h(
247 - RouterLink,
248 - {
249 - to: {
250 - name: "VulnerabilityOverview"
251 - }
252 - },
253 - { default: () => "Vulnerability Overview" }
254 - ),
255 - key: "VulnerabilityOverview"
256 - },
99 + ]
100 + },
101 + {
102 + label: "Alerts",
103 + key: "Alerts",
104 + icon: renderIcon(AlertsIcon),
105 + children: [
106 {
258 - label: () =>
259 - h(
260 - RouterLink,
261 - {
262 - to: {
263 - name: "PatchTuesday"
264 - }
265 - },
266 - { default: () => "Patch Tuesday" }
267 - ),
268 - key: "PatchTuesday"
269 - },
270 - {
271 - label: () =>
272 - h(
273 - RouterLink,
274 - {
275 - to: {
276 - name: "ScaOverview"
277 - }
278 - },
279 - { default: () => "SCA Overview" }
280 - ),
281 - key: "ScaOverview"
282 - }
283 - ]
284 - },
285 - {
286 - label: "Report Creation",
287 - key: "ReportCreation",
288 - icon: renderIcon(ReportCreationIcon),
289 - children: [
290 - {
291 - label: () =>
292 - h(
293 - RouterLink,
294 - {
295 - to: {
296 - name: "ReportCreation"
297 - }
298 - },
299 - { default: () => "General Reports" }
300 - ),
301 - key: "ReportCreation"
302 - },
303 - {
304 - label: () =>
305 - h(
306 - RouterLink,
307 - {
308 - to: {
309 - name: "VulnerabilityReports"
310 - }
311 - },
312 - { default: () => "Vulnerability Reports" }
313 - ),
314 - key: "VulnerabilityReports"
315 - },
107 + label: () =>
108 + h(
109 + RouterLink,
110 + {
111 + to: {
112 + name: "Alerts-SIEM"
113 + }
114 + },
115 + { default: () => "SIEM" }
116 + ),
117 + key: "Alerts-SIEM"
118 + },
119 {
317 - label: () =>
318 - h(
319 - RouterLink,
320 - {
321 - to: {
322 - name: "SCAReports"
323 - }
324 - },
325 - { default: () => "SCA Reports" }
326 - ),
327 - key: "SCAReports"
328 - }
329 - ]
330 - },
331 - {
332 - label: () =>
333 - h(
334 - RouterLink,
335 - {
336 - to: {
337 - name: "Healthcheck"
338 - }
339 - },
340 - { default: () => "Healthcheck" }
341 - ),
342 - key: "Healthcheck",
343 - icon: renderIcon(HealthcheckIcon)
344 - },
345 - {
346 - label: "Indices",
347 - key: "IndicesMenu",
348 - icon: renderIcon(IndiciesIcon),
349 - children: [
350 - {
351 - label: () =>
352 - h(
353 - RouterLink,
354 - {
355 - to: {
356 - name: "Indices"
357 - }
358 - },
359 - { default: () => "Index Management" }
360 - ),
361 - key: "Indices"
362 - },
363 - {
364 - label: () =>
365 - h(
366 - RouterLink,
367 - {
368 - to: {
369 - name: "Snapshots"
370 - }
371 - },
372 - { default: () => "Snapshot & Restore" }
373 - ),
374 - key: "Snapshots"
375 - }
376 - ]
377 - },
378 - {
379 - label: "Graylog",
380 - key: "Graylog",
381 - icon: renderIcon(GraylogIcon),
382 - children: [
383 - {
384 - label: () =>
385 - h(
386 - RouterLink,
387 - {
388 - to: {
389 - name: "Graylog-Management"
390 - }
391 - },
392 - { default: () => "Management" }
393 - ),
394 - key: "Graylog-Management"
395 - },
396 - {
397 - label: () =>
398 - h(
399 - RouterLink,
400 - {
401 - to: {
402 - name: "Graylog-Metrics"
403 - }
404 - },
405 - { default: () => "Metrics" }
406 - ),
407 - key: "Graylog-Metrics"
408 - },
409 - {
410 - label: () =>
411 - h(
412 - RouterLink,
413 - {
414 - to: {
415 - name: "Graylog-Pipelines"
416 - }
417 - },
418 - { default: () => "Pipelines" }
419 - ),
420 - key: "Graylog-Pipelines"
421 - }
422 - ]
423 - },
424 - {
425 - label: () =>
426 - h(
427 - RouterLink,
428 - {
429 - to: {
430 - name: "Connectors"
431 - }
432 - },
433 - { default: () => "Connectors" }
434 - ),
435 - key: "Connectors",
436 - icon: renderIcon(ConnectorsIcon)
437 - },
438 - {
439 - label: "External Services",
440 - key: "ExternalServices",
441 - icon: renderIcon(ExternalServicesIcon),
442 - children: [
443 - {
444 - label: () =>
445 - h(
446 - RouterLink,
447 - {
448 - to: {
449 - name: "ExternalServices-ThirdPartyIntegrations"
450 - }
451 - },
452 - { default: () => "3rd Party Integrations" }
453 - ),
454 - key: "ExternalServices-ThirdPartyIntegrations"
455 - },
456 - {
457 - label: () =>
458 - h(
459 - RouterLink,
460 - {
461 - to: {
462 - name: "ExternalServices-NetworkConnectors"
463 - }
464 - },
465 - { default: () => "Network Connectors" }
466 - ),
467 - key: "ExternalServices-NetworkConnectors"
468 - },
469 - {
470 - label: () =>
471 - h(
472 - RouterLink,
473 - {
474 - to: {
475 - name: "ExternalServices-SingulAppAuth"
476 - }
477 - },
478 - { default: () => "Singul App Auth" }
479 - ),
480 - key: "ExternalServices-SingulAppAuth"
481 - }
482 - ]
483 - },
484 - {
485 - label: () =>
486 - h(
487 - RouterLink,
488 - {
489 - to: {
490 - name: "Scheduler"
491 - }
492 - },
493 - { default: () => "Scheduler" }
494 - ),
495 - key: "Scheduler",
496 - icon: renderIcon(SchedulerIcon)
497 - },
498 - {
499 - label: () =>
500 - h(
501 - RouterLink,
502 - {
503 - to: {
504 - name: "CustomerPortal"
505 - }
506 - },
507 - { default: () => "Customer Portal" }
508 - ),
509 - key: "CustomerPortal",
510 - icon: renderIcon(CustomerPortalIcon)
511 - }
512 - ]
120 + label: () =>
121 + h(
122 + RouterLink,
123 + {
124 + to: {
125 + name: "Alerts-Mitre"
126 + }
127 + },
128 + { default: () => "MITRE ATT&CK" }
129 + ),
130 + key: "Alerts-Mitre"
131 + },
132 + {
133 + label: () =>
134 + h(
135 + RouterLink,
136 + {
137 + to: {
138 + name: "Alerts-AtomicRedTeam"
139 + }
140 + },
141 + { default: () => "Atomic Red Team" }
142 + ),
143 + key: "Alerts-AtomicRedTeam"
144 + }
145 + ]
146 + },
147 + {
148 + label: () =>
149 + h(
150 + RouterLink,
151 + {
152 + to: {
153 + name: "Artifacts"
154 + }
155 + },
156 + { default: () => "Artifacts" }
157 + ),
158 + key: "Artifacts",
159 + icon: renderIcon(ArtifactsIcon)
160 + },
161 + {
162 + label: () =>
163 + h(
164 + RouterLink,
165 + {
166 + to: {
167 + name: "Customers"
168 + }
169 + },
170 + { default: () => "Customers" }
171 + ),
172 + key: "Customers",
173 + icon: renderIcon(CustomersIcon)
174 + },
175 + {
176 + label: "Agents",
177 + key: "Agents",
178 + icon: renderIcon(AgentsIcon),
179 + children: [
180 + {
181 + label: () =>
182 + h(
183 + RouterLink,
184 + {
185 + to: {
186 + name: "Agents"
187 + }
188 + },
189 + { default: () => "Agents list" }
190 + ),
191 + key: "Agents"
192 + },
193 + {
194 + label: () =>
195 + h(
196 + RouterLink,
197 + {
198 + to: {
199 + name: "Groups"
200 + }
201 + },
202 + { default: () => "Groups" }
203 + ),
204 + key: "Groups"
205 + },
206 + {
207 + label: () =>
208 + h(
209 + RouterLink,
210 + {
211 + to: {
212 + name: "SysmonConfig"
213 + }
214 + },
215 + { default: () => "Sysmon Config" }
216 + ),
217 + key: "SysmonConfig"
218 + },
219 + {
220 + label: () =>
221 + h(
222 + RouterLink,
223 + {
224 + to: {
225 + name: "DetectionRules"
226 + }
227 + },
228 + { default: () => "Detection Rules" }
229 + ),
230 + key: "DetectionRules"
231 + },
232 + {
233 + label: () =>
234 + h(
235 + RouterLink,
236 + {
237 + to: {
238 + name: "CopilotActions"
239 + }
240 + },
241 + { default: () => "CoPilot Actions" }
242 + ),
243 + key: "CopilotActions"
244 + },
245 + {
246 + label: () =>
247 + h(
248 + RouterLink,
249 + {
250 + to: {
251 + name: "VulnerabilityOverview"
252 + }
253 + },
254 + { default: () => "Vulnerability Overview" }
255 + ),
256 + key: "VulnerabilityOverview"
257 + },
258 + {
259 + label: () =>
260 + h(
261 + RouterLink,
262 + {
263 + to: {
264 + name: "PatchTuesday"
265 + }
266 + },
267 + { default: () => "Patch Tuesday" }
268 + ),
269 + key: "PatchTuesday"
270 + },
271 + {
272 + label: () =>
273 + h(
274 + RouterLink,
275 + {
276 + to: {
277 + name: "ScaOverview"
278 + }
279 + },
280 + { default: () => "SCA Overview" }
281 + ),
282 + key: "ScaOverview"
283 + }
284 + ]
285 + },
286 + {
287 + label: "Report Creation",
288 + key: "ReportCreation",
289 + icon: renderIcon(ReportCreationIcon),
290 + children: [
291 + {
292 + label: () =>
293 + h(
294 + RouterLink,
295 + {
296 + to: {
297 + name: "ReportCreation"
298 + }
299 + },
300 + { default: () => "General Reports" }
301 + ),
302 + key: "ReportCreation"
303 + },
304 + {
305 + label: () =>
306 + h(
307 + RouterLink,
308 + {
309 + to: {
310 + name: "VulnerabilityReports"
311 + }
312 + },
313 + { default: () => "Vulnerability Reports" }
314 + ),
315 + key: "VulnerabilityReports"
316 + },
317 + {
318 + label: () =>
319 + h(
320 + RouterLink,
321 + {
322 + to: {
323 + name: "SCAReports"
324 + }
325 + },
326 + { default: () => "SCA Reports" }
327 + ),
328 + key: "SCAReports"
329 + }
330 + ]
331 + },
332 + {
333 + label: () =>
334 + h(
335 + RouterLink,
336 + {
337 + to: {
338 + name: "Healthcheck"
339 + }
340 + },
341 + { default: () => "Healthcheck" }
342 + ),
343 + key: "Healthcheck",
344 + icon: renderIcon(HealthcheckIcon)
345 + },
346 + {
347 + label: "Indices",
348 + key: "IndicesMenu",
349 + icon: renderIcon(IndiciesIcon),
350 + children: [
351 + {
352 + label: () =>
353 + h(
354 + RouterLink,
355 + {
356 + to: {
357 + name: "Indices"
358 + }
359 + },
360 + { default: () => "Index Management" }
361 + ),
362 + key: "Indices"
363 + },
364 + {
365 + label: () =>
366 + h(
367 + RouterLink,
368 + {
369 + to: {
370 + name: "Snapshots"
371 + }
372 + },
373 + { default: () => "Snapshot & Restore" }
374 + ),
375 + key: "Snapshots"
376 + }
377 + ]
378 + },
379 + {
380 + label: "Graylog",
381 + key: "Graylog",
382 + icon: renderIcon(GraylogIcon),
383 + children: [
384 + {
385 + label: () =>
386 + h(
387 + RouterLink,
388 + {
389 + to: {
390 + name: "Graylog-Management"
391 + }
392 + },
393 + { default: () => "Management" }
394 + ),
395 + key: "Graylog-Management"
396 + },
397 + {
398 + label: () =>
399 + h(
400 + RouterLink,
401 + {
402 + to: {
403 + name: "Graylog-Metrics"
404 + }
405 + },
406 + { default: () => "Metrics" }
407 + ),
408 + key: "Graylog-Metrics"
409 + },
410 + {
411 + label: () =>
412 + h(
413 + RouterLink,
414 + {
415 + to: {
416 + name: "Graylog-Pipelines"
417 + }
418 + },
419 + { default: () => "Pipelines" }
420 + ),
421 + key: "Graylog-Pipelines"
422 + }
423 + ]
424 + },
425 + {
426 + label: () =>
427 + h(
428 + RouterLink,
429 + {
430 + to: {
431 + name: "Connectors"
432 + }
433 + },
434 + { default: () => "Connectors" }
435 + ),
436 + key: "Connectors",
437 + icon: renderIcon(ConnectorsIcon)
438 + },
439 + {
440 + label: "External Services",
441 + key: "ExternalServices",
442 + icon: renderIcon(ExternalServicesIcon),
443 + children: [
444 + {
445 + label: () =>
446 + h(
447 + RouterLink,
448 + {
449 + to: {
450 + name: "ExternalServices-ThirdPartyIntegrations"
451 + }
452 + },
453 + { default: () => "3rd Party Integrations" }
454 + ),
455 + key: "ExternalServices-ThirdPartyIntegrations"
456 + },
457 + {
458 + label: () =>
459 + h(
460 + RouterLink,
461 + {
462 + to: {
463 + name: "ExternalServices-NetworkConnectors"
464 + }
465 + },
466 + { default: () => "Network Connectors" }
467 + ),
468 + key: "ExternalServices-NetworkConnectors"
469 + },
470 + {
471 + label: () =>
472 + h(
473 + RouterLink,
474 + {
475 + to: {
476 + name: "ExternalServices-SingulAppAuth"
477 + }
478 + },
479 + { default: () => "Singul App Auth" }
480 + ),
481 + key: "ExternalServices-SingulAppAuth"
482 + }
483 + ]
484 + },
485 + {
486 + label: () =>
487 + h(
488 + RouterLink,
489 + {
490 + to: {
491 + name: "Scheduler"
492 + }
493 + },
494 + { default: () => "Scheduler" }
495 + ),
496 + key: "Scheduler",
497 + icon: renderIcon(SchedulerIcon)
498 + },
499 + {
500 + label: () =>
501 + h(
502 + RouterLink,
503 + {
504 + to: {
505 + name: "CustomerPortal"
506 + }
507 + },
508 + { default: () => "Customer Portal" }
509 + ),
510 + key: "CustomerPortal",
511 + icon: renderIcon(CustomerPortalIcon)
512 + }
513 + ]
514 }
frontend/src/app-layouts/common/Toolbar/Breadcrumb.vue
+11 -12
@@ -18,27 +18,23 @@
18
19 <script lang="ts" setup>
20 import type { RouteLocationNormalizedLoaded } from "vue-router"
21 +import type { BreadcrumbItem } from "@/composables/useBreadcrumb"
22 import _capitalize from "lodash/capitalize"
23 import _compact from "lodash/compact"
24 import _isEqual from "lodash/isEqual"
25 import _split from "lodash/split"
26 import { NBreadcrumb, NBreadcrumbItem } from "naive-ui"
26 -import { onBeforeMount, ref } from "vue"
27 +import { onBeforeMount } from "vue"
28 import { useRoute, useRouter } from "vue-router"
29 import Icon from "@/components/common/Icon.vue"
29 -
30 -interface Page {
31 - name: string
32 - path: string
33 - key: string
34 -}
30 +import { useBreadcrumb } from "@/composables/useBreadcrumb"
31
32 const HomeIcon = "fluent:home-24-regular"
33 const router = useRouter()
34 const route = useRoute()
39 -const items = ref<Page[]>([])
35 +const { items, setItems } = useBreadcrumb()
36
41 -function goto(page: Partial<Page>) {
37 +function goto(page: Partial<BreadcrumbItem>) {
38 if (page.name && page.name !== route.name) {
39 router.push({ name: page.name })
40 }
@@ -48,7 +44,7 @@ function goto(page: Partial<Page>) {
44 }
45
46 function checkRoute(route: RouteLocationNormalizedLoaded) {
51 - const newItems: Page[] = []
47 + const newItems: BreadcrumbItem[] = []
48 let pathChunks = _compact(_split(route?.path || "", "/"))
49 if (!pathChunks.length) {
50 pathChunks = _compact(_split(route?.matched?.[0]?.aliasOf?.path || "", "/"))
@@ -68,11 +64,14 @@ function checkRoute(route: RouteLocationNormalizedLoaded) {
64 }
65
66 if (route.meta?.title && newItems.length) {
71 - newItems[newItems.length - 1].name = route.meta.title
67 + const lastItem = newItems[newItems.length - 1]
68 + if (lastItem) {
69 + lastItem.name = route.meta.title
70 + }
71 }
72
73 if (!_isEqual(items.value, newItems)) {
75 - items.value = newItems
74 + setItems(newItems)
75 }
76 }
77
frontend/src/app-layouts/common/Toolbar/Toolbar.vue
-3
@@ -26,7 +26,6 @@
26 <script lang="ts" setup>
27 import BlurEffect from "@/components/common/BlurEffect.vue"
28 import Icon from "@/components/common/Icon.vue"
29 -import { useLoadingBarSetup } from "@/composables/useLoadingBarSetup"
29 import { useThemeStore } from "@/stores/theme"
30 import Logo from "../Logo.vue"
31 import Avatar from "./Avatar.vue"
@@ -45,8 +44,6 @@ const { boxed, gradient } = defineProps<{
44
45 const themeStore = useThemeStore()
46 const openNav = () => themeStore.openSidebar()
48 -
49 -useLoadingBarSetup()
47 </script>
48
49 <style lang="scss" scoped>
frontend/src/components/agents/AgentCard.vue
+158 -157
@@ -82,6 +82,7 @@
82 </template>
83
84 <script setup lang="ts">
85 +// TODO: refactor
86 import type { Agent } from "@/types/agents.d"
87 import { NButton, NCheckbox, NTooltip, useDialog, useMessage } from "naive-ui"
88 import { computed, ref, toRefs } from "vue"
@@ -93,19 +94,19 @@ import dayjs from "@/utils/dayjs"
94 import { handleDeleteAgent, toggleAgentCritical } from "./utils"
95
96 const props = defineProps<{
96 - agent: Agent
97 - showActions?: boolean
98 - embedded?: boolean
99 - hoverable?: boolean
100 - clickable?: boolean
101 - selectable?: boolean
102 - selected?: boolean
97 + agent: Agent
98 + showActions?: boolean
99 + embedded?: boolean
100 + hoverable?: boolean
101 + clickable?: boolean
102 + selectable?: boolean
103 + selected?: boolean
104 }>()
105
106 const emit = defineEmits<{
106 - (e: "delete"): void
107 - (e: "click"): void
108 - (e: "toggle-selection"): void
107 + (e: "delete"): void
108 + (e: "click"): void
109 + (e: "toggle-selection"): void
110 }>()
111
112 const { agent, showActions, embedded, hoverable, clickable, selectable, selected } = toRefs(props)
@@ -118,182 +119,182 @@ const loading = ref(false)
119 const message = useMessage()
120 const dialog = useDialog()
121 const isOnline = computed(() => {
121 - return agent.value.wazuh_agent_status === AgentStatus.Active
122 + return agent.value.wazuh_agent_status === AgentStatus.Active
123 })
124 const formatLastSeen = computed(() => {
124 - const lastSeenDate = dayjs(agent.value.wazuh_last_seen)
125 - if (!lastSeenDate.isValid()) return agent.value.wazuh_last_seen
125 + const lastSeenDate = dayjs(agent.value.wazuh_last_seen)
126 + if (!lastSeenDate.isValid()) return agent.value.wazuh_last_seen
127
127 - return lastSeenDate.format(dFormats.datetime)
128 + return lastSeenDate.format(dFormats.datetime)
129 })
130
131 function handleDelete() {
131 - handleDeleteAgent({
132 - agent: agent.value,
133 - cbBefore: () => {
134 - loading.value = true
135 - },
136 - cbSuccess: () => {
137 - emit("delete")
138 - },
139 - cbAfter: () => {
140 - loading.value = false
141 - },
142 - message,
143 - dialog
144 - })
132 + handleDeleteAgent({
133 + agent: agent.value,
134 + cbBefore: () => {
135 + loading.value = true
136 + },
137 + cbSuccess: () => {
138 + emit("delete")
139 + },
140 + cbAfter: () => {
141 + loading.value = false
142 + },
143 + message,
144 + dialog
145 + })
146 }
147
148 function toggleCritical(agentId: string, criticalStatus: boolean) {
148 - toggleAgentCritical({
149 - agentId,
150 - criticalStatus,
151 - message,
152 - cbBefore: () => {
153 - loading.value = true
154 - },
155 - cbSuccess: () => {
156 - agent.value.critical_asset = !criticalStatus
157 - },
158 - cbAfter: () => {
159 - loading.value = false
160 - }
161 - })
149 + toggleAgentCritical({
150 + agentId,
151 + criticalStatus,
152 + message,
153 + cbBefore: () => {
154 + loading.value = true
155 + },
156 + cbSuccess: () => {
157 + agent.value.critical_asset = !criticalStatus
158 + },
159 + cbAfter: () => {
160 + loading.value = false
161 + }
162 + })
163 }
164 </script>
165
166 <style lang="scss" scoped>
167 .agent-card {
167 - container-type: inline-size;
168 + container-type: inline-size;
169
169 - &.selected {
170 - border-color: var(--primary-color);
171 - background-color: var(--primary-color-opacity-1);
172 - }
170 + &.selected {
171 + border-color: var(--primary-color);
172 + background-color: var(--primary-color-opacity-1);
173 + }
174
174 - .card-content {
175 - display: flex;
176 - align-items: center;
177 - gap: 12px;
175 + .card-content {
176 + display: flex;
177 + align-items: center;
178 + gap: 12px;
179
179 - .selection-checkbox {
180 - flex-shrink: 0;
181 - display: flex;
182 - align-items: center;
183 - justify-content: center;
184 - cursor: pointer;
185 - }
180 + .selection-checkbox {
181 + flex-shrink: 0;
182 + display: flex;
183 + align-items: center;
184 + justify-content: center;
185 + cursor: pointer;
186 + }
187
187 - .wrapper {
188 - display: flex;
189 - flex-direction: row;
190 - align-items: center;
191 - gap: calc(var(--spacing) * 6);
192 - overflow: hidden;
193 - flex-grow: 1;
188 + .wrapper {
189 + display: flex;
190 + flex-direction: row;
191 + align-items: center;
192 + gap: calc(var(--spacing) * 6);
193 + overflow: hidden;
194 + flex-grow: 1;
195
195 - .agent-header {
196 - display: flex;
197 - flex-direction: column;
198 - min-width: 300px;
196 + .agent-header {
197 + display: flex;
198 + flex-direction: column;
199 + min-width: 300px;
200
200 - .title {
201 - display: flex;
202 - align-items: center;
203 - gap: calc(var(--spacing) * 2);
204 - margin-bottom: 4px;
201 + .title {
202 + display: flex;
203 + align-items: center;
204 + gap: calc(var(--spacing) * 2);
205 + margin-bottom: 4px;
206
206 - .hostname {
207 - font-weight: bold;
208 - white-space: nowrap;
209 - line-height: 32px;
210 - height: 32px;
211 - border-radius: 4px;
212 - border: 1px solid var(--info-color);
213 - border-color: transparent;
214 - box-sizing: border-box;
215 - overflow: hidden;
216 - text-overflow: ellipsis;
207 + .hostname {
208 + font-weight: bold;
209 + white-space: nowrap;
210 + line-height: 32px;
211 + height: 32px;
212 + border-radius: 4px;
213 + border: 1px solid var(--info-color);
214 + border-color: transparent;
215 + box-sizing: border-box;
216 + overflow: hidden;
217 + text-overflow: ellipsis;
218
218 - &.online {
219 - padding: 0px 15px;
220 - color: var(--success-color);
221 - border-color: var(--success-color);
222 - }
223 - }
219 + &.online {
220 + padding: 0px 15px;
221 + color: var(--success-color);
222 + border-color: var(--success-color);
223 + }
224 + }
225
225 - .quarantined {
226 - display: flex;
227 - padding-top: 1px;
228 - color: var(--warning-color);
229 - }
230 - }
231 - .info {
232 - font-family: var(--font-family-mono);
233 - font-size: var(--text-xs);
234 - opacity: 0.7;
235 - white-space: nowrap;
236 - overflow: hidden;
237 - text-overflow: ellipsis;
238 - margin-left: 2px;
239 - }
240 - }
226 + .quarantined {
227 + display: flex;
228 + padding-top: 1px;
229 + color: var(--warning-color);
230 + }
231 + }
232 + .info {
233 + font-family: var(--font-family-mono);
234 + font-size: var(--text-xs);
235 + opacity: 0.7;
236 + white-space: nowrap;
237 + overflow: hidden;
238 + text-overflow: ellipsis;
239 + margin-left: 2px;
240 + }
241 + }
242
242 - .agent-info {
243 - display: flex;
244 - flex-direction: column;
245 - flex-grow: 1;
246 - overflow: hidden;
243 + .agent-info {
244 + display: flex;
245 + flex-direction: column;
246 + flex-grow: 1;
247 + overflow: hidden;
248
248 - .os {
249 - line-height: 32px;
250 - height: 32px;
251 - margin-bottom: 4px;
252 - white-space: nowrap;
253 - overflow: hidden;
254 - text-overflow: ellipsis;
255 - }
256 - .ip-address {
257 - white-space: nowrap;
258 - font-size: var(--text-xs);
259 - font-family: var(--font-family-mono);
260 - opacity: 0.7;
261 - overflow: hidden;
262 - text-overflow: ellipsis;
263 - }
264 - }
249 + .os {
250 + line-height: 32px;
251 + height: 32px;
252 + margin-bottom: 4px;
253 + white-space: nowrap;
254 + overflow: hidden;
255 + text-overflow: ellipsis;
256 + }
257 + .ip-address {
258 + white-space: nowrap;
259 + font-size: var(--text-xs);
260 + font-family: var(--font-family-mono);
261 + opacity: 0.7;
262 + overflow: hidden;
263 + text-overflow: ellipsis;
264 + }
265 + }
266
266 - .agent-actions {
267 - display: flex;
268 - }
269 - }
270 - }
267 + .agent-actions {
268 + display: flex;
269 + }
270 + }
271 + }
272
272 - &.critical {
273 - border-color: var(--warning-color);
274 - }
273 + &.critical {
274 + border-color: var(--warning-color);
275 + }
276
276 - @container (max-width: 550px) {
277 - .card-content .wrapper {
278 - gap: calc(var(--spacing) * 5);
277 + @container (max-width: 550px) {
278 + .card-content .wrapper {
279 + gap: calc(var(--spacing) * 5);
280
280 - .agent-header {
281 - min-width: initial;
282 - }
283 - }
284 - }
285 - @container (max-width: 400px) {
286 - .card-content .wrapper {
287 - gap: calc(var(--spacing) * 4);
281 + .agent-header {
282 + min-width: initial;
283 + }
284 + }
285 + }
286 + @container (max-width: 400px) {
287 + .card-content .wrapper {
288 + gap: calc(var(--spacing) * 4);
289
289 - .agent-header {
290 - flex-grow: 1;
291 - overflow: hidden;
292 - }
293 - .agent-info {
294 - display: none;
295 - }
296 - }
297 - }
290 + .agent-header {
291 + flex-grow: 1;
292 + overflow: hidden;
293 + }
294 + .agent-info {
295 + display: none;
296 + }
297 + }
298 + }
299 }
300 </style>
frontend/src/components/agents/AgentToolbar.vue
+202 -207
@@ -31,33 +31,27 @@
31 <!-- Selection Mode & Bulk Delete Section -->
32 <div class="bulk-actions flex flex-col gap-3">
33 <div class="selection-toggle flex items-center gap-2">
34 - <n-switch v-model:value="selectionModeInternal" @update:value="emit('update:selection-mode', $event)">
34 + <n-switch
35 + v-model:value="selectionModeInternal"
36 + @update:value="emit('update:selection-mode', $event)"
37 + >
38 <template #checked>Selection ON</template>
39 <template #unchecked>Selection OFF</template>
40 </n-switch>
41 </div>
42
40 - <p v-if="selectionModeInternal" class="text-xs text-secondary-color">
41 - Click agents to select them for bulk operations. Or select "Bulk Delete" and apply a filter to delete multiple agents at once.
43 + <p v-if="selectionModeInternal" class="text-secondary-color text-xs">
44 + Click agents to select them for bulk operations. Or select "Bulk Delete" and apply a filter to
45 + delete multiple agents at once.
46 </p>
47
48 <!-- Selected count and actions -->
49 <div v-if="selectedCount && selectedCount > 0" class="selected-info flex items-center gap-2">
46 - <n-tag type="info" size="small">
47 - {{ selectedCount }} selected
48 - </n-tag>
49 - <n-button size="tiny" quaternary @click="emit('clear-selection')">
50 - Clear
51 - </n-button>
50 + <n-tag type="info" size="small">{{ selectedCount }} selected</n-tag>
51 + <n-button size="tiny" quaternary @click="emit('clear-selection')">Clear</n-button>
52 </div>
53
54 - <n-button
55 - type="error"
56 - secondary
57 - size="small"
58 - :disabled="syncing"
59 - @click="emit('bulk-delete')"
60 - >
54 + <n-button type="error" secondary size="small" :disabled="syncing" @click="emit('bulk-delete')">
55 <template #icon>
56 <Icon :name="DeleteIcon" />
57 </template>
@@ -106,6 +100,7 @@
100 </template>
101
102 <script setup lang="ts">
103 +// TODO: refactor
104 import type { DropdownMixedOption } from "naive-ui/es/dropdown/src/interface"
105 import type { Agent } from "@/types/agents.d"
106 import type { Customer } from "@/types/customers.d"
@@ -116,36 +111,36 @@ import Api from "@/api"
111 import Icon from "@/components/common/Icon.vue"
112
113 const props = defineProps<{
119 - modelValue: string
120 - syncing?: boolean
121 - enableSyncVulnerabilitiesDropdown?: boolean
122 - agentsLength?: number
123 - agentsFilteredLength?: number
124 - agentsCritical?: Agent[]
125 - agentsOnline?: Agent[]
126 - selectionMode?: boolean
127 - selectedCount?: number
114 + modelValue: string
115 + syncing?: boolean
116 + enableSyncVulnerabilitiesDropdown?: boolean
117 + agentsLength?: number
118 + agentsFilteredLength?: number
119 + agentsCritical?: Agent[]
120 + agentsOnline?: Agent[]
121 + selectionMode?: boolean
122 + selectedCount?: number
123 }>()
124
125 const emit = defineEmits<{
131 - (e: "run", value: "sync-agents" | `sync-vulnerabilities:${string}`): void
132 - (e: "update:modelValue", value: string): void
133 - (e: "click", value: Agent): void
134 - (e: "bulk-delete"): void
135 - (e: "update:selection-mode", value: boolean): void
136 - (e: "clear-selection"): void
126 + (e: "run", value: "sync-agents" | `sync-vulnerabilities:${string}`): void
127 + (e: "update:modelValue", value: string): void
128 + (e: "click", value: Agent): void
129 + (e: "bulk-delete"): void
130 + (e: "update:selection-mode", value: boolean): void
131 + (e: "clear-selection"): void
132 }>()
133
134 const {
140 - modelValue,
141 - syncing,
142 - agentsLength,
143 - agentsFilteredLength,
144 - agentsCritical,
145 - agentsOnline,
146 - enableSyncVulnerabilitiesDropdown,
147 - selectionMode,
148 - selectedCount
135 + modelValue,
136 + syncing,
137 + agentsLength,
138 + agentsFilteredLength,
139 + agentsCritical,
140 + agentsOnline,
141 + enableSyncVulnerabilitiesDropdown,
142 + selectionMode,
143 + selectedCount
144 } = toRefs(props)
145
146 const SearchIcon = "carbon:search"
@@ -153,21 +148,21 @@ const DeleteIcon = "carbon:trash-can"
148 const message = useMessage()
149
150 const textFilter = computed<string>({
156 - get() {
157 - return modelValue.value
158 - },
159 - set(value) {
160 - emit("update:modelValue", value)
161 - }
151 + get() {
152 + return modelValue.value
153 + },
154 + set(value) {
155 + emit("update:modelValue", value)
156 + }
157 })
158
159 const selectionModeInternal = ref(selectionMode?.value ?? false)
160
161 watch(
167 - () => selectionMode?.value,
168 - val => {
169 - selectionModeInternal.value = val ?? false
170 - }
162 + () => selectionMode?.value,
163 + val => {
164 + selectionModeInternal.value = val ?? false
165 + }
166 )
167
168 const loadingCustomersList = ref(false)
@@ -175,173 +170,173 @@ const customersList = ref<Customer[]>([])
170 const { width: winWidth } = useWindowSize()
171
172 const customersOptions = computed(() => {
178 - const options: DropdownMixedOption[] = [
179 - {
180 - label: "Sync Agents",
181 - key: "sync-agents"
182 - }
183 - ]
184 -
185 - if (winWidth.value > 550) {
186 - options.push({
187 - label: `Sync Agent Vulnerabilities${loadingCustomersList.value ? "..." : ""}`,
188 - key: "sync-vulnerabilities",
189 - disabled: loadingCustomersList.value,
190 - children: loadingCustomersList.value
191 - ? undefined
192 - : [
193 - {
194 - label: () => h("div", { class: "pl-2" }, "Select a Customer"),
195 - type: "group",
196 - children: customersList.value.map(o => ({
197 - label: `#${o.customer_code} - ${o.customer_name}`,
198 - key: `sync-vulnerabilities:${o.customer_code}`
199 - }))
200 - }
201 - ]
202 - })
203 - } else {
204 - options.push({
205 - label: () => h("div", { class: "pl-2" }, "Select a Customer"),
206 - type: "group",
207 - children: customersList.value.map(o => ({
208 - label: `#${o.customer_code} - ${o.customer_name}`,
209 - key: `sync-vulnerabilities:${o.customer_code}`
210 - }))
211 - })
212 - }
213 -
214 - return options
173 + const options: DropdownMixedOption[] = [
174 + {
175 + label: "Sync Agents",
176 + key: "sync-agents"
177 + }
178 + ]
179 +
180 + if (winWidth.value > 550) {
181 + options.push({
182 + label: `Sync Agent Vulnerabilities${loadingCustomersList.value ? "..." : ""}`,
183 + key: "sync-vulnerabilities",
184 + disabled: loadingCustomersList.value,
185 + children: loadingCustomersList.value
186 + ? undefined
187 + : [
188 + {
189 + label: () => h("div", { class: "pl-2" }, "Select a Customer"),
190 + type: "group",
191 + children: customersList.value.map(o => ({
192 + label: `#${o.customer_code} - ${o.customer_name}`,
193 + key: `sync-vulnerabilities:${o.customer_code}`
194 + }))
195 + }
196 + ]
197 + })
198 + } else {
199 + options.push({
200 + label: () => h("div", { class: "pl-2" }, "Select a Customer"),
201 + type: "group",
202 + children: customersList.value.map(o => ({
203 + label: `#${o.customer_code} - ${o.customer_name}`,
204 + key: `sync-vulnerabilities:${o.customer_code}`
205 + }))
206 + })
207 + }
208 +
209 + return options
210 })
211
212 function getCustomers() {
218 - loadingCustomersList.value = true
219 -
220 - Api.customers
221 - .getCustomers()
222 - .then(res => {
223 - if (res.data.success) {
224 - customersList.value = res.data?.customers || []
225 - } else {
226 - message.warning(res.data?.message || "An error occurred. Please try again later.")
227 - }
228 - })
229 - .catch(err => {
230 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
231 - })
232 - .finally(() => {
233 - loadingCustomersList.value = false
234 - })
213 + loadingCustomersList.value = true
214 +
215 + Api.customers
216 + .getCustomers()
217 + .then(res => {
218 + if (res.data.success) {
219 + customersList.value = res.data?.customers || []
220 + } else {
221 + message.warning(res.data?.message || "An error occurred. Please try again later.")
222 + }
223 + })
224 + .catch(err => {
225 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
226 + })
227 + .finally(() => {
228 + loadingCustomersList.value = false
229 + })
230 }
231
232 function load() {
238 - if (!customersList.value.length) {
239 - getCustomers()
240 - }
233 + if (!customersList.value.length) {
234 + getCustomers()
235 + }
236 }
237 </script>
238
239 <style lang="scss" scoped>
240 .agent-toolbar {
246 - container-type: inline-size;
247 - overflow: hidden;
248 - max-width: 100%;
249 - min-width: 340px;
250 -
251 - .wrapper {
252 - overflow: hidden;
253 - height: 100%;
254 -
255 - .search-info {
256 - color: var(--fg-secondary-color);
257 - }
258 -
259 - .bulk-actions {
260 - padding: 12px;
261 - background: var(--bg-secondary-color);
262 - border-radius: var(--border-radius);
263 -
264 - .selection-toggle {
265 - font-size: 13px;
266 - }
267 -
268 - .selected-info {
269 - flex-wrap: wrap;
270 - }
271 - }
272 -
273 - .agents-list {
274 - .title {
275 - margin-bottom: calc(var(--spacing) * 2);
276 - }
277 -
278 - .list {
279 - .item {
280 - border: 2px solid transparent;
281 - padding-inline: calc(var(--spacing) * 3);
282 - padding-block: calc(var(--spacing) * 2);
283 - font-size: 14px;
284 - font-weight: bold;
285 - cursor: pointer;
286 - border-radius: var(--border-radius);
287 -
288 - &:not(:last-child) {
289 - margin-bottom: calc(var(--spacing) * 2);
290 - }
291 -
292 - &:hover {
293 - background-color: var(--hover-color);
294 - }
295 - }
296 - }
297 -
298 - .agents-critical-list {
299 - margin-bottom: calc(var(--spacing) * 5);
300 -
301 - .list {
302 - .item {
303 - border-color: var(--warning-color);
304 - }
305 - }
306 - }
307 - .agents-online-list {
308 - .list {
309 - .item {
310 - border-color: var(--success-color);
311 - }
312 - }
313 - }
314 - }
315 - }
316 -
317 - @container (min-width: 350px) {
318 - .wrapper {
319 - .agent-search {
320 - flex-grow: 1;
321 - }
322 - .search-info {
323 - display: none;
324 - }
325 - .agents-list {
326 - display: none;
327 - }
328 - }
329 - }
330 - @media (max-width: 500px) {
331 - min-width: 100%;
332 -
333 - .wrapper {
334 - .agent-search {
335 - flex-grow: 1;
336 - width: 100%;
337 - }
338 - .search-info {
339 - display: none;
340 - }
341 - .agents-list {
342 - display: none;
343 - }
344 - }
345 - }
241 + container-type: inline-size;
242 + overflow: hidden;
243 + max-width: 100%;
244 + min-width: 340px;
245 +
246 + .wrapper {
247 + overflow: hidden;
248 + height: 100%;
249 +
250 + .search-info {
251 + color: var(--fg-secondary-color);
252 + }
253 +
254 + .bulk-actions {
255 + padding: 12px;
256 + background: var(--bg-secondary-color);
257 + border-radius: var(--border-radius);
258 +
259 + .selection-toggle {
260 + font-size: 13px;
261 + }
262 +
263 + .selected-info {
264 + flex-wrap: wrap;
265 + }
266 + }
267 +
268 + .agents-list {
269 + .title {
270 + margin-bottom: calc(var(--spacing) * 2);
271 + }
272 +
273 + .list {
274 + .item {
275 + border: 2px solid transparent;
276 + padding-inline: calc(var(--spacing) * 3);
277 + padding-block: calc(var(--spacing) * 2);
278 + font-size: 14px;
279 + font-weight: bold;
280 + cursor: pointer;
281 + border-radius: var(--border-radius);
282 +
283 + &:not(:last-child) {
284 + margin-bottom: calc(var(--spacing) * 2);
285 + }
286 +
287 + &:hover {
288 + background-color: var(--hover-color);
289 + }
290 + }
291 + }
292 +
293 + .agents-critical-list {
294 + margin-bottom: calc(var(--spacing) * 5);
295 +
296 + .list {
297 + .item {
298 + border-color: var(--warning-color);
299 + }
300 + }
301 + }
302 + .agents-online-list {
303 + .list {
304 + .item {
305 + border-color: var(--success-color);
306 + }
307 + }
308 + }
309 + }
310 + }
311 +
312 + @container (min-width: 350px) {
313 + .wrapper {
314 + .agent-search {
315 + flex-grow: 1;
316 + }
317 + .search-info {
318 + display: none;
319 + }
320 + .agents-list {
321 + display: none;
322 + }
323 + }
324 + }
325 + @media (max-width: 500px) {
326 + min-width: 100%;
327 +
328 + .wrapper {
329 + .agent-search {
330 + flex-grow: 1;
331 + width: 100%;
332 + }
333 + .search-info {
334 + display: none;
335 + }
336 + .agents-list {
337 + display: none;
338 + }
339 + }
340 + }
341 }
342 </style>
frontend/src/components/agents/BulkDeleteModal.vue
+117 -110
@@ -1,5 +1,11 @@
1 <template>
2 - <n-modal v-model:show="showModal" preset="card" title="Bulk Delete Agents" class="bulk-delete-modal" :style="{ maxWidth: '600px' }">
2 + <n-modal
3 + v-model:show="showModal"
4 + preset="card"
5 + title="Bulk Delete Agents"
6 + class="bulk-delete-modal"
7 + :style="{ maxWidth: '600px' }"
8 + >
9 <n-tabs v-model:value="activeTab" type="line">
10 <n-tab-pane name="selection" tab="By Selection">
11 <div class="tab-content">
@@ -8,7 +14,9 @@
14 </n-alert>
15 <template v-else>
16 <p class="mb-4">
11 - You are about to delete <strong>{{ selectedAgents.length }}</strong> selected agent(s).
17 + You are about to delete
18 + <strong>{{ selectedAgents.length }}</strong>
19 + selected agent(s).
20 </p>
21 <n-scrollbar style="max-height: 200px" class="mb-4">
22 <div class="selected-agents-list">
@@ -29,7 +37,7 @@
37
38 <n-tab-pane name="filter" tab="By Filter">
39 <div class="tab-content">
32 - <n-form ref="filterFormRef" :model="filterForm" label-placement="top">
40 + <n-form :model="filterForm" label-placement="top">
41 <n-alert type="info" class="mb-4">
42 At least one filter must be specified. This will delete all matching agents.
43 </n-alert>
@@ -102,12 +110,7 @@
110 <template #footer>
111 <n-space justify="end">
112 <n-button @click="closeModal">Cancel</n-button>
105 - <n-button
106 - type="error"
107 - :loading="loading"
108 - :disabled="!canDelete"
109 - @click="confirmDelete"
110 - >
113 + <n-button type="error" :loading="loading" :disabled="!canDelete" @click="confirmDelete">
114 <template #icon>
115 <n-icon><Icon :name="DeleteIcon" /></n-icon>
116 </template>
@@ -126,10 +129,12 @@
129 <p class="mt-2">
130 <strong>Filters:</strong>
131 </p>
129 - <ul class="list-disc ml-4">
132 + <ul class="ml-4 list-disc">
133 <li v-if="filterForm.customer_code">Customer: {{ filterForm.customer_code }}</li>
134 <li v-if="filterForm.status">Status: {{ filterForm.status }}</li>
132 - <li v-if="filterForm.disconnected_days">Disconnected for: {{ filterForm.disconnected_days }}+ days</li>
135 + <li v-if="filterForm.disconnected_days">
136 + Disconnected for: {{ filterForm.disconnected_days }}+ days
137 + </li>
138 </ul>
139 </template>
140 <template v-else>
@@ -138,56 +143,55 @@
143 </template>
144 <template #action>
145 <n-button @click="showConfirmDialog = false">Cancel</n-button>
141 - <n-button type="error" :loading="loading" @click="executeDelete">
142 - Confirm Delete
143 - </n-button>
146 + <n-button type="error" :loading="loading" @click="executeDelete">Confirm Delete</n-button>
147 </template>
148 </n-modal>
149 </template>
150
151 <script setup lang="ts">
152 +// TODO: refactor
153 import type { Agent, BulkDeleteAgentsResponse, BulkDeleteFilterRequest } from "@/types/agents.d"
154 import {
151 - NAlert,
152 - NButton,
153 - NCollapse,
154 - NCollapseItem,
155 - NDivider,
156 - NForm,
157 - NFormItem,
158 - NIcon,
159 - NInputNumber,
160 - NModal,
161 - NScrollbar,
162 - NSelect,
163 - NSpace,
164 - NTabPane,
165 - NTabs,
166 - NTag,
167 - useMessage
155 + NAlert,
156 + NButton,
157 + NCollapse,
158 + NCollapseItem,
159 + NDivider,
160 + NForm,
161 + NFormItem,
162 + NIcon,
163 + NInputNumber,
164 + NModal,
165 + NScrollbar,
166 + NSelect,
167 + NSpace,
168 + NTabPane,
169 + NTabs,
170 + NTag,
171 + useMessage
172 } from "naive-ui"
173 import { computed, ref, watch } from "vue"
174 import Api from "@/api"
175 import Icon from "@/components/common/Icon.vue"
176
177 const props = defineProps<{
174 - show: boolean
175 - selectedAgents: Agent[]
176 - customers: string[]
178 + show: boolean
179 + selectedAgents: Agent[]
180 + customers: string[]
181 }>()
182
183 const emit = defineEmits<{
180 - (e: "update:show", value: boolean): void
181 - (e: "remove-selection", agent: Agent): void
182 - (e: "deleted"): void
184 + (e: "update:show", value: boolean): void
185 + (e: "remove-selection", agent: Agent): void
186 + (e: "deleted"): void
187 }>()
188
189 const message = useMessage()
190 const DeleteIcon = "carbon:trash-can"
191
192 const showModal = computed({
189 - get: () => props.show,
190 - set: (value) => emit("update:show", value)
193 + get: () => props.show,
194 + set: value => emit("update:show", value)
195 })
196
197 const activeTab = ref<"selection" | "filter">("selection")
@@ -196,114 +200,117 @@ const showConfirmDialog = ref(false)
200 const deleteResults = ref<BulkDeleteAgentsResponse | null>(null)
201
202 const filterForm = ref<BulkDeleteFilterRequest>({
199 - customer_code: undefined,
200 - status: undefined,
201 - disconnected_days: undefined
203 + customer_code: undefined,
204 + status: undefined,
205 + disconnected_days: undefined
206 })
207
208 const customerOptions = computed(() => {
205 - return props.customers.map(code => ({ label: code, value: code }))
209 + return props.customers.map(code => ({ label: code, value: code }))
210 })
211
212 const statusOptions = [
209 - { label: "Disconnected", value: "disconnected" },
210 - { label: "Never Connected", value: "never_connected" },
211 - { label: "Active", value: "active" }
213 + { label: "Disconnected", value: "disconnected" },
214 + { label: "Never Connected", value: "never_connected" },
215 + { label: "Active", value: "active" }
216 ]
217
218 const hasFilters = computed(() => {
215 - return filterForm.value.customer_code || filterForm.value.status || filterForm.value.disconnected_days
219 + return filterForm.value.customer_code || filterForm.value.status || filterForm.value.disconnected_days
220 })
221
222 const canDelete = computed(() => {
219 - if (activeTab.value === "selection") {
220 - return props.selectedAgents.length > 0
221 - }
222 - return hasFilters.value
223 + if (activeTab.value === "selection") {
224 + return props.selectedAgents.length > 0
225 + }
226 + return hasFilters.value
227 })
228
229 function closeModal() {
226 - showModal.value = false
227 - deleteResults.value = null
230 + showModal.value = false
231 + deleteResults.value = null
232 }
233
234 function confirmDelete() {
231 - showConfirmDialog.value = true
235 + showConfirmDialog.value = true
236 }
237
238 async function executeDelete() {
235 - showConfirmDialog.value = false
236 - loading.value = true
237 - deleteResults.value = null
239 + showConfirmDialog.value = false
240 + loading.value = true
241 + deleteResults.value = null
242
239 - try {
240 - let response
241 - if (activeTab.value === "selection") {
242 - const agentIds = props.selectedAgents.map(a => a.agent_id)
243 - response = await Api.agents.bulkDeleteAgents(agentIds)
244 - } else {
245 - response = await Api.agents.bulkDeleteAgentsByFilter(filterForm.value)
246 - }
243 + try {
244 + let response
245 + if (activeTab.value === "selection") {
246 + const agentIds = props.selectedAgents.map(a => a.agent_id)
247 + response = await Api.agents.bulkDeleteAgents(agentIds)
248 + } else {
249 + response = await Api.agents.bulkDeleteAgentsByFilter(filterForm.value)
250 + }
251
248 - deleteResults.value = response.data
252 + deleteResults.value = response.data
253
250 - if (response.data.success) {
251 - message.success(response.data.message)
252 - emit("deleted")
253 - } else {
254 - message.warning(response.data.message)
255 - }
256 - } catch (err: any) {
257 - message.error(err.response?.data?.detail || err.response?.data?.message || "Failed to delete agents")
258 - } finally {
259 - loading.value = false
260 - }
254 + if (response.data.success) {
255 + message.success(response.data.message)
256 + emit("deleted")
257 + } else {
258 + message.warning(response.data.message)
259 + }
260 + } catch (err: any) {
261 + message.error(err.response?.data?.detail || err.response?.data?.message || "Failed to delete agents")
262 + } finally {
263 + loading.value = false
264 + }
265 }
266
267 // Reset form when modal closes
264 -watch(showModal, (newVal) => {
265 - if (!newVal) {
266 - filterForm.value = {
267 - customer_code: undefined,
268 - status: undefined,
269 - disconnected_days: undefined
270 - }
271 - deleteResults.value = null
272 - }
268 +watch(showModal, newVal => {
269 + if (!newVal) {
270 + filterForm.value = {
271 + customer_code: undefined,
272 + status: undefined,
273 + disconnected_days: undefined
274 + }
275 + deleteResults.value = null
276 + }
277 })
278
279 // Switch to selection tab if agents are selected
276 -watch(() => props.selectedAgents.length, (len) => {
277 - if (len > 0) {
278 - activeTab.value = "selection"
279 - }
280 -})
280 +watch(
281 + () => props.selectedAgents.length,
282 + len => {
283 + if (len > 0) {
284 + activeTab.value = "selection"
285 + }
286 + }
287 +)
288 </script>
289
290 <style lang="scss" scoped>
291 .bulk-delete-modal {
285 - .tab-content {
286 - padding-top: 16px;
287 - }
292 + .tab-content {
293 + padding-top: 16px;
294 + }
295
289 - .selected-agents-list {
290 - display: flex;
291 - flex-wrap: wrap;
292 - }
296 + .selected-agents-list {
297 + display: flex;
298 + flex-wrap: wrap;
299 + }
300
294 - .result-item {
295 - display: flex;
296 - align-items: center;
297 - padding: 4px 0;
298 - border-bottom: 1px solid var(--border-color);
301 + .result-item {
302 + display: flex;
303 + align-items: center;
304 + padding: 4px 0;
305 + border-bottom: 1px solid var(--border-color);
306
300 - &:last-child {
301 - border-bottom: none;
302 - }
303 - }
307 + &:last-child {
308 + border-bottom: none;
309 + }
310 + }
311
305 - .w-full {
306 - width: 100%;
307 - }
312 + .w-full {
313 + width: 100%;
314 + }
315 }
316 </style>
frontend/src/components/agents/OverviewSection.vue
+4 -4
@@ -6,7 +6,7 @@
6 </template>
7 <template #value>
8 <template v-if="item.key === 'customer_code' && item.val !== '-'">
9 - <code class="text-primary cursor-pointer" @click="gotoCustomer({ code: item.val })">
9 + <code class="text-primary cursor-pointer" @click="routeCustomer({ code: item.val }).navigate()">
10 {{ item.val }}
11 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
12 </code>
@@ -27,9 +27,9 @@ import type { Agent } from "@/types/agents.d"
27 import { computed, toRefs } from "vue"
28 import CardKV from "@/components/common/cards/CardKV.vue"
29 import Icon from "@/components/common/Icon.vue"
30 -import { useGoto } from "@/composables/useGoto"
30 +import { useNavigation } from "@/composables/useNavigation"
31 import { useSettingsStore } from "@/stores/settings"
32 -import { formatDate } from "@/utils"
32 +import { formatDate } from "@/utils/format"
33 import AgentVelociraptorIdForm from "./AgentVelociraptorIdForm.vue"
34
35 const props = defineProps<{
@@ -44,7 +44,7 @@ const { agent } = toRefs(props)
44
45 const LinkIcon = "carbon:launch"
46 const dFormats = useSettingsStore().dateFormat
47 -const { gotoCustomer } = useGoto()
47 +const { routeCustomer } = useNavigation()
48
49 const propsSanitized = computed(() => {
50 const obj = []
frontend/src/components/agents/agentFlow/AgentFlowItem.vue
+1 -1
@@ -179,8 +179,8 @@ import CardEntity from "@/components/common/cards/CardEntity.vue"
179 import CardKV from "@/components/common/cards/CardKV.vue"
180 import Icon from "@/components/common/Icon.vue"
181 import { useSettingsStore } from "@/stores/settings"
182 -import { formatDate } from "@/utils"
182 import dayjs from "@/utils/dayjs"
183 +import { formatDate } from "@/utils/format"
184 import "@/assets/scss/overrides/vuesjv-override.scss"
185
186 const { flow, embedded } = defineProps<{ flow: FlowResult; embedded?: boolean }>()
frontend/src/components/agents/agentFlow/AgentFlowQueryStat.vue
+1 -1
@@ -93,8 +93,8 @@ import Badge from "@/components/common/Badge.vue"
93 import CardEntity from "@/components/common/cards/CardEntity.vue"
94 import CardKV from "@/components/common/cards/CardKV.vue"
95 import { useSettingsStore } from "@/stores/settings"
96 -import { formatDate } from "@/utils"
96 import dayjs from "@/utils/dayjs"
97 +import { formatDate } from "@/utils/format"
98
99 const { stat, embedded } = defineProps<{ stat: FlowQueryStat; embedded?: boolean }>()
100
frontend/src/components/agents/agentFlow/AgentFlowTimeline.vue
+1 -1
@@ -15,7 +15,7 @@
15 import type { FlowResult } from "@/types/flow.d"
16 import { NTimeline, NTimelineItem } from "naive-ui"
17 import { useSettingsStore } from "@/stores/settings"
18 -import { formatDate } from "@/utils"
18 +import { formatDate } from "@/utils/format"
19
20 const { flow } = defineProps<{ flow: FlowResult }>()
21
frontend/src/components/agents/dataStore/ArtifactCard.vue
+1 -1
@@ -72,7 +72,7 @@ import { NBadge, NButton, NCard, NDivider } from "naive-ui"
72 import { computed } from "vue"
73 import Icon from "@/components/common/Icon.vue"
74 import { useSettingsStore } from "@/stores/settings"
75 -import { formatDate } from "@/utils"
75 +import { formatDate } from "@/utils/format"
76
77 // TODO: join ArtifactCard + ArtifactCardCompact
78
frontend/src/components/agents/dataStore/ArtifactCardCompact.vue
+1 -1
@@ -47,7 +47,7 @@ import { NButton, NCard, NTag } from "naive-ui"
47 import { computed } from "vue"
48 import Icon from "@/components/common/Icon.vue"
49 import { useSettingsStore } from "@/stores/settings"
50 -import { formatDate } from "@/utils"
50 +import { formatDate } from "@/utils/format"
51
52 const { artifact, showActions = false } = defineProps<{
53 artifact: AgentArtifactData
frontend/src/components/agents/dataStore/ArtifactDetails.vue
+1 -1
@@ -17,7 +17,7 @@ import bytes from "bytes"
17 import { NBadge, NDescriptions, NDescriptionsItem } from "naive-ui"
18 import { computed } from "vue"
19 import { useSettingsStore } from "@/stores/settings"
20 -import { formatDate } from "@/utils"
20 +import { formatDate } from "@/utils/format"
21
22 const { artifact } = defineProps<{ artifact: AgentArtifactData }>()
23
frontend/src/components/agents/sca/ScaItem.vue
+1 -1
@@ -86,7 +86,7 @@ import { computed, defineAsyncComponent } from "vue"
86 import CardKV from "@/components/common/cards/CardKV.vue"
87 import Icon from "@/components/common/Icon.vue"
88 import { useSettingsStore } from "@/stores/settings"
89 -import { formatDate } from "@/utils"
89 +import { formatDate } from "@/utils/format"
90
91 const { sca, agent } = defineProps<{ sca: AgentSca; agent: Agent }>()
92
frontend/src/components/agents/sca/ScaResults.vue
+3
@@ -88,6 +88,7 @@
88 </template>
89
90 <script setup lang="ts">
91 +// TODO: refactor
92 import type { Agent, AgentSca, ScaPolicyResult } from "@/types/agents.d"
93 import { useResizeObserver } from "@vueuse/core"
94 import { NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
@@ -164,6 +165,8 @@ function getSCAResults(agentId: string, policyId: string) {
165
166 useResizeObserver(header, entries => {
167 const entry = entries[0]
168 + if (!entry) return
169 +
170 const { width } = entry.contentRect
171
172 pageSlot.value = width < 650 ? 5 : 8
frontend/src/components/agents/sca/ScaTable.vue
+1 -1
@@ -106,7 +106,7 @@ import { onBeforeMount, ref, toRefs } from "vue"
106 import Api from "@/api"
107 import Icon from "@/components/common/Icon.vue"
108 import { useSettingsStore } from "@/stores/settings"
109 -import { formatDate } from "@/utils"
109 +import { formatDate } from "@/utils/format"
110 import ScaItem from "./ScaItem.vue"
111
112 interface SCAExt extends AgentSca {
frontend/src/components/agents/vulnerabilities/VulnerabilitiesGrid.vue
+1 -1
@@ -37,7 +37,7 @@ import { nanoid } from "nanoid"
37 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
38 import Api from "@/api"
39 import { useSettingsStore } from "@/stores/settings"
40 -import { formatDate } from "@/utils"
40 +import { formatDate } from "@/utils/format"
41 import VulnerabilityCard from "./VulnerabilityCard.vue"
42
43 const props = defineProps<{
frontend/src/components/aiChatbot/ChatBubble.vue
+1 -1
@@ -104,7 +104,7 @@ import CollapseKeepAlive from "@/components/common/CollapseKeepAlive.vue"
104 import Icon from "@/components/common/Icon.vue"
105 import Markdown from "@/components/common/Markdown.vue"
106 import { useSettingsStore } from "@/stores/settings"
107 -import { formatDate } from "@/utils"
107 +import { formatDate } from "@/utils/format"
108
109 export interface ChatBubble {
110 id: string
frontend/src/components/aiChatbot/ChatQuery.vue
+5 -4
@@ -12,7 +12,7 @@
12 </div>
13 <n-input
14 v-model:value.trim="input"
15 - class="max-h-full min-h-20 pb-9"
15 + class="max-h-full min-h-20 pt-1 pb-9"
16 type="textarea"
17 placeholder="How can I help you?"
18 :autosize="{
@@ -62,13 +62,13 @@
62 </template>
63 <div class="divide-border flex flex-col divide-y">
64 <div class="px-3 py-2">
65 - <div class="flex items-center gap-2 text-sm">
65 + <div class="flex items-center justify-between gap-4 text-sm">
66 <div>verbose response</div>
67 <n-switch v-model:value="verbose" size="small" />
68 </div>
69 </div>
70 <div class="px-3 py-2">
71 - <div class="flex items-center gap-2 text-sm">
71 + <div class="flex items-center justify-between gap-4 text-sm">
72 <div>show example questions</div>
73 <n-switch v-model:value="showQuestions" size="small" />
74 </div>
@@ -93,6 +93,7 @@
93 </template>
94
95 <script setup lang="ts">
96 +// TODO: refactor
97 import type { RemovableRef } from "@vueuse/core"
98 import type { SelectOption } from "naive-ui"
99 import type { VNode } from "vue"
@@ -157,7 +158,7 @@ function getList() {
158 if (res.data.success) {
159 servers.value = res.data?.servers || []
160 if (servers.value.length && !selectedServer.value) {
160 - selectedServer.value = servers.value[0].value
161 + selectedServer.value = servers.value[0]?.value ?? null
162 }
163 emit("server-loaded")
164 } else {
frontend/src/components/alerts/Alert.vue
+14 -7
@@ -52,7 +52,7 @@
52 agent_id:
53 <code
54 class="text-primary cursor-pointer"
55 - @click.stop="gotoAgent(alert._source.agent_id)"
55 + @click.stop="routeAgent(alert._source.agent_id).navigate()"
56 >
57 {{ alert._source.agent_id }}
58 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -70,7 +70,9 @@
70 agent_labels_customer:
71 <code
72 class="text-primary cursor-pointer"
73 - @click.stop="gotoCustomer({ code: alert._source.agent_labels_customer })"
73 + @click.stop="
74 + routeCustomer({ code: alert._source.agent_labels_customer }).navigate()
75 + "
76 >
77 {{ alert._source.agent_labels_customer }}
78 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -134,7 +136,10 @@
136 </template>
137 <template #value>
138 <template v-if="key === 'agent_id'">
137 - <code class="text-primary cursor-pointer" @click.stop="gotoAgent(`${value}`)">
139 + <code
140 + class="text-primary cursor-pointer"
141 + @click.stop="routeAgent(`${value}`).navigate()"
142 + >
143 {{ value }}
144 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
145 </code>
@@ -142,7 +147,9 @@
147 <template v-else-if="key === 'agent_labels_customer'">
148 <code
149 class="text-primary cursor-pointer"
145 - @click.stop="gotoCustomer(value ? { code: value.toString() } : undefined)"
150 + @click.stop="
151 + routeCustomer(value ? { code: value.toString() } : undefined).navigate()
152 + "
153 >
154 {{ value }}
155 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -218,9 +225,9 @@ import Badge from "@/components/common/Badge.vue"
225 import CardEntity from "@/components/common/cards/CardEntity.vue"
226 import CardKV from "@/components/common/cards/CardKV.vue"
227 import Icon from "@/components/common/Icon.vue"
221 -import { useGoto } from "@/composables/useGoto"
228 +import { useNavigation } from "@/composables/useNavigation"
229 import { useSettingsStore } from "@/stores/settings"
223 -import { formatDate } from "@/utils"
230 +import { formatDate } from "@/utils/format"
231
232 const props = defineProps<{ alert: Alert; hideActions?: boolean; embedded?: boolean }>()
233 const AlertActions = defineAsyncComponent(() => import("./AlertActions.vue"))
@@ -235,7 +242,7 @@ const MailIcon = "carbon:email"
242 const AgentIcon = "carbon:police"
243 const LinkIcon = "carbon:launch"
244
238 -const { gotoCustomer, gotoAgent } = useGoto()
245 +const { routeCustomer, routeAgent } = useNavigation()
246 const loading = ref(false)
247 const showDetails = ref(false)
248 const dFormats = useSettingsStore().dateFormat
frontend/src/components/alerts/AlertsFilters.vue
+3 -2
@@ -56,6 +56,7 @@
56 </template>
57
58 <script setup lang="ts">
59 +// TODO: refactor
60 import type { SelectOption } from "naive-ui"
61 import type { VNodeChild } from "vue"
62 import type { AlertsQueryTimeRange, AlertsSummaryQuery } from "@/api/endpoints/alerts"
@@ -143,10 +144,10 @@ watch(
144
145 onBeforeMount(() => {
146 if (!filters.value.timerange) {
146 - filters.value.timerange = timerangeOptions[3].value
147 + filters.value.timerange = timerangeOptions[3]?.value ?? "1h"
148 }
149 if (!filters.value.maxAlerts) {
149 - filters.value.maxAlerts = maxAlertsOptions[2].value
150 + filters.value.maxAlerts = maxAlertsOptions[2]?.value ?? 10
151 }
152 })
153 </script>
frontend/src/components/alerts/AlertsGraylogFilters.vue
+2 -2
@@ -56,10 +56,10 @@ const sizeOptions = [
56 { label: "20 Alert", value: 20 }
57 ]
58
59 -const sizeDefault = useStorage<number>("alert-size-default", sizeOptions[2].value, localStorage)
59 +const sizeDefault = useStorage<number>("alert-size-default", sizeOptions[2]?.value ?? 10, localStorage)
60 const timerangeDefault = useStorage<AlertsQueryTimeRange>(
61 "alert-timerange-default",
62 - timerangeOptions[3].value,
62 + timerangeOptions[3]?.value ?? "1h",
63 localStorage
64 )
65
frontend/src/components/alerts/AlertsSummary.vue
+3 -3
@@ -3,7 +3,7 @@
3 <template #headerMain>
4 <div
5 class="hover:text-primary flex cursor-pointer items-center gap-2"
6 - @click="gotoIndex(alertsSummary.index_name)"
6 + @click="routeIndex(alertsSummary.index_name).navigate()"
7 >
8 <IndexIcon v-if="alertsSummary.indexStats?.health" :health="alertsSummary.indexStats?.health" color />
9 <Icon v-else :name="PlaceholderIcon" :size="18" />
@@ -56,7 +56,7 @@ import { ref } from "vue"
56 import CardEntity from "@/components/common/cards/CardEntity.vue"
57 import Icon from "@/components/common/Icon.vue"
58 import IndexIcon from "@/components/indices/IndexIcon.vue"
59 -import { useGoto } from "@/composables/useGoto"
59 +import { useNavigation } from "@/composables/useNavigation"
60 import Alert from "./Alert.vue"
61
62 export interface AlertsSummaryExt extends AlertsSummary {
@@ -70,7 +70,7 @@ const PlaceholderIcon = "ph:question"
70 const LinkIcon = "carbon:launch"
71
72 const showAllAlerts = ref(false)
73 -const { gotoIndex } = useGoto()
73 +const { routeIndex } = useNavigation()
74 </script>
75
76 <style lang="scss" scoped>
frontend/src/components/artifacts/ArtifactsCollect.vue
+262 -230
@@ -63,7 +63,8 @@
63 </n-checkbox>
64 </template>
65 <div class="text-xs">
66 - Skip rendering results in frontend and only store the artifact in the data store.<br />
66 + Skip rendering results in frontend and only store the artifact in the data store.
67 + <br />
68 This is faster for large collections.
69 </div>
70 </n-tooltip>
@@ -88,7 +89,9 @@
89 <n-card title="Artifact Parameters" size="small">
90 <template #header-extra>
91 <n-tag size="small" type="info">
91 - {{ selectedArtifactParameters.length }} parameter{{ selectedArtifactParameters.length !== 1 ? 's' : '' }}
92 + {{ selectedArtifactParameters.length }} parameter{{
93 + selectedArtifactParameters.length !== 1 ? "s" : ""
94 + }}
95 </n-tag>
96 </template>
97 <n-spin :show="loadingParameters">
@@ -104,13 +107,18 @@
107 </div>
108 <n-popover v-if="param.description" trigger="hover" placement="top">
109 <template #trigger>
107 - <Icon :name="InfoIcon" :size="16" class="cursor-help text-gray-400 hover:text-gray-600" />
110 + <Icon
111 + :name="InfoIcon"
112 + :size="16"
113 + class="cursor-help text-gray-400 hover:text-gray-600"
114 + />
115 </template>
116 <div class="parameter-tooltip">
110 - <div class="font-medium mb-2">{{ param.name }}</div>
117 + <div class="mb-2 font-medium">{{ param.name }}</div>
118 <div class="text-sm">{{ param.description }}</div>
112 - <div v-if="param.default" class="text-xs mt-2 opacity-70">
113 - Default: <code class="bg-gray-100 px-1 rounded">{{ param.default }}</code>
119 + <div v-if="param.default" class="mt-2 text-xs opacity-70">
120 + Default:
121 + <code class="rounded bg-gray-100 px-1">{{ param.default }}</code>
122 </div>
123 </div>
124 </n-popover>
@@ -128,7 +136,11 @@
136 <n-button
137 text
138 size="tiny"
131 - @click="parameterValues[param.name] = escapeBackslashes(param.default?.toString() || '')"
139 + @click="
140 + parameterValues[param.name] = escapeBackslashes(
141 + param.default?.toString() || ''
142 + )
143 + "
144 >
145 <Icon name="carbon:reset" :size="14" />
146 </n-button>
@@ -150,8 +162,13 @@
162 <n-spin :show="loading">
163 <div class="my-7 flex min-h-52 flex-col gap-3">
164 <template v-if="filters.data_store_only && isDirty">
153 - <n-alert type="success" title="Artifact Stored Successfully" class="item-appear item-appear-bottom item-appear-005">
154 - The artifact has been collected and stored in the data store. Results were not retrieved to improve performance.
165 + <n-alert
166 + type="success"
167 + title="Artifact Stored Successfully"
168 + class="item-appear item-appear-bottom item-appear-005"
169 + >
170 + The artifact has been collected and stored in the data store. Results were not retrieved to
171 + improve performance.
172 </n-alert>
173 </template>
174 <template v-else-if="collectList.length">
@@ -172,10 +189,25 @@
189 </template>
190
191 <script setup lang="ts">
192 +// TODO: refactor
193 import type { ArtifactsQuery, CollectRequest } from "@/api/endpoints/artifacts"
194 import type { Agent } from "@/types/agents.d"
195 import type { Artifact, ArtifactParameter, CollectResult } from "@/types/artifacts.d"
178 -import { NAlert, NButton, NCard, NCheckbox, NEmpty, NInput, NPopover, NScrollbar, NSelect, NSpin, NTag, NTooltip, useMessage } from "naive-ui"
196 +import {
197 + NAlert,
198 + NButton,
199 + NCard,
200 + NCheckbox,
201 + NEmpty,
202 + NInput,
203 + NPopover,
204 + NScrollbar,
205 + NSelect,
206 + NSpin,
207 + NTag,
208 + NTooltip,
209 + useMessage
210 +} from "naive-ui"
211 import { nanoid } from "nanoid"
212 import { computed, nextTick, onBeforeMount, ref, toRefs } from "vue"
213 import Api from "@/api"
@@ -183,22 +215,22 @@ import Icon from "@/components/common/Icon.vue"
215 import CollectItem from "./CollectItem.vue"
216
217 const props = defineProps<{
186 - hostname?: string
187 - velociraptorId?: string
188 - agents?: Agent[]
189 - artifacts?: Artifact[]
190 - artifactsFilter?: ArtifactsQuery
191 - hideHostnameField?: boolean
192 - hideVelociraptorIdField?: boolean
218 + hostname?: string
219 + velociraptorId?: string
220 + agents?: Agent[]
221 + artifacts?: Artifact[]
222 + artifactsFilter?: ArtifactsQuery
223 + hideHostnameField?: boolean
224 + hideVelociraptorIdField?: boolean
225 }>()
226
227 const emit = defineEmits<{
196 - (e: "loaded-agents", value: Agent[]): void
197 - (e: "loaded-artifacts", value: Artifact[]): void
228 + (e: "loaded-agents", value: Agent[]): void
229 + (e: "loaded-artifacts", value: Artifact[]): void
230 }>()
231
232 const { hostname, velociraptorId, agents, artifacts, artifactsFilter, hideHostnameField, hideVelociraptorIdField } =
201 - toRefs(props)
233 + toRefs(props)
234
235 const message = useMessage()
236 const loadingAgents = ref(false)
@@ -215,273 +247,273 @@ const parameterValues = ref<Record<string, string>>({})
247 const InfoIcon = "carbon:information"
248
249 const total = computed<number>(() => {
218 - return collectList.value.length || 0
250 + return collectList.value.length || 0
251 })
252
253 const filters = ref<Partial<CollectRequest>>({
222 - data_store_only: false
254 + data_store_only: false
255 })
256
257 const areFiltersValid = computed(() => {
226 - return !!filters.value.artifact_name && !!filters.value.hostname
258 + return !!filters.value.artifact_name && !!filters.value.hostname
259 })
260
261 const agentHostnameOptions = computed(() => {
230 - if (hostname?.value) {
231 - return [{ value: hostname.value, label: hostname.value }]
232 - }
233 - return (agentsList.value || []).map(o => ({ value: o.hostname, label: o.hostname }))
262 + if (hostname?.value) {
263 + return [{ value: hostname.value, label: hostname.value }]
264 + }
265 + return (agentsList.value || []).map(o => ({ value: o.hostname, label: o.hostname }))
266 })
267
268 const artifactsOptions = computed(() => {
237 - return (artifactsList.value || []).map(o => ({ value: o.name, label: o.name }))
269 + return (artifactsList.value || []).map(o => ({ value: o.name, label: o.name }))
270 })
271
272 // Function to escape backslashes in Windows paths
273 function escapeBackslashes(value: string): string {
242 - // Only escape if it looks like a Windows path (contains backslashes)
243 - if (value && typeof value === 'string' && value.includes('\\')) {
244 - // Replace single backslashes with double backslashes
245 - return value.replace(/\\/g, '\\\\')
246 - }
247 - return value
274 + // Only escape if it looks like a Windows path (contains backslashes)
275 + if (value && typeof value === "string" && value.includes("\\")) {
276 + // Replace single backslashes with double backslashes
277 + return value.replace(/\\/g, "\\\\")
278 + }
279 + return value
280 }
281
282 async function onArtifactSelect(artifactName: string | null) {
251 - selectedArtifactParameters.value = []
252 - parameterValues.value = {}
253 -
254 - if (!artifactName) {
255 - return
256 - }
257 -
258 - loadingParameters.value = true
259 -
260 - try {
261 - const res = await Api.artifacts.getByName(artifactName)
262 -
263 - if (res.data.success && res.data.artifacts?.length) {
264 - const artifact = res.data.artifacts[0]
265 -
266 - if (artifact.parameters?.length) {
267 - selectedArtifactParameters.value = artifact.parameters
268 -
269 - // Initialize parameter values with defaults (with escaped backslashes)
270 - artifact.parameters.forEach(param => {
271 - if (param.default !== undefined && param.default !== null && param.default !== "") {
272 - const defaultValue = param.default.toString()
273 - parameterValues.value[param.name] = escapeBackslashes(defaultValue)
274 - }
275 - })
276 - }
277 - }
278 - } catch (err: any) {
279 - message.error(err.response?.data?.message || "Failed to load artifact parameters")
280 - } finally {
281 - loadingParameters.value = false
282 - }
283 + selectedArtifactParameters.value = []
284 + parameterValues.value = {}
285 +
286 + if (!artifactName) {
287 + return
288 + }
289 +
290 + loadingParameters.value = true
291 +
292 + try {
293 + const res = await Api.artifacts.getByName(artifactName)
294 +
295 + if (res.data.success && res.data.artifacts?.length) {
296 + const artifact = res.data.artifacts[0]
297 +
298 + if (artifact?.parameters?.length) {
299 + selectedArtifactParameters.value = artifact.parameters
300 +
301 + // Initialize parameter values with defaults (with escaped backslashes)
302 + artifact.parameters.forEach(param => {
303 + if (param.default !== undefined && param.default !== null && param.default !== "") {
304 + const defaultValue = param.default.toString()
305 + parameterValues.value[param.name] = escapeBackslashes(defaultValue)
306 + }
307 + })
308 + }
309 + }
310 + } catch (err: any) {
311 + message.error(err.response?.data?.message || "Failed to load artifact parameters")
312 + } finally {
313 + loadingParameters.value = false
314 + }
315 }
316
317 function getData() {
286 - if (areFiltersValid.value) {
287 - loading.value = true
288 -
289 - // Build parameters object if any values are set
290 - const parameters: CollectRequest["parameters"] = {
291 - env: []
292 - }
293 -
294 - Object.entries(parameterValues.value).forEach(([key, value]) => {
295 - if (value !== undefined && value !== null && value !== "") {
296 - parameters.env!.push({ key, value })
297 - }
298 - })
299 -
300 - const payload: CollectRequest = {
301 - ...filters.value,
302 - hostname: filters.value.hostname!,
303 - artifact_name: filters.value.artifact_name!,
304 - data_store_only: filters.value.data_store_only || false
305 - }
306 -
307 - // Only add parameters if there are any
308 - if (parameters.env!.length > 0) {
309 - payload.parameters = parameters
310 - }
311 -
312 - Api.artifacts
313 - .collect(payload)
314 - .then(res => {
315 - if (res.data.success) {
316 - isDirty.value = true
317 -
318 - // If data_store_only, results will be null/empty
319 - if (filters.value.data_store_only) {
320 - collectList.value = []
321 - message.success(res.data?.message || "Artifact stored successfully")
322 - } else {
323 - collectList.value = (res.data?.results || []).map(o => {
324 - o.___id = nanoid()
325 - return o
326 - })
327 - }
328 - } else {
329 - message.warning(res.data?.message || "An error occurred. Please try again later.")
330 - }
331 - })
332 - .catch(err => {
333 - collectList.value = []
334 -
335 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
336 - })
337 - .finally(() => {
338 - loading.value = false
339 - })
340 - }
318 + if (areFiltersValid.value) {
319 + loading.value = true
320 +
321 + // Build parameters object if any values are set
322 + const parameters: CollectRequest["parameters"] = {
323 + env: []
324 + }
325 +
326 + Object.entries(parameterValues.value).forEach(([key, value]) => {
327 + if (value !== undefined && value !== null && value !== "") {
328 + parameters.env!.push({ key, value })
329 + }
330 + })
331 +
332 + const payload: CollectRequest = {
333 + ...filters.value,
334 + hostname: filters.value.hostname!,
335 + artifact_name: filters.value.artifact_name!,
336 + data_store_only: filters.value.data_store_only || false
337 + }
338 +
339 + // Only add parameters if there are any
340 + if (parameters.env!.length > 0) {
341 + payload.parameters = parameters
342 + }
343 +
344 + Api.artifacts
345 + .collect(payload)
346 + .then(res => {
347 + if (res.data.success) {
348 + isDirty.value = true
349 +
350 + // If data_store_only, results will be null/empty
351 + if (filters.value.data_store_only) {
352 + collectList.value = []
353 + message.success(res.data?.message || "Artifact stored successfully")
354 + } else {
355 + collectList.value = (res.data?.results || []).map(o => {
356 + o.___id = nanoid()
357 + return o
358 + })
359 + }
360 + } else {
361 + message.warning(res.data?.message || "An error occurred. Please try again later.")
362 + }
363 + })
364 + .catch(err => {
365 + collectList.value = []
366 +
367 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
368 + })
369 + .finally(() => {
370 + loading.value = false
371 + })
372 + }
373 }
374
375 function getAgents(cb?: (agents: Agent[]) => void) {
344 - loadingAgents.value = true
345 -
346 - Api.agents
347 - .getAgents()
348 - .then(res => {
349 - if (res.data.success) {
350 - agentsList.value = res.data.agents || []
351 -
352 - if (cb && typeof cb === "function") {
353 - cb(agentsList.value)
354 - }
355 - } else {
356 - message.error(res.data?.message || "An error occurred. Please try again later.")
357 - }
358 - })
359 - .catch(err => {
360 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
361 - })
362 - .finally(() => {
363 - loadingAgents.value = false
364 - })
376 + loadingAgents.value = true
377 +
378 + Api.agents
379 + .getAgents()
380 + .then(res => {
381 + if (res.data.success) {
382 + agentsList.value = res.data.agents || []
383 +
384 + if (cb && typeof cb === "function") {
385 + cb(agentsList.value)
386 + }
387 + } else {
388 + message.error(res.data?.message || "An error occurred. Please try again later.")
389 + }
390 + })
391 + .catch(err => {
392 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
393 + })
394 + .finally(() => {
395 + loadingAgents.value = false
396 + })
397 }
398
399 function getArtifacts(cb?: (artifacts: Artifact[]) => void) {
368 - loadingArtifacts.value = true
369 -
370 - Api.artifacts
371 - .getAll(artifactsFilter.value)
372 - .then(res => {
373 - if (res.data.success) {
374 - artifactsList.value = res.data.artifacts || []
375 -
376 - if (cb && typeof cb === "function") {
377 - cb(artifactsList.value)
378 - }
379 - } else {
380 - message.error(res.data?.message || "An error occurred. Please try again later.")
381 - }
382 - })
383 - .catch(err => {
384 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
385 - })
386 - .finally(() => {
387 - loadingArtifacts.value = false
388 - })
400 + loadingArtifacts.value = true
401 +
402 + Api.artifacts
403 + .getAll(artifactsFilter.value)
404 + .then(res => {
405 + if (res.data.success) {
406 + artifactsList.value = res.data.artifacts || []
407 +
408 + if (cb && typeof cb === "function") {
409 + cb(artifactsList.value)
410 + }
411 + } else {
412 + message.error(res.data?.message || "An error occurred. Please try again later.")
413 + }
414 + })
415 + .catch(err => {
416 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
417 + })
418 + .finally(() => {
419 + loadingArtifacts.value = false
420 + })
421 }
422
423 onBeforeMount(() => {
392 - if (hostname?.value) {
393 - filters.value.hostname = hostname.value
394 - }
395 -
396 - if (velociraptorId?.value) {
397 - filters.value.velociraptor_id = velociraptorId.value
398 - }
399 -
400 - if (agents?.value?.length && !agentsList.value.length) {
401 - agentsList.value = agents.value
402 - }
403 -
404 - if (artifacts?.value?.length && !artifactsList.value.length) {
405 - artifactsList.value = artifacts.value
406 - }
407 -
408 - nextTick(() => {
409 - if (!agentsList.value.length && !hostname?.value) {
410 - getAgents((agents: Agent[]) => {
411 - emit("loaded-agents", agents)
412 - })
413 - }
414 - if (!artifactsList.value.length) {
415 - getArtifacts((artifacts: Artifact[]) => {
416 - emit("loaded-artifacts", artifacts)
417 - })
418 - }
419 - })
424 + if (hostname?.value) {
425 + filters.value.hostname = hostname.value
426 + }
427 +
428 + if (velociraptorId?.value) {
429 + filters.value.velociraptor_id = velociraptorId.value
430 + }
431 +
432 + if (agents?.value?.length && !agentsList.value.length) {
433 + agentsList.value = agents.value
434 + }
435 +
436 + if (artifacts?.value?.length && !artifactsList.value.length) {
437 + artifactsList.value = artifacts.value
438 + }
439 +
440 + nextTick(() => {
441 + if (!agentsList.value.length && !hostname?.value) {
442 + getAgents((agents: Agent[]) => {
443 + emit("loaded-agents", agents)
444 + })
445 + }
446 + if (!artifactsList.value.length) {
447 + getArtifacts((artifacts: Artifact[]) => {
448 + emit("loaded-artifacts", artifacts)
449 + })
450 + }
451 + })
452 })
453 </script>
454
455 <style scoped>
456 .parameters-grid {
425 - display: grid;
426 - gap: 1rem;
427 - padding: 0.5rem;
457 + display: grid;
458 + gap: 1rem;
459 + padding: 0.5rem;
460 }
461
462 .parameter-field {
431 - background-color: #f9fafb;
432 - border: 1px solid #e5e7eb;
433 - border-radius: 8px;
434 - padding: 1rem;
435 - transition: all 0.2s ease;
463 + background-color: #f9fafb;
464 + border: 1px solid #e5e7eb;
465 + border-radius: 8px;
466 + padding: 1rem;
467 + transition: all 0.2s ease;
468 }
469
470 .parameter-field:hover {
439 - border-color: #d1d5db;
440 - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
471 + border-color: #d1d5db;
472 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
473 }
474
475 .parameter-header {
444 - display: flex;
445 - align-items: center;
446 - justify-content: space-between;
447 - margin-bottom: 0.5rem;
476 + display: flex;
477 + align-items: center;
478 + justify-content: space-between;
479 + margin-bottom: 0.5rem;
480 }
481
482 .parameter-name {
451 - font-weight: 600;
452 - font-size: 0.875rem;
453 - color: #374151;
483 + font-weight: 600;
484 + font-size: 0.875rem;
485 + color: #374151;
486 }
487
488 .parameter-description {
457 - font-size: 0.75rem;
458 - color: #6b7280;
459 - margin-top: 0.5rem;
460 - line-height: 1.4;
489 + font-size: 0.75rem;
490 + color: #6b7280;
491 + margin-top: 0.5rem;
492 + line-height: 1.4;
493 }
494
495 .parameter-tooltip {
464 - max-width: 400px;
496 + max-width: 400px;
497 }
498
499 /* Dark mode support */
500 @media (prefers-color-scheme: dark) {
469 - .parameter-field {
470 - background-color: #1f2937;
471 - border-color: #374151;
472 - }
473 -
474 - .parameter-field:hover {
475 - border-color: #4b5563;
476 - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
477 - }
478 -
479 - .parameter-name {
480 - color: #f3f4f6;
481 - }
482 -
483 - .parameter-description {
484 - color: #9ca3af;
485 - }
501 + .parameter-field {
502 + background-color: #1f2937;
503 + border-color: #374151;
504 + }
505 +
506 + .parameter-field:hover {
507 + border-color: #4b5563;
508 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
509 + }
510 +
511 + .parameter-name {
512 + color: #f3f4f6;
513 + }
514 +
515 + .parameter-description {
516 + color: #9ca3af;
517 + }
518 }
519 </style>
frontend/src/components/artifacts/ArtifactsCommand.vue
+1 -1
@@ -122,8 +122,8 @@ import Api from "@/api"
122 import Badge from "@/components/common/Badge.vue"
123 import Icon from "@/components/common/Icon.vue"
124 import { useSettingsStore } from "@/stores/settings"
125 -import { formatDate } from "@/utils"
125 import dayjs from "@/utils/dayjs"
126 +import { formatDate } from "@/utils/format"
127 import CommandItem from "./CommandItem.vue"
128
129 const props = defineProps<{
frontend/src/components/artifacts/ArtifactsList.vue
+3
@@ -125,6 +125,7 @@
125 </template>
126
127 <script setup lang="ts">
128 +// TODO: refactor
129 import type { ArtifactsQuery } from "@/api/endpoints/artifacts"
130 import type { Agent } from "@/types/agents.d"
131 import type { Artifact } from "@/types/artifacts.d"
@@ -262,6 +263,8 @@ function getAgents(cb?: (agents: Agent[]) => void) {
263
264 useResizeObserver(header, entries => {
265 const entry = entries[0]
266 + if (!entry) return
267 +
268 const { width } = entry.contentRect
269
270 pageSlot.value = width < 650 ? 5 : 8
frontend/src/components/artifacts/ArtifactsQuarantine.vue
+2 -1
@@ -78,6 +78,7 @@
78 </template>
79
80 <script setup lang="ts">
81 +// TODO: refactor
82 import type { QuarantineRequest } from "@/api/endpoints/artifacts"
83 import type { Agent } from "@/types/agents.d"
84 import type { Artifact, QuarantineResult } from "@/types/artifacts.d"
@@ -225,7 +226,7 @@ onBeforeMount(() => {
226 artifactsList.value = artifacts.value
227 }
228
228 - filters.value.action = actionsOptions.value[0].value as QuarantineRequest["action"]
229 + filters.value.action = actionsOptions.value[0]?.value as QuarantineRequest["action"]
230
231 nextTick(() => {
232 if (!agentsList.value.length && !hostname?.value) {
frontend/src/components/artifacts/CollectItem.vue
+1 -1
@@ -40,8 +40,8 @@ import { SimpleJsonViewer } from "vue-sjv"
40 import CardEntity from "@/components/common/cards/CardEntity.vue"
41 import CardKV from "@/components/common/cards/CardKV.vue"
42 import { useSettingsStore } from "@/stores/settings"
43 -import { formatDate } from "@/utils"
43 import dayjs from "@/utils/dayjs"
44 +import { formatDate } from "@/utils/format"
45 import "@/assets/scss/overrides/vuesjv-override.scss"
46
47 interface Prop {
frontend/src/components/artifacts/QuarantineItem.vue
+1 -1
@@ -13,7 +13,7 @@
13 import type { QuarantineResult } from "@/types/artifacts.d"
14 import CardEntity from "@/components/common/cards/CardEntity.vue"
15 import { useSettingsStore } from "@/stores/settings"
16 -import { formatDate } from "@/utils"
16 +import { formatDate } from "@/utils/format"
17
18 const { quarantine } = defineProps<{ quarantine: QuarantineResult }>()
19
frontend/src/components/auth/SignUp.vue
+2 -1
@@ -158,6 +158,7 @@
158 </template>
159
160 <script lang="ts" setup>
161 +// TODO: refactor
162 import type { FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui"
163 import type { SelectBaseOption } from "naive-ui/es/select/src/interface"
164 import type { RegisterPayload } from "@/types/auth.d"
@@ -411,7 +412,7 @@ function signUp(e: Event) {
412 })
413 } else {
414 for (const err of errors) {
414 - message.error(err[0].message || "Invalid fields")
415 + message.error(err[0]?.message ?? "Invalid fields")
416 }
417 }
418 })
frontend/src/components/common/AccessDenied.vue
+12 -15
@@ -10,17 +10,13 @@
10 <n-space justify="center">
11 <n-button type="primary" @click="goBack">
12 <template #icon>
13 - <n-icon>
14 - <ArrowBackOutline />
15 - </n-icon>
13 + <Icon name="carbon:arrow-left" />
14 </template>
15 Go Back
16 </n-button>
17 <n-button @click="goHome">
18 <template #icon>
21 - <n-icon>
22 - <HomeOutline />
23 - </n-icon>
19 + <Icon name="carbon:home" />
20 </template>
21 Go to Dashboard
22 </n-button>
@@ -31,11 +27,12 @@
27 </template>
28
29 <script setup lang="ts">
34 -import { ArrowBackOutline, HomeOutline } from '@vicons/ionicons5'
35 -import { useRouter } from 'vue-router'
30 +// TODO: refactor
31 +import { useRouter } from "vue-router"
32 +import Icon from "@/components/common/Icon.vue"
33
34 interface Props {
38 - description?: string
35 + description?: string
36 }
37
38 defineProps<Props>()
@@ -43,19 +40,19 @@ defineProps<Props>()
40 const router = useRouter()
41
42 function goBack() {
46 - router.back()
43 + router.back()
44 }
45
46 function goHome() {
50 - router.push('/')
47 + router.push("/")
48 }
49 </script>
50
51 <style scoped>
52 .access-denied-container {
56 - display: flex;
57 - align-items: center;
58 - justify-content: center;
59 - min-height: 60vh;
53 + display: flex;
54 + align-items: center;
55 + justify-content: center;
56 + min-height: 60vh;
57 }
58 </style>
frontend/src/components/common/Markdown.vue
+15 -13
@@ -53,20 +53,22 @@ function markdownItLinkTargetBlank(md: MarkdownIt): void {
53 md.renderer.rules.link_open = function (tokens: Token[], idx: number, options, env, self) {
54 const token = tokens[idx]
55
56 - // Aggiungi target="_blank"
57 - const targetIndex = token.attrIndex("target")
58 - if (targetIndex < 0) {
59 - token.attrPush(["target", "_blank"])
60 - } else {
61 - token.attrs![targetIndex][1] = "_blank"
62 - }
56 + if (token) {
57 + // Aggiungi target="_blank"
58 + const targetIndex = token.attrIndex("target")
59 + if (targetIndex < 0) {
60 + token.attrPush(["target", "_blank"])
61 + } else if (token.attrs?.[targetIndex]?.[1]) {
62 + token.attrs[targetIndex][1] = "_blank"
63 + }
64
64 - // Aggiungi rel="noopener noreferrer"
65 - const relIndex = token.attrIndex("rel")
66 - if (relIndex < 0) {
67 - token.attrPush(["rel", "noopener noreferrer"])
68 - } else {
69 - token.attrs![relIndex][1] = "noopener noreferrer"
65 + // Aggiungi rel="noopener noreferrer"
66 + const relIndex = token.attrIndex("rel")
67 + if (relIndex < 0) {
68 + token.attrPush(["rel", "noopener noreferrer"])
69 + } else if (token.attrs?.[relIndex]?.[1]) {
70 + token.attrs[relIndex][1] = "noopener noreferrer"
71 + }
72 }
73
74 return defaultRender(tokens, idx, options, env, self)
frontend/src/components/common/SearchDialog.vue
+38 -26
@@ -77,7 +77,7 @@ import { computed, onMounted, ref } from "vue"
77 import Highlighter from "vue-highlight-words"
78 import Icon from "@/components/common/Icon.vue"
79 import { useFullscreenSwitch } from "@/composables/useFullscreenSwitch"
80 -import { useGoto } from "@/composables/useGoto"
80 +import { useNavigation } from "@/composables/useNavigation"
81 import { useSearchDialog } from "@/composables/useSearchDialog"
82 import { useThemeSwitch } from "@/composables/useThemeSwitch"
83 import { emitter } from "@/emitter"
@@ -116,7 +116,7 @@ const search = ref("")
116 const activeItem = ref<null | string | number>(null)
117 const commandIcon = ref("⌘")
118 const scrollContent = ref<(ScrollbarInst & { $el: HTMLElement }) | null>(null)
119 -const { gotoCustomer, gotoSocAlerts, gotoAlerts, gotoConnectors } = useGoto()
119 +const { routeCustomer, routeSocAlerts, routeAlerts, routeConnectors } = useNavigation()
120
121 // TODO: review the groups and items
122 const groups = ref<Groups>([
@@ -130,7 +130,7 @@ const groups = ref<Groups>([
130 title: "Add a Customer",
131 label: "Shortcut",
132 action() {
133 - gotoCustomer({ action: "add-customer" })
133 + routeCustomer({ action: "add-customer" }).navigate()
134 emitter.emit("action:add-customer")
135 }
136 },
@@ -141,7 +141,7 @@ const groups = ref<Groups>([
141 title: "Configure a connector",
142 label: "Shortcut",
143 action() {
144 - gotoConnectors()
144 + routeConnectors().navigate()
145 }
146 },
147 {
@@ -151,7 +151,7 @@ const groups = ref<Groups>([
151 title: "View Escalated Alerts",
152 label: "Shortcut",
153 action() {
154 - gotoSocAlerts()
154 + routeSocAlerts().navigate()
155 }
156 },
157 {
@@ -161,7 +161,7 @@ const groups = ref<Groups>([
161 title: "View Identified Alerts",
162 label: "Shortcut",
163 action() {
164 - gotoAlerts()
164 + routeAlerts().navigate()
165 }
166 }
167 ]
@@ -239,24 +239,32 @@ function callAction(action: () => void) {
239 closeBox()
240 }
241
242 -function nextItem() {
243 - const currentIndex = filteredFlattenItems.value.findIndex(item => item.key === activeItem.value)
244 - if (currentIndex === filteredFlattenItems.value.length - 1 || activeItem.value === null) {
245 - activeItem.value = filteredFlattenItems.value[0].key
242 +function navigateItem(direction: "next" | "prev") {
243 + const items = filteredFlattenItems.value
244 + if (!items.length) return
245 +
246 + const currentIndex = items.findIndex(item => item.key === activeItem.value)
247 + const isAtEnd = currentIndex === items.length - 1
248 + const isAtStart = currentIndex === 0
249 + const nextItem = items[currentIndex + 1]
250 + const prevItem = items[currentIndex - 1]
251 + const firstItem = items[0]
252 + const lastItem = items[items.length - 1]
253 +
254 + if (direction === "next") {
255 + activeItem.value = (activeItem.value === null || isAtEnd) && firstItem ? firstItem.key : (nextItem?.key ?? null)
256 } else {
247 - activeItem.value = filteredFlattenItems.value[currentIndex + 1].key
257 + activeItem.value = (activeItem.value === null || isAtStart) && lastItem ? lastItem.key : (prevItem?.key ?? null)
258 }
259 centerItem()
260 }
261
262 +function nextItem() {
263 + navigateItem("next")
264 +}
265 +
266 function prevItem() {
253 - const currentIndex = filteredFlattenItems.value.findIndex(item => item.key === activeItem.value)
254 - if (currentIndex === 0 || activeItem.value === null) {
255 - activeItem.value = filteredFlattenItems.value[filteredFlattenItems.value.length - 1].key
256 - } else {
257 - activeItem.value = filteredFlattenItems.value[currentIndex - 1].key
258 - }
259 - centerItem()
267 + navigateItem("prev")
268 }
269
270 function performAction() {
@@ -283,15 +291,19 @@ onMounted(() => {
291
292 useSearchDialog().trigger(openBox)
293
286 - whenever(ActiveCMD, () => {
287 - openBox()
288 - })
294 + if (ActiveCMD) {
295 + whenever(ActiveCMD, () => {
296 + openBox()
297 + })
298 + }
299
290 - whenever(Enter, () => {
291 - if (showSearchBox.value) {
292 - performAction()
293 - }
294 - })
300 + if (Enter) {
301 + whenever(Enter, () => {
302 + if (showSearchBox.value) {
303 + performAction()
304 + }
305 + })
306 + }
307 })
308 </script>
309
frontend/src/components/common/SegmentedPage.vue
+1 -2
@@ -1,6 +1,5 @@
1 <template>
2 <n-split
3 - ref="splitPane"
3 direction="horizontal"
4 :default-size="sanitizedDefaultSplit"
5 :min="0"
@@ -76,6 +75,7 @@
75 </template>
76
77 <script setup lang="ts">
78 +// TODO: refactor
79 import type { SetupContext } from "vue"
80 import { onClickOutside, useWindowSize } from "@vueuse/core"
81 import { NButton, NScrollbar, NSplit } from "naive-ui"
@@ -135,7 +135,6 @@ const MenuIcon = "ph:list-light"
135 const CloseIcon = "carbon:close"
136 const SplitIcon = "carbon:draggable"
137
138 -const splitPane = ref()
138 const sanitizedDefaultSplit = ref(defaultSplit)
139 const splitDisabled = ref(false)
140
frontend/src/components/common/VersionUpdateBanner.vue
+1
@@ -42,6 +42,7 @@
42 </template>
43
44 <script setup lang="ts">
45 +// TODO: refactor
46 import type { VersionCheckResponse } from "@/types/version.d"
47 import { NAlert, NButton, NCollapseTransition, NDivider } from "naive-ui"
48 import { computed, onMounted, ref } from "vue"
frontend/src/components/common/XMLEditor.vue
+1 -1
@@ -74,7 +74,7 @@ function convertXMLErrorsToDiagnostics(errors: XMLError[], text: string): Diagno
74 // Calculate position in text
75 let from = 0
76 for (let i = 0; i < error.line - 1; i++) {
77 - from += lines[i].length + 1 // +1 for newline
77 + from += (lines[i]?.length ?? 0) + 1 // +1 for newline
78 }
79 from += error.column - 1
80
frontend/src/components/common/cards/CardStatsMulti.vue
+1 -5
@@ -10,7 +10,7 @@
10 <slot name="icon"></slot>
11 </div>
12 </div>
13 - <div class="card-content flex grow">
13 + <div class="card-content divide-border grid grow grid-cols-[repeat(auto-fit,minmax(0,1fr))] divide-x">
14 <div
15 v-for="item of values"
16 :key="JSON.stringify(item)"
@@ -72,10 +72,6 @@ const ArrowRightIcon = "carbon:arrow-right"
72 text-align: center;
73 overflow: hidden;
74
75 - &:not(:last-child) {
76 - border-right: 1px solid var(--border-color);
77 - }
78 -
75 .value {
76 font-family: var(--font-family-display);
77 padding: 10px 6px;
frontend/src/components/copilotAction/ActionCardContent.vue
+1 -1
@@ -135,7 +135,7 @@ import Badge from "@/components/common/Badge.vue"
135 import CardEntity from "@/components/common/cards/CardEntity.vue"
136 import CardKV from "@/components/common/cards/CardKV.vue"
137 import { useSettingsStore } from "@/stores/settings"
138 -import { formatDate } from "@/utils"
138 +import { formatDate } from "@/utils/format"
139
140 const { action } = defineProps<{
141 action: CopilotAction
frontend/src/components/copilotAction/InvokeActionForm.vue
+164 -176
@@ -10,12 +10,12 @@
10 </template>
11 Only showing agents compatible with {{ action.technology }} technology
12 <template v-if="incompatibleAgentCount > 0">
13 - ({{ incompatibleAgentCount }} incompatible agent{{ incompatibleAgentCount !== 1 ? 's' : '' }} hidden)
13 + ({{ incompatibleAgentCount }} incompatible agent{{ incompatibleAgentCount !== 1 ? "s" : "" }} hidden)
14 </template>
15 </n-alert>
16
17 <n-form-item label="Target Agents" path="agent_names" required>
18 - <div class="flex flex-col gap-2 w-full">
18 + <div class="flex w-full flex-col gap-2">
19 <n-select
20 v-model:value="formValue.agent_names"
21 :options="filteredAgentOptions"
@@ -29,7 +29,7 @@
29 <n-empty description="No compatible agents found" />
30 </template>
31 </n-select>
32 - <div class="flex gap-2 justify-end">
32 + <div class="flex justify-end gap-2">
33 <n-button
34 secondary
35 type="primary"
@@ -42,12 +42,7 @@
42 </template>
43 Select All
44 </n-button>
45 - <n-button
46 - v-if="formValue.agent_names.length > 0"
47 - secondary
48 - size="small"
49 - @click="clearAllAgents"
50 - >
45 + <n-button v-if="formValue.agent_names.length > 0" secondary size="small" @click="clearAllAgents">
46 <template #icon>
47 <Icon name="carbon:close" />
48 </template>
@@ -61,11 +56,7 @@
56 <div v-if="action?.script_parameters?.length" class="mb-4">
57 <n-divider>Parameters</n-divider>
58 <div v-for="param in action.script_parameters" :key="param.name" class="mb-3">
64 - <n-form-item
65 - :label="param.name"
66 - :path="`parameters.${param.name}`"
67 - :required="param.required"
68 - >
59 + <n-form-item :label="param.name" :path="`parameters.${param.name}`" :required="param.required">
60 <template #label>
61 <div class="flex items-center gap-2">
62 <span>{{ param.name }}</span>
@@ -76,15 +67,12 @@
67 </template>
68
69 <!-- Boolean Parameter -->
79 - <n-switch
80 - v-if="param.type === 'boolean'"
81 - v-model:value="formValue.parameters[param.name]"
82 - />
70 + <n-switch v-if="param.type === 'boolean'" v-model:value="formValue.parameters[param.name]" />
71
72 <!-- Integer Parameter -->
73 <n-input-number
74 v-else-if="param.type === 'integer'"
87 - v-model:value="formValue.parameters[param.name]"
75 + v-model:value="formValue.parameters[param.name] as number"
76 :placeholder="param.default?.toString() || 'Enter value...'"
77 class="w-full"
78 />
@@ -92,7 +80,7 @@
80 <!-- String with Enum (Select) -->
81 <n-select
82 v-else-if="param.enum?.length"
95 - v-model:value="formValue.parameters[param.name]"
83 + v-model:value="formValue.parameters[param.name] as string"
84 :options="param.enum.map(v => ({ label: v, value: v }))"
85 :placeholder="param.default?.toString() || 'Select value...'"
86 />
@@ -100,7 +88,7 @@
88 <!-- String Parameter -->
89 <n-input
90 v-else
103 - v-model:value="formValue.parameters[param.name]"
91 + v-model:value="formValue.parameters[param.name] as string"
92 :placeholder="param.default?.toString() || 'Enter value...'"
93 clearable
94 />
@@ -114,12 +102,7 @@
102
103 <div class="flex justify-end gap-2">
104 <n-button @click="emit('close')">Cancel</n-button>
117 - <n-button
118 - type="primary"
119 - :loading="loading"
120 - :disabled="!isFormValid"
121 - @click="handleInvoke"
122 - >
105 + <n-button type="primary" :loading="loading" :disabled="!isFormValid" @click="handleInvoke">
106 <template #icon>
107 <Icon :name="PlayIcon" />
108 </template>
@@ -130,22 +113,23 @@
113 </template>
114
115 <script setup lang="ts">
116 +// TODO: refactor
117 import type { FormInst, FormRules } from "naive-ui"
118 import type { Agent } from "@/types/agents.d"
119 import type { CopilotAction } from "@/types/copilotAction.d"
120 import {
137 - NAlert,
138 - NButton,
139 - NDivider,
140 - NEmpty,
141 - NForm,
142 - NFormItem,
143 - NInput,
144 - NInputNumber,
145 - NSelect,
146 - NSwitch,
147 - NTag,
148 - useMessage
121 + NAlert,
122 + NButton,
123 + NDivider,
124 + NEmpty,
125 + NForm,
126 + NFormItem,
127 + NInput,
128 + NInputNumber,
129 + NSelect,
130 + NSwitch,
131 + NTag,
132 + useMessage
133 } from "naive-ui"
134 import { computed, onBeforeMount, ref } from "vue"
135 import Api from "@/api"
@@ -153,12 +137,12 @@ import Icon from "@/components/common/Icon.vue"
137 import TechnologyIcon from "./TechnologyIcon.vue"
138
139 const { action } = defineProps<{
156 - action: CopilotAction
140 + action: CopilotAction
141 }>()
142
143 const emit = defineEmits<{
160 - (e: "success"): void
161 - (e: "close"): void
144 + (e: "success"): void
145 + (e: "close"): void
146 }>()
147
148 const message = useMessage()
@@ -169,181 +153,185 @@ const agents = ref<Agent[]>([])
153 const PlayIcon = "carbon:play"
154
155 const formValue = ref<{
172 - agent_names: string[]
173 - parameters: Record<string, string | number | boolean>
156 + agent_names: string[]
157 + parameters: Record<string, string | number | boolean>
158 }>({
175 - agent_names: [],
176 - parameters: {}
159 + agent_names: [],
160 + parameters: {}
161 })
162
163 // Initialize default parameter values
164 onBeforeMount(() => {
181 - if (action?.script_parameters?.length) {
182 - action.script_parameters.forEach(param => {
183 - if (param.default !== undefined && param.default !== null) {
184 - formValue.value.parameters[param.name] = param.default
185 - }
186 - })
187 - }
188 - getAgents()
165 + if (action?.script_parameters?.length) {
166 + action.script_parameters.forEach(param => {
167 + if (param.default !== undefined && param.default !== null) {
168 + formValue.value.parameters[param.name] = param.default
169 + }
170 + })
171 + }
172 + getAgents()
173 })
174
175 // Helper function to determine if agent OS is compatible with technology
176 function isAgentCompatible(agent: Agent, technology: string): boolean {
193 - const agentOS = agent.os?.toLowerCase() || ''
194 - const tech = technology.toLowerCase()
195 -
196 - switch (tech) {
197 - case 'linux':
198 - return agentOS.includes('linux') ||
199 - agentOS.includes('ubuntu') ||
200 - agentOS.includes('debian') ||
201 - agentOS.includes('centos') ||
202 - agentOS.includes('red hat') ||
203 - agentOS.includes('fedora') ||
204 - agentOS.includes('suse')
205 -
206 - case 'windows':
207 - return agentOS.includes('windows')
208 -
209 - case 'macos':
210 - case 'darwin':
211 - return agentOS.includes('darwin') || agentOS.includes('macos')
212 -
213 - // For other technologies or if no specific filtering is needed
214 - default:
215 - return true
216 - }
177 + const agentOS = agent.os?.toLowerCase() || ""
178 + const tech = technology.toLowerCase()
179 +
180 + switch (tech) {
181 + case "linux":
182 + return (
183 + agentOS.includes("linux") ||
184 + agentOS.includes("ubuntu") ||
185 + agentOS.includes("debian") ||
186 + agentOS.includes("centos") ||
187 + agentOS.includes("red hat") ||
188 + agentOS.includes("fedora") ||
189 + agentOS.includes("suse")
190 + )
191 +
192 + case "windows":
193 + return agentOS.includes("windows")
194 +
195 + case "macos":
196 + case "darwin":
197 + return agentOS.includes("darwin") || agentOS.includes("macos")
198 +
199 + // For other technologies or if no specific filtering is needed
200 + default:
201 + return true
202 + }
203 }
204
205 // Filtered agent options based on technology
206 const filteredAgentOptions = computed(() => {
221 - if (!action?.technology) {
222 - return agents.value.map(a => ({
223 - label: `${a.hostname} (${a.os})`,
224 - value: a.hostname
225 - }))
226 - }
227 -
228 - return agents.value
229 - .filter(agent => isAgentCompatible(agent, action.technology))
230 - .map(a => ({
231 - label: `${a.hostname} (${a.os})`,
232 - value: a.hostname
233 - }))
207 + if (!action?.technology) {
208 + return agents.value.map(a => ({
209 + label: `${a.hostname} (${a.os})`,
210 + value: a.hostname
211 + }))
212 + }
213 +
214 + return agents.value
215 + .filter(agent => isAgentCompatible(agent, action.technology))
216 + .map(a => ({
217 + label: `${a.hostname} (${a.os})`,
218 + value: a.hostname
219 + }))
220 })
221
222 // Count of incompatible agents (for display purposes)
223 const incompatibleAgentCount = computed(() => {
238 - if (!action?.technology) return 0
224 + if (!action?.technology) return 0
225
240 - return agents.value.filter(agent => !isAgentCompatible(agent, action.technology)).length
226 + return agents.value.filter(agent => !isAgentCompatible(agent, action.technology)).length
227 })
228
229 // Select all filtered agents
230 function selectAllAgents() {
245 - formValue.value.agent_names = filteredAgentOptions.value.map(option => option.value)
246 - message.success(`Selected ${formValue.value.agent_names.length} agent${formValue.value.agent_names.length !== 1 ? 's' : ''}`)
231 + formValue.value.agent_names = filteredAgentOptions.value.map(option => option.value)
232 + message.success(
233 + `Selected ${formValue.value.agent_names.length} agent${formValue.value.agent_names.length !== 1 ? "s" : ""}`
234 + )
235 }
236
237 // Clear all selected agents
238 function clearAllAgents() {
251 - formValue.value.agent_names = []
252 - message.info('Cleared all agents')
239 + formValue.value.agent_names = []
240 + message.info("Cleared all agents")
241 }
242
243 const rules: FormRules = {
256 - agent_names: {
257 - type: 'array',
258 - required: true,
259 - message: 'Please select at least one agent',
260 - trigger: ['blur', 'change']
261 - }
244 + agent_names: {
245 + type: "array",
246 + required: true,
247 + message: "Please select at least one agent",
248 + trigger: ["blur", "change"]
249 + }
250 }
251
252 // Add dynamic rules for required parameters
253 if (action?.script_parameters?.length) {
266 - action.script_parameters.forEach(param => {
267 - if (param.required) {
268 - rules[`parameters.${param.name}`] = {
269 - required: true,
270 - message: `${param.name} is required`,
271 - trigger: ['blur', 'change']
272 - }
273 - }
274 - })
254 + action.script_parameters.forEach(param => {
255 + if (param.required) {
256 + rules[`parameters.${param.name}`] = {
257 + required: true,
258 + message: `${param.name} is required`,
259 + trigger: ["blur", "change"]
260 + }
261 + }
262 + })
263 }
264
265 const isFormValid = computed(() => {
278 - if (!formValue.value.agent_names.length) return false
279 -
280 - // Check required parameters
281 - if (action?.script_parameters?.length) {
282 - for (const param of action.script_parameters) {
283 - if (param.required) {
284 - const value = formValue.value.parameters[param.name]
285 - if (value === undefined || value === null || value === '') {
286 - return false
287 - }
288 - }
289 - }
290 - }
291 -
292 - return true
266 + if (!formValue.value.agent_names.length) return false
267 +
268 + // Check required parameters
269 + if (action?.script_parameters?.length) {
270 + for (const param of action.script_parameters) {
271 + if (param.required) {
272 + const value = formValue.value.parameters[param.name]
273 + if (value === undefined || value === null || value === "") {
274 + return false
275 + }
276 + }
277 + }
278 + }
279 +
280 + return true
281 })
282
283 async function getAgents() {
296 - loadingAgents.value = true
297 - try {
298 - const res = await Api.agents.getAgents()
299 - if (res.data.success) {
300 - agents.value = res.data.agents || []
301 - } else {
302 - message.error(res.data?.message || "Failed to load agents")
303 - }
304 - } catch (err: any) {
305 - message.error(err.response?.data?.message || "Failed to load agents")
306 - } finally {
307 - loadingAgents.value = false
308 - }
284 + loadingAgents.value = true
285 + try {
286 + const res = await Api.agents.getAgents()
287 + if (res.data.success) {
288 + agents.value = res.data.agents || []
289 + } else {
290 + message.error(res.data?.message || "Failed to load agents")
291 + }
292 + } catch (err: any) {
293 + message.error(err.response?.data?.message || "Failed to load agents")
294 + } finally {
295 + loadingAgents.value = false
296 + }
297 }
298
299 async function handleInvoke() {
312 - if (!formRef.value) return
313 -
314 - try {
315 - await formRef.value.validate()
316 - } catch {
317 - return
318 - }
319 -
320 - loading.value = true
321 -
322 - // Filter out undefined/null parameters
323 - const cleanedParameters: Record<string, string | number> = {}
324 - Object.entries(formValue.value.parameters).forEach(([key, value]) => {
325 - if (value !== undefined && value !== null && value !== '') {
326 - cleanedParameters[key] = typeof value === 'boolean' ? (value ? 'true' : 'false') : value
327 - }
328 - })
329 -
330 - try {
331 - const res = await Api.copilotAction.invokeAction({
332 - copilot_action_name: action.copilot_action_name,
333 - agent_names: formValue.value.agent_names,
334 - parameters: cleanedParameters
335 - })
336 -
337 - if (res.data.success) {
338 - message.success(res.data?.message || "Action invoked successfully")
339 - emit('success')
340 - } else {
341 - message.warning(res.data?.message || "Failed to invoke action")
342 - }
343 - } catch (err: any) {
344 - message.error(err.response?.data?.message || "Failed to invoke action")
345 - } finally {
346 - loading.value = false
347 - }
300 + if (!formRef.value) return
301 +
302 + try {
303 + await formRef.value.validate()
304 + } catch {
305 + return
306 + }
307 +
308 + loading.value = true
309 +
310 + // Filter out undefined/null parameters
311 + const cleanedParameters: Record<string, string | number> = {}
312 + Object.entries(formValue.value.parameters).forEach(([key, value]) => {
313 + if (value !== undefined && value !== null && value !== "") {
314 + cleanedParameters[key] = typeof value === "boolean" ? (value ? "true" : "false") : value
315 + }
316 + })
317 +
318 + try {
319 + const res = await Api.copilotAction.invokeAction({
320 + copilot_action_name: action.copilot_action_name,
321 + agent_names: formValue.value.agent_names,
322 + parameters: cleanedParameters
323 + })
324 +
325 + if (res.data.success) {
326 + message.success(res.data?.message || "Action invoked successfully")
327 + emit("success")
328 + } else {
329 + message.warning(res.data?.message || "Failed to invoke action")
330 + }
331 + } catch (err: any) {
332 + message.error(err.response?.data?.message || "Failed to invoke action")
333 + } finally {
334 + loading.value = false
335 + }
336 }
337 </script>
frontend/src/components/copilotAction/TechnologyIcon.vue
+21 -21
@@ -7,32 +7,32 @@ import Icon from "@/components/common/Icon.vue"
7 import { iconFromOs } from "@/utils"
8
9 const { technology, size = 14 } = defineProps<{
10 - technology?: string
11 - size?: number
10 + technology?: string
11 + size?: number
12 }>()
13
14 -function getTechnologyIcon(tech: string | undefined): string {
15 - if (!tech) return "carbon:application"
14 +function getTechnologyIcon(tech?: string): string {
15 + if (!tech) return "carbon:application"
16
17 - const techLower = tech.toLowerCase()
17 + const techLower = tech.toLowerCase()
18
19 - if (techLower.includes("win") || techLower.includes("lin") || techLower.includes("mac")) {
20 - return iconFromOs(tech)
21 - }
19 + if (techLower.includes("win") || techLower.includes("lin") || techLower.includes("mac")) {
20 + return iconFromOs(tech)
21 + }
22
23 - if (techLower.includes("wazuh")) {
24 - return "carbon:security"
25 - }
26 - if (techLower.includes("velociraptor")) {
27 - return "fluent-emoji-high-contrast:eagle"
28 - }
29 - if (techLower.includes("network")) {
30 - return "carbon:network-3"
31 - }
32 - if (techLower.includes("cloud")) {
33 - return "carbon:cloud"
34 - }
23 + if (techLower.includes("wazuh")) {
24 + return "carbon:security"
25 + }
26 + if (techLower.includes("velociraptor")) {
27 + return "fluent-emoji-high-contrast:eagle"
28 + }
29 + if (techLower.includes("network")) {
30 + return "carbon:network-3"
31 + }
32 + if (techLower.includes("cloud")) {
33 + return "carbon:cloud"
34 + }
35
36 - return "carbon:application"
36 + return "carbon:application"
37 }
38 </script>
frontend/src/components/customerPortal/Form.vue
+4 -3
@@ -41,6 +41,7 @@
41 </template>
42
43 <script setup lang="ts">
44 +// TODO: refactor
45 import type { CustomerPortalSettingsPayload } from "@/api/endpoints/customerPortal"
46 import type { ImageCropperResult } from "@/components/common/ImageCropper.vue"
47 import type { CustomerPortalSettings } from "@/types/customerPortal"
@@ -95,12 +96,12 @@ function getLogoMeta(logo?: string | null): { base64: string | null; mime_type:
96 }
97
98 const base64 = parts[1]
98 - const mimeMatch = parts[0].match(/data:([^;]+);base64/)
99 + const mimeMatch = parts[0]?.match(/data:([^;]+);base64/)
100 const mime_type = mimeMatch ? mimeMatch[1] : null
101
102 return {
102 - base64,
103 - mime_type
103 + base64: base64 ?? null,
104 + mime_type: mime_type ?? null
105 }
106 }
107
frontend/src/components/customers/CustomerAgents.vue
+11 -2
@@ -1,7 +1,7 @@
1 <template>
2 <n-spin :show="loading">
3 <div class="flex min-h-28 flex-col gap-2">
4 - <div v-if="sortedList.length" class="flex justify-end mb-2">
4 + <div v-if="sortedList.length" class="mb-2 flex justify-end">
5 <n-button type="primary" @click="exportToCSV">
6 <template #icon>
7 <Icon :name="DownloadIcon" />
@@ -25,6 +25,7 @@
25 </template>
26
27 <script setup lang="ts">
28 +// TODO: refactor
29 import type { Agent } from "@/types/agents.d"
30 import type { Customer } from "@/types/customers.d"
31 import { NButton, NEmpty, NSpin, useMessage } from "naive-ui"
@@ -59,7 +60,15 @@ function exportToCSV() {
60 }
61
62 // CSV headers
62 - const headers = ["customer_code", "agent_id", "hostname", "ip_address", "os", "wazuh_last_seen", "velociraptor_last_seen"]
63 + const headers = [
64 + "customer_code",
65 + "agent_id",
66 + "hostname",
67 + "ip_address",
68 + "os",
69 + "wazuh_last_seen",
70 + "velociraptor_last_seen"
71 + ]
72
73 // Create CSV content
74 const csvContent = [
frontend/src/components/customers/CustomerForm.vue
+4 -3
@@ -3,11 +3,11 @@
3 <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 - <div v-for="(_, key) of form" :key="key" class="grow">
7 - <n-form-item :label="fieldsMeta[key].label" :path="key" class="grow">
6 + <div v-for="(val, key) of fieldsMeta" :key="key" class="grow">
7 + <n-form-item :label="val.label" :path="key" class="grow">
8 <n-input
9 v-model:value.trim="form[key]"
10 - :placeholder="fieldsMeta[key].placeholder"
10 + :placeholder="val.placeholder"
11 clearable
12 :readonly="key === 'customer_code' && lockCode"
13 :disabled="key === 'customer_code' && lockCode"
@@ -32,6 +32,7 @@
32 </template>
33
34 <script setup lang="ts">
35 +// TODO: refactor
36 import type { FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui"
37 import type { Customer } from "@/types/customers.d"
38 import _get from "lodash/get"
frontend/src/components/customers/CustomerItem.vue
+2 -1
@@ -266,6 +266,7 @@
266 </template>
267
268 <script setup lang="ts">
269 +// TODO: refactor
270 import type { Customer, CustomerMeta } from "@/types/customers.d"
271 import _toSafeInteger from "lodash/toSafeInteger"
272 import { NAvatar, NButton, NModal, NPopover, NScrollbar, NTabPane, NTabs, useMessage } from "naive-ui"
@@ -327,7 +328,7 @@ const fallbackAvatar = computed(() => {
328
329 if (customer.value.customer_name.includes(" ")) {
330 const chunks = customer.value.customer_name.split(" ")
330 - text = (chunks[0][0] + chunks[1][0]).toUpperCase()
331 + text = ((chunks[0]?.[0] ?? "") + (chunks[1]?.[0] ?? "")).toUpperCase()
332 }
333
334 const hash = hashMD5(customer.value.customer_code)
frontend/src/components/customers/CustomerMetaForm.vue
+22 -16
@@ -4,11 +4,11 @@
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 <div v-for="(_, key) of form" :key="key" class="grow">
7 - <n-form-item :label="fieldsMeta[key].label" :path="key" class="grow">
7 + <n-form-item :label="fieldsMeta[key]?.label ?? ''" :path="key" class="grow">
8 <n-input
9 v-model:value.trim="form[key]"
10 - :placeholder="fieldsMeta[key].placeholder"
11 - :readonly="!!fieldsMeta[key].readonly"
10 + :placeholder="fieldsMeta[key]?.placeholder ?? ''"
11 + :readonly="fieldsMeta[key]?.readonly ?? false"
12 clearable
13 />
14 </n-form-item>
@@ -31,6 +31,7 @@
31 </template>
32
33 <script setup lang="ts">
34 +// TODO: refactor
35 import type { FormInst, FormRules, FormValidationError } from "naive-ui"
36 import type { CustomerMeta } from "@/types/customers.d"
37 import _get from "lodash/get"
@@ -40,10 +41,7 @@ import { NButton, NForm, NFormItem, NInput, NSpin, useMessage } from "naive-ui"
41 import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
42 import Api from "@/api"
43
43 -interface CustomerMetaExt extends Omit<CustomerMeta, "id" | "customer_meta_iris_customer_id"> {
44 - id: string
45 - customer_meta_iris_customer_id: string
46 -}
44 +type CustomerMetaModel = { [K in keyof CustomerMeta]: string }
45
46 const props = defineProps<{
47 customerMeta?: CustomerMeta
@@ -68,7 +66,7 @@ const { customerMeta, customerCode, customerName, metaId, resetOnSubmit } = toRe
66
67 const loading = ref(false)
68 const message = useMessage()
71 -const form = ref<CustomerMetaExt>(getClearForm())
69 +const form = ref<CustomerMetaModel>(getClearForm())
70 const formRef = ref<FormInst | null>(null)
71
72 const rules: FormRules = {
@@ -179,6 +177,10 @@ const fieldsMeta: { [key: string]: { label: string; placeholder: string; readonl
177 customer_meta_office365_organization_id: {
178 label: "Office365 Organization ID",
179 placeholder: "Office365 Organization ID..."
180 + },
181 + customer_meta_wazuh_api_port: {
182 + label: "Wazuh API port",
183 + placeholder: "Wazuh API port..."
184 }
185 }
186
@@ -209,9 +211,9 @@ function validate() {
211 })
212 }
213
212 -function getClearForm(customerMeta?: Partial<CustomerMeta>): CustomerMetaExt {
214 +function getClearForm(customerMeta?: Partial<CustomerMeta>): CustomerMetaModel {
215 return {
214 - id: metaId.value?.toString() || "",
216 + id: `${metaId.value}` || "",
217 customer_code: customerCode.value || "",
218 customer_name: customerName.value || "",
219 customer_meta_graylog_index: customerMeta?.customer_meta_graylog_index || "",
@@ -222,12 +224,13 @@ function getClearForm(customerMeta?: Partial<CustomerMeta>): CustomerMetaExt {
224 customer_meta_wazuh_registration_port: customerMeta?.customer_meta_wazuh_registration_port || "",
225 customer_meta_wazuh_log_ingestion_port: customerMeta?.customer_meta_wazuh_log_ingestion_port || "",
226 customer_meta_wazuh_auth_password: customerMeta?.customer_meta_wazuh_auth_password || "",
225 - customer_meta_iris_customer_id: customerMeta?.customer_meta_iris_customer_id?.toString() || "",
226 - customer_meta_office365_organization_id: customerMeta?.customer_meta_office365_organization_id || ""
227 + customer_meta_iris_customer_id: `${customerMeta?.customer_meta_iris_customer_id}` || "",
228 + customer_meta_office365_organization_id: customerMeta?.customer_meta_office365_organization_id || "",
229 + customer_meta_wazuh_api_port: customerMeta?.customer_meta_wazuh_api_port || ""
230 }
231 }
232
230 -function getPayload(meta: CustomerMetaExt): CustomerMeta {
233 +function getPayload(meta: CustomerMetaModel): CustomerMeta {
234 return {
235 id: _toSafeInteger(meta.id),
236 customer_code: meta.customer_code,
@@ -241,7 +244,8 @@ function getPayload(meta: CustomerMetaExt): CustomerMeta {
244 customer_meta_wazuh_log_ingestion_port: meta.customer_meta_wazuh_log_ingestion_port,
245 customer_meta_wazuh_auth_password: meta.customer_meta_wazuh_auth_password,
246 customer_meta_iris_customer_id: _toSafeInteger(meta.customer_meta_iris_customer_id),
244 - customer_meta_office365_organization_id: meta.customer_meta_office365_organization_id
247 + customer_meta_office365_organization_id: meta.customer_meta_office365_organization_id,
248 + customer_meta_wazuh_api_port: meta.customer_meta_wazuh_api_port
249 }
250 }
251
@@ -259,9 +263,11 @@ function resetForm() {
263 function submit() {
264 loading.value = true
265
262 - const method = customerMeta.value?.customer_meta_graylog_index ? "updateCustomerMeta" : "createCustomerMeta"
266 + const method = customerMeta.value?.customer_meta_graylog_index
267 + ? Api.customers.updateCustomerMeta
268 + : Api.customers.createCustomerMeta
269
264 - Api.customers[method](getPayload(form.value), customerCode.value)
270 + method(getPayload(form.value), customerCode.value)
271 .then(res => {
272 if (res.data.success) {
273 emit("submitted", res.data.customer_meta)
frontend/src/components/customers/CustomerWazuhWorker.vue
+1 -1
@@ -150,7 +150,7 @@ import CardKV from "@/components/common/cards/CardKV.vue"
150 import Icon from "@/components/common/Icon.vue"
151 import { useSettingsStore } from "@/stores/settings"
152 import { PortainerStackStatus } from "@/types/portainer.d"
153 -import { formatDate } from "@/utils"
153 +import { formatDate } from "@/utils/format"
154 import "@/assets/scss/overrides/vuesjv-override.scss"
155
156 const { stackId } = defineProps<{ stackId: number }>()
frontend/src/components/customers/CustomersList.vue
+2 -6
@@ -8,12 +8,7 @@
8 </div>
9 <div class="flex items-center gap-2">
10 <span class="text-sm">Sort by:</span>
11 - <n-select
12 - v-model:value="sortOption"
13 - :options="sortOptions"
14 - style="width: 120px"
15 - size="small"
16 - />
11 + <n-select v-model:value="sortOption" :options="sortOptions" style="width: 120px" size="small" />
12 </div>
13 </div>
14 <div class="flex items-center gap-3">
@@ -42,6 +37,7 @@
37 </template>
38
39 <script setup lang="ts">
40 +// TODO: refactor
41 import type { Customer } from "@/types/customers.d"
42 import { NEmpty, NSelect, NSpin, useMessage } from "naive-ui"
43 import { computed, nextTick, onBeforeMount, ref, toRefs, watch } from "vue"
frontend/src/components/customers/healthcheck/CustomerHealthcheckItem.vue
+6 -3
@@ -54,7 +54,10 @@
54 <div class="flex flex-col gap-1">
55 <div class="box">
56 agent_id:
57 - <code class="text-primary cursor-pointer" @click="gotoAgent(healthData.agent_id)">
57 + <code
58 + class="text-primary cursor-pointer"
59 + @click="routeAgent(healthData.agent_id).navigate()"
60 + >
61 {{ healthData.agent_id }}
62 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
63 </code>
@@ -100,7 +103,7 @@ import Badge from "@/components/common/Badge.vue"
103 import CardEntity from "@/components/common/cards/CardEntity.vue"
104 import CardKV from "@/components/common/cards/CardKV.vue"
105 import Icon from "@/components/common/Icon.vue"
103 -import { useGoto } from "@/composables/useGoto"
106 +import { useNavigation } from "@/composables/useNavigation"
107 import { useSettingsStore } from "@/stores/settings"
108 import { iconFromOs } from "@/utils"
109 import dayjs from "@/utils/dayjs"
@@ -117,7 +120,7 @@ const AgentIcon = "carbon:police"
120 const LinkIcon = "carbon:launch"
121
122 const showDetails = ref(false)
120 -const { gotoAgent } = useGoto()
123 +const { routeAgent } = useNavigation()
124
125 const agentVersion = computed(() => {
126 let agent = ""
frontend/src/components/customers/healthcheck/CustomerHealthcheckList.vue
+4 -3
@@ -65,6 +65,7 @@
65 </template>
66
67 <script setup lang="ts">
68 +// TODO: refactor
69 import type { CustomerAgentsHealthcheckQuery } from "@/api/endpoints/customers"
70 import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/customers.d"
71 import { watchDebounced } from "@vueuse/core"
@@ -103,17 +104,17 @@ const unitOptions = [
104
105 const filters = computed({
106 get: () => props.filters,
106 - set: (value) => emit("update:filters", value)
107 + set: value => emit("update:filters", value)
108 })
109
110 const filterTime = computed({
111 get: () => props.filters.time,
111 - set: (value) => emit("update:filters", { ...props.filters, time: value })
112 + set: value => emit("update:filters", { ...props.filters, time: value })
113 })
114
115 const filterUnit = computed({
116 get: () => props.filters.unit,
116 - set: (value) => emit("update:filters", { ...props.filters, unit: value })
117 + set: value => emit("update:filters", { ...props.filters, unit: value })
118 })
119
120 function getList() {
frontend/src/components/customers/integrations/CustomerIntegrationForm.vue
+2 -1
@@ -80,6 +80,7 @@
80 </template>
81
82 <script setup lang="ts">
83 +// TODO: refactor
84 import type { StepsProps } from "naive-ui"
85 import type { NewIntegration } from "@/api/endpoints/integrations"
86 import type { ServiceItemData } from "@/components/services/types"
@@ -131,7 +132,7 @@ watch(selectedIntegration, val => {
132 for (const ak of val.keys) {
133 authKeysForm.value.push({
134 key: ak.auth_key_name,
134 - value: ak.auth_key_name === "API_TYPE" ? apiTypeOptions[0].value : "",
135 + value: ak.auth_key_name === "API_TYPE" ? (apiTypeOptions[0]?.value ?? "") : "",
136 type: ak.auth_key_name === "API_TYPE" ? "selectType" : "string"
137 })
138 }
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorActions.vue
+178 -178
@@ -172,7 +172,7 @@
172 import type { Size } from "naive-ui/es/button/src/interface"
173 import type { FortinetModel } from "./provisions/FortinetForm.vue"
174 import type { SentinelOneModel } from "./provisions/SentinelOneForm.vue"
175 -import type { SonicwallModel } from "./provisions/SonicwallForm.vue"
175 +import type { SonicWallModel } from "./provisions/SonicwallForm.vue"
176 import type { FortinetProvision, SentinelOneProvision, SonicwallProvision } from "@/api/endpoints/networkConnectors"
177 import type { CustomerNetworkConnector } from "@/types/networkConnectors.d"
178 import { NButton, NModal, NSpin, useDialog, useMessage } from "naive-ui"
@@ -184,17 +184,17 @@ import SentinelOneForm from "./provisions/SentinelOneForm.vue"
184 import SonicwallForm from "./provisions/SonicwallForm.vue"
185
186 const { networkConnector, hideDeleteButton, size } = defineProps<{
187 - networkConnector: CustomerNetworkConnector
188 - hideDeleteButton?: boolean
189 - size?: Size
187 + networkConnector: CustomerNetworkConnector
188 + hideDeleteButton?: boolean
189 + size?: Size
190 }>()
191
192 const emit = defineEmits<{
193 - (e: "startLoading"): void
194 - (e: "stopLoading"): void
195 - (e: "deployed"): void
196 - (e: "decommissioned"): void
197 - (e: "deleted"): void
193 + (e: "startLoading"): void
194 + (e: "stopLoading"): void
195 + (e: "deployed"): void
196 + (e: "decommissioned"): void
197 + (e: "deleted"): void
198 }>()
199
200 const DeployIcon = "carbon:deploy"
@@ -209,12 +209,12 @@ const loadingSentinelOneProvision = ref(false)
209 const loadingDelete = ref(false)
210 const loadingDecommission = ref(false)
211 const loading = computed(
212 - () =>
213 - loadingFortinetProvision.value ||
214 - loadingSonicwallProvision.value ||
215 - loadingSentinelOneProvision.value ||
216 - loadingDecommission.value ||
217 - loadingDelete.value
212 + () =>
213 + loadingFortinetProvision.value ||
214 + loadingSonicwallProvision.value ||
215 + loadingSentinelOneProvision.value ||
216 + loadingDecommission.value ||
217 + loadingDelete.value
218 )
219
220 const serviceName = computed(() => networkConnector.network_connector_service_name)
@@ -228,203 +228,203 @@ const showSonicwallForm = ref(false)
228 const showSentinelOneForm = ref(false)
229
230 const fortinetOptions = ref<FortinetModel>({
231 - protocol: "tcp",
232 - hot_data_retention: 1,
233 - index_replicas: 0
231 + protocol: "tcp",
232 + hot_data_retention: 1,
233 + index_replicas: 0
234 })
235
236 -const sonicwallOptions = ref<SonicwallModel>({
237 - protocol: "tcp",
238 - hot_data_retention: 1,
239 - index_replicas: 0
236 +const sonicwallOptions = ref<SonicWallModel>({
237 + protocol: "tcp",
238 + hot_data_retention: 1,
239 + index_replicas: 0
240 })
241
242 const sentinelOneOptions = ref<SentinelOneModel>({
243 - protocol: "tls",
244 - hot_data_retention: 1,
245 - index_replicas: 0
243 + protocol: "tls",
244 + hot_data_retention: 1,
245 + index_replicas: 0
246 })
247
248 const isFortinetFormValid = computed(() => {
249 - if (fortinetOptions.value.hot_data_retention === null) {
250 - return false
251 - }
252 - if (fortinetOptions.value.index_replicas === null) {
253 - return false
254 - }
255 - return true
249 + if (fortinetOptions.value.hot_data_retention === null) {
250 + return false
251 + }
252 + if (fortinetOptions.value.index_replicas === null) {
253 + return false
254 + }
255 + return true
256 })
257
258 const isSonicwallFormValid = computed(() => {
259 - if (sonicwallOptions.value.hot_data_retention === null) {
260 - return false
261 - }
262 - if (sonicwallOptions.value.index_replicas === null) {
263 - return false
264 - }
265 - return true
259 + if (sonicwallOptions.value.hot_data_retention === null) {
260 + return false
261 + }
262 + if (sonicwallOptions.value.index_replicas === null) {
263 + return false
264 + }
265 + return true
266 })
267
268 const isSentinelOneFormValid = computed(() => {
269 - if (sentinelOneOptions.value.hot_data_retention === null) {
270 - return false
271 - }
272 - if (sentinelOneOptions.value.index_replicas === null) {
273 - return false
274 - }
275 - return true
269 + if (sentinelOneOptions.value.hot_data_retention === null) {
270 + return false
271 + }
272 + if (sentinelOneOptions.value.index_replicas === null) {
273 + return false
274 + }
275 + return true
276 })
277
278 watch(loading, val => {
279 - if (val) {
280 - emit("startLoading")
281 - } else {
282 - emit("stopLoading")
283 - }
279 + if (val) {
280 + emit("startLoading")
281 + } else {
282 + emit("stopLoading")
283 + }
284 })
285
286 function fortinetProvision() {
287 - loadingFortinetProvision.value = true
288 -
289 - const options: FortinetProvision = {
290 - tcp_enabled: fortinetOptions.value.protocol === "tcp",
291 - udp_enabled: fortinetOptions.value.protocol === "udp",
292 - hot_data_retention: fortinetOptions.value.hot_data_retention,
293 - index_replicas: fortinetOptions.value.index_replicas
294 - }
295 -
296 - Api.networkConnectors
297 - .fortinetProvision(customerCode.value, serviceName.value, options)
298 - .then(res => {
299 - if (res.data.success) {
300 - emit("deployed")
301 - showFortinetForm.value = false
302 - message.success(res.data?.message || "Fortinet customer provisioned successfully.")
303 - } else {
304 - message.warning(res.data?.message || "An error occurred. Please try again later.")
305 - }
306 - })
307 - .catch(err => {
308 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
309 - })
310 - .finally(() => {
311 - loadingFortinetProvision.value = false
312 - })
287 + loadingFortinetProvision.value = true
288 +
289 + const options: FortinetProvision = {
290 + tcp_enabled: fortinetOptions.value.protocol === "tcp",
291 + udp_enabled: fortinetOptions.value.protocol === "udp",
292 + hot_data_retention: fortinetOptions.value.hot_data_retention,
293 + index_replicas: fortinetOptions.value.index_replicas
294 + }
295 +
296 + Api.networkConnectors
297 + .fortinetProvision(customerCode.value, serviceName.value, options)
298 + .then(res => {
299 + if (res.data.success) {
300 + emit("deployed")
301 + showFortinetForm.value = false
302 + message.success(res.data?.message || "Fortinet customer provisioned successfully.")
303 + } else {
304 + message.warning(res.data?.message || "An error occurred. Please try again later.")
305 + }
306 + })
307 + .catch(err => {
308 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
309 + })
310 + .finally(() => {
311 + loadingFortinetProvision.value = false
312 + })
313 }
314
315 function sonicwallProvision() {
316 - loadingSonicwallProvision.value = true
317 -
318 - const options: SonicwallProvision = {
319 - tcp_enabled: sonicwallOptions.value.protocol === "tcp",
320 - hot_data_retention: sonicwallOptions.value.hot_data_retention,
321 - index_replicas: sonicwallOptions.value.index_replicas
322 - }
323 -
324 - Api.networkConnectors
325 - .sonicwallProvision(customerCode.value, serviceName.value, options)
326 - .then(res => {
327 - if (res.data.success) {
328 - emit("deployed")
329 - showSonicwallForm.value = false
330 - message.success(res.data?.message || "SonicWall customer provisioned successfully.")
331 - } else {
332 - message.warning(res.data?.message || "An error occurred. Please try again later.")
333 - }
334 - })
335 - .catch(err => {
336 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
337 - })
338 - .finally(() => {
339 - loadingSonicwallProvision.value = false
340 - })
316 + loadingSonicwallProvision.value = true
317 +
318 + const options: SonicwallProvision = {
319 + tcp_enabled: sonicwallOptions.value.protocol === "tcp",
320 + hot_data_retention: sonicwallOptions.value.hot_data_retention,
321 + index_replicas: sonicwallOptions.value.index_replicas
322 + }
323 +
324 + Api.networkConnectors
325 + .sonicwallProvision(customerCode.value, serviceName.value, options)
326 + .then(res => {
327 + if (res.data.success) {
328 + emit("deployed")
329 + showSonicwallForm.value = false
330 + message.success(res.data?.message || "SonicWall customer provisioned successfully.")
331 + } else {
332 + message.warning(res.data?.message || "An error occurred. Please try again later.")
333 + }
334 + })
335 + .catch(err => {
336 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
337 + })
338 + .finally(() => {
339 + loadingSonicwallProvision.value = false
340 + })
341 }
342
343 function sentinelOneProvision() {
344 - loadingSentinelOneProvision.value = true
345 -
346 - const options: SentinelOneProvision = {
347 - tls_enabled: sentinelOneOptions.value.protocol === "tls",
348 - hot_data_retention: sentinelOneOptions.value.hot_data_retention,
349 - index_replicas: sentinelOneOptions.value.index_replicas
350 - }
351 -
352 - Api.networkConnectors
353 - .sentineloneProvision(customerCode.value, serviceName.value, options)
354 - .then(res => {
355 - if (res.data.success) {
356 - emit("deployed")
357 - showSentinelOneForm.value = false
358 - message.success(res.data?.message || "SentinelOne customer provisioned successfully.")
359 - } else {
360 - message.warning(res.data?.message || "An error occurred. Please try again later.")
361 - }
362 - })
363 - .catch(err => {
364 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
365 - })
366 - .finally(() => {
367 - loadingSentinelOneProvision.value = false
368 - })
344 + loadingSentinelOneProvision.value = true
345 +
346 + const options: SentinelOneProvision = {
347 + tls_enabled: sentinelOneOptions.value.protocol === "tls",
348 + hot_data_retention: sentinelOneOptions.value.hot_data_retention,
349 + index_replicas: sentinelOneOptions.value.index_replicas
350 + }
351 +
352 + Api.networkConnectors
353 + .sentineloneProvision(customerCode.value, serviceName.value, options)
354 + .then(res => {
355 + if (res.data.success) {
356 + emit("deployed")
357 + showSentinelOneForm.value = false
358 + message.success(res.data?.message || "SentinelOne customer provisioned successfully.")
359 + } else {
360 + message.warning(res.data?.message || "An error occurred. Please try again later.")
361 + }
362 + })
363 + .catch(err => {
364 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
365 + })
366 + .finally(() => {
367 + loadingSentinelOneProvision.value = false
368 + })
369 }
370
371 function handleDelete() {
372 - dialog.warning({
373 - title: "Confirm",
374 - content: () =>
375 - h("div", {
376 - innerHTML: `Are you sure you want to delete the Network Connector: <strong>${serviceName.value}</strong> ?`
377 - }),
378 - positiveText: "Yes I'm sure",
379 - negativeText: "Cancel",
380 - onPositiveClick: () => {
381 - deleteNetworkConnector()
382 - },
383 - onNegativeClick: () => {
384 - message.info("Delete canceled")
385 - }
386 - })
372 + dialog.warning({
373 + title: "Confirm",
374 + content: () =>
375 + h("div", {
376 + innerHTML: `Are you sure you want to delete the Network Connector: <strong>${serviceName.value}</strong> ?`
377 + }),
378 + positiveText: "Yes I'm sure",
379 + negativeText: "Cancel",
380 + onPositiveClick: () => {
381 + deleteNetworkConnector()
382 + },
383 + onNegativeClick: () => {
384 + message.info("Delete canceled")
385 + }
386 + })
387 }
388
389 function deleteNetworkConnector() {
390 - loadingDelete.value = true
391 -
392 - Api.networkConnectors
393 - .deleteNetworkConnector(customerCode.value, serviceName.value)
394 - .then(res => {
395 - if (res.data.success) {
396 - emit("deleted")
397 - message.success(res.data?.message || "Customer Network Connector successfully deleted.")
398 - } else {
399 - message.warning(res.data?.message || "An error occurred. Please try again later.")
400 - }
401 - })
402 - .catch(err => {
403 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
404 - })
405 - .finally(() => {
406 - loadingDelete.value = false
407 - })
390 + loadingDelete.value = true
391 +
392 + Api.networkConnectors
393 + .deleteNetworkConnector(customerCode.value, serviceName.value)
394 + .then(res => {
395 + if (res.data.success) {
396 + emit("deleted")
397 + message.success(res.data?.message || "Customer Network Connector successfully deleted.")
398 + } else {
399 + message.warning(res.data?.message || "An error occurred. Please try again later.")
400 + }
401 + })
402 + .catch(err => {
403 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
404 + })
405 + .finally(() => {
406 + loadingDelete.value = false
407 + })
408 }
409
410 function decommissionNetworkConnector() {
411 - loadingDecommission.value = true
412 -
413 - Api.networkConnectors
414 - .decommissionNetworkConnector(customerCode.value, serviceName.value)
415 - .then(res => {
416 - if (res.data.success) {
417 - emit("decommissioned")
418 - message.success(res.data?.message || "Customer Network Connector successfully decommissioned.")
419 - } else {
420 - message.warning(res.data?.message || "An error occurred. Please try again later.")
421 - }
422 - })
423 - .catch(err => {
424 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
425 - })
426 - .finally(() => {
427 - loadingDecommission.value = false
428 - })
411 + loadingDecommission.value = true
412 +
413 + Api.networkConnectors
414 + .decommissionNetworkConnector(customerCode.value, serviceName.value)
415 + .then(res => {
416 + if (res.data.success) {
417 + emit("decommissioned")
418 + message.success(res.data?.message || "Customer Network Connector successfully decommissioned.")
419 + } else {
420 + message.warning(res.data?.message || "An error occurred. Please try again later.")
421 + }
422 + })
423 + .catch(err => {
424 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
425 + })
426 + .finally(() => {
427 + loadingDecommission.value = false
428 + })
429 }
430 </script>
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorForm.vue
+2 -1
@@ -80,6 +80,7 @@
80 </template>
81
82 <script setup lang="ts">
83 +// TODO: refactor
84 import type { StepsProps } from "naive-ui"
85 import type { NewNetworkConnector } from "@/api/endpoints/networkConnectors"
86 import type { ServiceItemData } from "@/components/services/types"
@@ -131,7 +132,7 @@ watch(selectedNetworkConnector, val => {
132 for (const ak of val.keys) {
133 authKeysForm.value.push({
134 key: ak.auth_key_name,
134 - value: ak.auth_key_name === "API_TYPE" ? apiTypeOptions[0].value : "",
135 + value: ak.auth_key_name === "API_TYPE" ? (apiTypeOptions[0]?.value ?? "") : "",
136 type: ak.auth_key_name === "API_TYPE" ? "selectType" : "string"
137 })
138 }
frontend/src/components/customers/networkConnectors/provisions/SentinelOneForm.vue
+3 -3
@@ -20,9 +20,9 @@
20 import { NForm, NFormItem, NInputNumber, NRadioButton, NRadioGroup } from "naive-ui"
21
22 export interface SentinelOneModel {
23 - protocol: "tcp"
24 - hot_data_retention: number
25 - index_replicas: number
23 + protocol: string
24 + hot_data_retention: number
25 + index_replicas: number
26 }
27
28 const model = defineModel<SentinelOneModel>("options", { required: true })
frontend/src/components/customers/networkConnectors/provisions/SonicwallForm.vue
+5 -5
@@ -19,11 +19,11 @@
19 <script setup lang="ts">
20 import { NForm, NFormItem, NInputNumber, NRadioButton, NRadioGroup } from "naive-ui"
21
22 -export interface SonicwallModel {
23 - protocol: "tcp"
24 - hot_data_retention: number
25 - index_replicas: number
22 +export interface SonicWallModel {
23 + protocol: "tcp"
24 + hot_data_retention: number
25 + index_replicas: number
26 }
27
28 -const model = defineModel<SonicwallModel>("options", { required: true })
28 +const model = defineModel<SonicWallModel>("options", { required: true })
29 </script>
frontend/src/components/customers/provision/CustomerProvision.vue
+2 -1
@@ -46,6 +46,7 @@
46 </template>
47
48 <script setup lang="ts">
49 +// TODO: refactor
50 import type { CustomerMeta } from "@/types/customers.d"
51 import { NButton, useDialog, useMessage } from "naive-ui"
52 import { computed, h, ref, toRefs } from "vue"
@@ -82,7 +83,7 @@ function formatValue(key: string, value: any): string {
83
84 // Add field-specific formatting rules here
85 const formatRules: Record<string, (val: any) => string> = {
85 - customer_meta_index_retention: (val) => `${val} days`
86 + customer_meta_index_retention: val => `${val} days`
87 // Add more formatting rules as needed:
88 // another_field: (val) => `${val} units`,
89 }
frontend/src/components/customers/provision/CustomerProvisionWizard.vue
+13 -5
@@ -153,8 +153,10 @@
153 <n-form-item label="Deploy Wazuh Worker" path="provision_wazuh_worker">
154 <n-switch v-model:value="form.provision_wazuh_worker" clearable />
155 </n-form-item>
156 - <div class="mt-2 text-sm text-gray-500">
157 - Multi-tenancy (SOCFortress Pro Services only): Deploys a dedicated worker used in SOCFortress-managed multi-tenant Wazuh environments. Leave off for single-tenant deployments.
156 + <div class="text-secondary mt-2 text-sm">
157 + Multi-tenancy (SOCFortress Pro Services only): Deploys a dedicated worker used in
158 + SOCFortress-managed multi-tenant Wazuh environments. Leave off for single-tenant
159 + deployments.
160 </div>
161 </n-card>
162 <n-card class="grow">
@@ -165,8 +167,10 @@
167 :disabled="!form.provision_wazuh_worker"
168 />
169 </n-form-item>
168 - <div class="mt-2 text-sm text-gray-500">
169 - Multi-tenancy (SOCFortress Pro Services only): Deploys the load balancer used to route agent connections across multi-tenant Wazuh workers for high availability. Leave off for single-tenant deployments.
170 + <div class="text-secondary mt-2 text-sm">
171 + Multi-tenancy (SOCFortress Pro Services only): Deploys the load balancer used to
172 + route agent connections across multi-tenant Wazuh workers for high availability.
173 + Leave off for single-tenant deployments.
174 </div>
175 </n-card>
176 </div>
@@ -274,6 +278,7 @@
278 </template>
279
280 <script setup lang="ts">
281 +// TODO: refactor
282 import type { FormInst, FormItemRule, FormRules, FormValidationError, StepsProps } from "naive-ui"
283 import type { CustomerMeta, CustomerProvision, CustomerProvisioningDefaultSettings } from "@/types/customers.d"
284 import _uniqBy from "lodash/uniqBy"
@@ -671,7 +676,10 @@ function generateRandomPassword(length: number = 20): string {
676 }
677
678 // Shuffle the password to randomize the guaranteed characters
674 - return password.split('').sort(() => Math.random() - 0.5).join('')
679 + return password
680 + .split("")
681 + .sort(() => Math.random() - 0.5)
682 + .join("")
683 }
684
685 function formPreset(step: number) {
frontend/src/components/githubAudit/GitHubAuditButton.vue
+4 -3
@@ -8,19 +8,20 @@
8 </template>
9
10 <script setup lang="ts">
11 +// TODO: refactor
12 import { NButton } from "naive-ui"
13 import { useRouter } from "vue-router"
14 import Icon from "@/components/common/Icon.vue"
15
16 defineProps<{
16 - size?: "tiny" | "small" | "medium" | "large"
17 - type?: "default" | "primary" | "info" | "success" | "warning" | "error"
17 + size?: "tiny" | "small" | "medium" | "large"
18 + type?: "default" | "primary" | "info" | "success" | "warning" | "error"
19 }>()
20
21 const GitHubIcon = "carbon:logo-github"
22 const router = useRouter()
23
24 function goToGitHubAudit() {
24 - router.push({ name: "GitHubAudit" })
25 + router.push({ name: "GitHubAudit" })
26 }
27 </script>
frontend/src/components/githubAudit/GitHubAuditCard.vue
+36 -30
@@ -1,27 +1,30 @@
1 <template>
2 <n-card class="github-audit-card" hoverable @click="$emit('click', config)">
3 - <div class="flex justify-between items-start">
3 + <div class="flex items-start justify-between">
4 <div class="flex-1">
5 - <div class="flex items-center gap-2 mb-2">
5 + <div class="mb-2 flex items-center gap-2">
6 <n-icon size="20" :color="config.enabled ? '#18a058' : '#999'">
7 <Icon :name="GithubIcon" />
8 </n-icon>
9 - <h3 class="text-lg font-semibold m-0">{{ config.organization }}</h3>
9 + <h3 class="m-0 text-lg font-semibold">{{ config.organization }}</h3>
10 <n-tag v-if="!config.enabled" type="warning" size="small">Disabled</n-tag>
11 </div>
12
13 - <div class="text-secondary text-sm mb-3">
13 + <div class="text-secondary mb-3 text-sm">
14 <span>Customer: {{ config.customer_code }}</span>
15 </div>
16
17 <div class="flex gap-4 text-sm">
18 <div v-if="config.last_audit_at" class="flex items-center gap-1">
19 <n-icon><Icon :name="ClockIcon" /></n-icon>
20 - <span>Last audit: {{ formatDate(config.last_audit_at) }}</span>
20 + <span>Last audit: {{ formatDate(config.last_audit_at, dFormats.datetime) }}</span>
21 </div>
22 <div v-if="config.last_audit_grade" class="flex items-center gap-1">
23 <span>Grade:</span>
24 - <GitHubAuditGradeBadge :grade="config.last_audit_grade" :score="config.last_audit_score ?? undefined" />
24 + <GitHubAuditGradeBadge
25 + :grade="config.last_audit_grade"
26 + :score="config.last_audit_score ?? undefined"
27 + />
28 </div>
29 <div v-if="config.auto_audit_enabled" class="flex items-center gap-1">
30 <n-icon color="#18a058"><Icon :name="ScheduleIcon" /></n-icon>
@@ -49,7 +52,7 @@
52 <n-divider v-if="config.last_audit_score !== null" style="margin: 12px 0" />
53
54 <div v-if="config.last_audit_score !== null" class="audit-score-bar">
52 - <div class="flex justify-between mb-1">
55 + <div class="mb-1 flex justify-between">
56 <span class="text-sm">Security Score</span>
57 <span class="text-sm font-semibold">{{ config.last_audit_score?.toFixed(1) }}%</span>
58 </div>
@@ -64,21 +67,23 @@
67 </template>
68
69 <script setup lang="ts">
70 +// TODO: refactor
71 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
72 import { NButton, NCard, NDivider, NIcon, NProgress, NTag, useMessage } from "naive-ui"
73 import { computed, ref } from "vue"
74 import Api from "@/api"
75 import Icon from "@/components/common/Icon.vue"
72 -import { formatDate } from "@/utils"
76 +import { useSettingsStore } from "@/stores/settings"
77 +import { formatDate } from "@/utils/format"
78 import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
79
80 const props = defineProps<{
76 - config: GitHubAuditConfig
81 + config: GitHubAuditConfig
82 }>()
83 const emit = defineEmits<{
79 - (e: "click", config: GitHubAuditConfig): void
80 - (e: "edit", config: GitHubAuditConfig): void
81 - (e: "audit-complete"): void
84 + (e: "click", config: GitHubAuditConfig): void
85 + (e: "edit", config: GitHubAuditConfig): void
86 + (e: "audit-complete"): void
87 }>()
88 const GithubIcon = "carbon:logo-github"
89 const ClockIcon = "carbon:time"
@@ -88,39 +93,40 @@ const EditIcon = "carbon:edit"
93
94 const message = useMessage()
95 const running = ref(false)
96 +const dFormats = useSettingsStore().dateFormat
97
98 const scoreStatus = computed(() => {
93 - const score = props.config.last_audit_score ?? 0
94 - if (score >= 80) return "success"
95 - if (score >= 60) return "warning"
96 - return "error"
99 + const score = props.config.last_audit_score ?? 0
100 + if (score >= 80) return "success"
101 + if (score >= 60) return "warning"
102 + return "error"
103 })
104
105 async function runAudit() {
100 - running.value = true
101 - try {
102 - await Api.githubAudit.runAuditFromConfig(props.config.id)
103 - message.success("Audit completed successfully")
104 - emit("audit-complete")
105 - } catch (error: any) {
106 - message.error(error.response?.data?.detail || "Failed to run audit")
107 - } finally {
108 - running.value = false
109 - }
106 + running.value = true
107 + try {
108 + await Api.githubAudit.runAuditFromConfig(props.config.id)
109 + message.success("Audit completed successfully")
110 + emit("audit-complete")
111 + } catch (error: any) {
112 + message.error(error.response?.data?.detail || "Failed to run audit")
113 + } finally {
114 + running.value = false
115 + }
116 }
117 </script>
118
119 <style scoped>
120 .github-audit-card {
115 - cursor: pointer;
116 - transition: all 0.2s ease;
121 + cursor: pointer;
122 + transition: all 0.2s ease;
123 }
124
125 .github-audit-card:hover {
120 - transform: translateY(-2px);
126 + transform: translateY(-2px);
127 }
128
129 .text-secondary {
124 - color: var(--text-color-3);
130 + color: var(--text-color-3);
131 }
132 </style>
frontend/src/components/githubAudit/GitHubAuditConfigForm.vue
+127 -122
@@ -73,9 +73,7 @@
73
74 <n-form-item v-if="formData.repo_filter_mode !== 'all'" label="Repository List">
75 <n-dynamic-tags v-model:value="formData.repo_filter_list" />
76 - <template #feedback>
77 - Enter repository names to {{ formData.repo_filter_mode }}
78 - </template>
76 + <template #feedback>Enter repository names to {{ formData.repo_filter_mode }}</template>
77 </n-form-item>
78
79 <n-divider title-placement="left">Schedule</n-divider>
@@ -87,7 +85,9 @@
85 <n-form-item v-if="formData.auto_audit_enabled" label="Schedule (Cron)" path="audit_schedule_cron">
86 <n-input v-model:value="formData.audit_schedule_cron" placeholder="0 0 * * 1 (Weekly on Monday)" />
87 <template #feedback>
90 - <n-text depth="3">Use cron format. Example: "0 0 * * 1" for weekly on Monday at midnight</n-text>
88 + <n-text depth="3">
89 + Use cron format. Example: "0 0 * * 1" for weekly on Monday at midnight
90 + </n-text>
91 </template>
92 </n-form-item>
93
@@ -118,7 +118,12 @@
118
119 <n-form-item label="Minimum Passing Score">
120 <n-slider v-model:value="formData.minimum_passing_score" :min="0" :max="100" :step="5" />
121 - <n-input-number v-model:value="formData.minimum_passing_score" :min="0" :max="100" style="width: 100px; margin-left: 16px" />
121 + <n-input-number
122 + v-model:value="formData.minimum_passing_score"
123 + :min="0"
124 + :max="100"
125 + style="width: 100px; margin-left: 16px"
126 + />
127 </n-form-item>
128 </n-form>
129
@@ -135,40 +140,40 @@
140 </template>
141
142 <script setup lang="ts">
143 +// TODO: refactor
144 import type { FormInst, FormRules } from "naive-ui"
145 import type { GitHubAuditConfig, GitHubAuditConfigCreate, GitHubAuditConfigUpdate } from "@/types/githubAudit.d"
146 import {
141 -
142 - NButton,
143 - NDivider,
144 - NDrawer,
145 - NDrawerContent,
146 - NDynamicTags,
147 - NForm,
148 - NFormItem,
149 - NGi,
150 - NGrid,
151 - NInput,
152 - NInputNumber,
153 - NRadio,
154 - NRadioGroup,
155 - NSelect,
156 - NSlider,
157 - NSwitch,
158 - NText,
159 - useMessage
147 + NButton,
148 + NDivider,
149 + NDrawer,
150 + NDrawerContent,
151 + NDynamicTags,
152 + NForm,
153 + NFormItem,
154 + NGi,
155 + NGrid,
156 + NInput,
157 + NInputNumber,
158 + NRadio,
159 + NRadioGroup,
160 + NSelect,
161 + NSlider,
162 + NSwitch,
163 + NText,
164 + useMessage
165 } from "naive-ui"
166 import { computed, onMounted, reactive, ref, watch } from "vue"
167 import Api from "@/api"
168
169 const props = defineProps<{
165 - show: boolean
166 - config?: GitHubAuditConfig | null
170 + show: boolean
171 + config?: GitHubAuditConfig | null
172 }>()
173
174 const emit = defineEmits<{
170 - (e: "update:show", value: boolean): void
171 - (e: "saved"): void
175 + (e: "update:show", value: boolean): void
176 + (e: "saved"): void
177 }>()
178
179 const message = useMessage()
@@ -177,118 +182,118 @@ const saving = ref(false)
182 const customerOptions = ref<{ label: string; value: string }[]>([])
183
184 const showDrawer = computed({
180 - get: () => props.show,
181 - set: (value) => emit("update:show", value)
185 + get: () => props.show,
186 + set: value => emit("update:show", value)
187 })
188
189 const isEdit = computed(() => !!props.config)
190
191 function defaultFormData(): GitHubAuditConfigCreate {
187 - return {
188 - customer_code: "",
189 - github_token: "",
190 - organization: "",
191 - token_type: "pat",
192 - enabled: true,
193 - auto_audit_enabled: false,
194 - audit_schedule_cron: null,
195 - include_repos: true,
196 - include_workflows: true,
197 - include_members: true,
198 - include_archived_repos: false,
199 - repo_filter_mode: "all",
200 - repo_filter_list: [],
201 - notify_on_critical: true,
202 - notify_on_high: false,
203 - notification_webhook_url: null,
204 - notification_email: null,
205 - minimum_passing_score: 70
206 -}
192 + return {
193 + customer_code: "",
194 + github_token: "",
195 + organization: "",
196 + token_type: "pat",
197 + enabled: true,
198 + auto_audit_enabled: false,
199 + audit_schedule_cron: null,
200 + include_repos: true,
201 + include_workflows: true,
202 + include_members: true,
203 + include_archived_repos: false,
204 + repo_filter_mode: "all",
205 + repo_filter_list: [],
206 + notify_on_critical: true,
207 + notify_on_high: false,
208 + notification_webhook_url: null,
209 + notification_email: null,
210 + minimum_passing_score: 70
211 + }
212 }
213
214 const formData = reactive<GitHubAuditConfigCreate>(defaultFormData())
215
216 const rules: FormRules = {
212 - customer_code: { required: true, message: "Customer is required", trigger: "blur" },
213 - organization: { required: true, message: "Organization is required", trigger: "blur" },
214 - github_token: {
215 - required: !isEdit.value,
216 - message: "GitHub token is required",
217 - trigger: "blur"
218 - }
217 + customer_code: { required: true, message: "Customer is required", trigger: "blur" },
218 + organization: { required: true, message: "Organization is required", trigger: "blur" },
219 + github_token: {
220 + required: !isEdit.value,
221 + message: "GitHub token is required",
222 + trigger: "blur"
223 + }
224 }
225
226 watch(
222 - () => props.config,
223 - (config) => {
224 - if (config) {
225 - Object.assign(formData, {
226 - customer_code: config.customer_code,
227 - github_token: "",
228 - organization: config.organization,
229 - token_type: config.token_type,
230 - enabled: config.enabled,
231 - auto_audit_enabled: config.auto_audit_enabled,
232 - audit_schedule_cron: config.audit_schedule_cron,
233 - include_repos: config.include_repos,
234 - include_workflows: config.include_workflows,
235 - include_members: config.include_members,
236 - include_archived_repos: config.include_archived_repos,
237 - repo_filter_mode: config.repo_filter_mode,
238 - repo_filter_list: config.repo_filter_list || [],
239 - notify_on_critical: config.notify_on_critical,
240 - notify_on_high: config.notify_on_high,
241 - notification_webhook_url: config.notification_webhook_url,
242 - notification_email: config.notification_email,
243 - minimum_passing_score: config.minimum_passing_score
244 - })
245 - } else {
246 - Object.assign(formData, defaultFormData())
247 - }
248 - },
249 - { immediate: true }
227 + () => props.config,
228 + config => {
229 + if (config) {
230 + Object.assign(formData, {
231 + customer_code: config.customer_code,
232 + github_token: "",
233 + organization: config.organization,
234 + token_type: config.token_type,
235 + enabled: config.enabled,
236 + auto_audit_enabled: config.auto_audit_enabled,
237 + audit_schedule_cron: config.audit_schedule_cron,
238 + include_repos: config.include_repos,
239 + include_workflows: config.include_workflows,
240 + include_members: config.include_members,
241 + include_archived_repos: config.include_archived_repos,
242 + repo_filter_mode: config.repo_filter_mode,
243 + repo_filter_list: config.repo_filter_list || [],
244 + notify_on_critical: config.notify_on_critical,
245 + notify_on_high: config.notify_on_high,
246 + notification_webhook_url: config.notification_webhook_url,
247 + notification_email: config.notification_email,
248 + minimum_passing_score: config.minimum_passing_score
249 + })
250 + } else {
251 + Object.assign(formData, defaultFormData())
252 + }
253 + },
254 + { immediate: true }
255 )
256
257 async function handleSubmit() {
253 - try {
254 - await formRef.value?.validate()
255 - } catch {
256 - return
257 - }
258 -
259 - saving.value = true
260 - try {
261 - if (isEdit.value && props.config) {
262 - const updateData: GitHubAuditConfigUpdate = { ...formData }
263 - if (!updateData.github_token) {
264 - delete updateData.github_token
265 - }
266 - await Api.githubAudit.updateConfig(props.config.id, updateData)
267 - message.success("Configuration updated successfully")
268 - } else {
269 - await Api.githubAudit.createConfig(formData)
270 - message.success("Configuration created successfully")
271 - }
272 - emit("saved")
273 - showDrawer.value = false
274 - } catch (error: any) {
275 - message.error(error.response?.data?.detail || "Failed to save configuration")
276 - } finally {
277 - saving.value = false
278 - }
258 + try {
259 + await formRef.value?.validate()
260 + } catch {
261 + return
262 + }
263 +
264 + saving.value = true
265 + try {
266 + if (isEdit.value && props.config) {
267 + const updateData: GitHubAuditConfigUpdate = { ...formData }
268 + if (!updateData.github_token) {
269 + delete updateData.github_token
270 + }
271 + await Api.githubAudit.updateConfig(props.config.id, updateData)
272 + message.success("Configuration updated successfully")
273 + } else {
274 + await Api.githubAudit.createConfig(formData)
275 + message.success("Configuration created successfully")
276 + }
277 + emit("saved")
278 + showDrawer.value = false
279 + } catch (error: any) {
280 + message.error(error.response?.data?.detail || "Failed to save configuration")
281 + } finally {
282 + saving.value = false
283 + }
284 }
285
286 onMounted(async () => {
282 - try {
283 - const response = await Api.customers.getCustomers()
284 - if (response.data.customers) {
285 - customerOptions.value = response.data.customers.map((c: any) => ({
286 - label: `${c.customer_name} (${c.customer_code})`,
287 - value: c.customer_code
288 - }))
289 - }
290 - } catch (error) {
291 - console.error("Failed to load customers:", error)
292 - }
287 + try {
288 + const response = await Api.customers.getCustomers()
289 + if (response.data.customers) {
290 + customerOptions.value = response.data.customers.map((c: any) => ({
291 + label: `${c.customer_name} (${c.customer_code})`,
292 + value: c.customer_code
293 + }))
294 + }
295 + } catch (error) {
296 + console.error("Failed to load customers:", error)
297 + }
298 })
299 </script>
frontend/src/components/githubAudit/GitHubAuditDetail.vue
+153 -139
@@ -30,7 +30,9 @@
30 </n-tag>
31 </n-descriptions-item>
32 <n-descriptions-item label="Last Audit">
33 - {{ config.last_audit_at ? formatDate(config.last_audit_at) : "Never" }}
33 + {{
34 + config.last_audit_at ? formatDate(config.last_audit_at, dFormats.datetime) : "Never"
35 + }}
36 </n-descriptions-item>
37 <n-descriptions-item label="Last Score">
38 <template v-if="config.last_audit_score !== null">
@@ -86,7 +88,7 @@
88
89 <n-tab-pane name="reports" tab="Reports">
90 <n-spin :show="loadingReports">
89 - <div v-if="reports.length === 0 && !loadingReports" class="text-center py-8">
91 + <div v-if="reports.length === 0 && !loadingReports" class="py-8 text-center">
92 <n-empty description="No reports yet">
93 <template #extra>
94 <n-button type="primary" @click="runAudit">Run your first audit</n-button>
@@ -124,7 +126,7 @@
126 </div>
127
128 <n-spin :show="loadingExclusions">
127 - <div v-if="exclusions.length === 0 && !loadingExclusions" class="text-center py-8">
129 + <div v-if="exclusions.length === 0 && !loadingExclusions" class="py-8 text-center">
130 <n-empty description="No exclusions configured" />
131 </div>
132
@@ -143,7 +145,13 @@
145 <td>{{ exclusion.check_id }}</td>
146 <td>{{ exclusion.resource_name || "All" }}</td>
147 <td>{{ exclusion.reason }}</td>
146 - <td>{{ exclusion.expires_at ? formatDate(exclusion.expires_at) : "Never" }}</td>
148 + <td>
149 + {{
150 + exclusion.expires_at
151 + ? formatDate(exclusion.expires_at, dFormats.datetime)
152 + : "Never"
153 + }}
154 + </td>
155 <td>
156 <n-button text type="error" @click="deleteExclusion(exclusion.id)">
157 <n-icon><Icon :name="DeleteIcon" /></n-icon>
@@ -163,53 +171,58 @@
171 @saved="loadExclusions"
172 />
173
166 - <GitHubAuditReportDetail
167 - v-if="showReportDetail"
168 - v-model:show="showReportDetail"
169 - :report="selectedReport"
170 - />
174 + <GitHubAuditReportDetail v-if="showReportDetail" v-model:show="showReportDetail" :report="selectedReport" />
175 </n-drawer-content>
176 </n-drawer>
177 </template>
178
179 <script setup lang="ts">
176 -import type { GitHubAuditCheckExclusion, GitHubAuditConfig, GitHubAuditReport, GitHubAuditReportSummary } from "@/types/githubAudit.d"
180 +// TODO: refactor
181 +import type {
182 + GitHubAuditCheckExclusion,
183 + GitHubAuditConfig,
184 + GitHubAuditReport,
185 + GitHubAuditReportSummary
186 +} from "@/types/githubAudit.d"
187 import {
178 - NButton,
179 - NDescriptions,
180 - NDescriptionsItem,
181 - NDrawer,
182 - NDrawerContent,
183 - NEmpty,
184 - NIcon,
185 - NPagination,
186 - NPopconfirm,
187 - NSpace,
188 - NSpin,
189 - NTable,
190 - NTabPane,
191 - NTabs,
192 - NTag,
193 - useMessage
188 + NButton,
189 + NDescriptions,
190 + NDescriptionsItem,
191 + NDrawer,
192 + NDrawerContent,
193 + NEmpty,
194 + NIcon,
195 + NPagination,
196 + NPopconfirm,
197 + NSpace,
198 + NSpin,
199 + NTable,
200 + NTabPane,
201 + NTabs,
202 + NTag,
203 + useMessage
204 } from "naive-ui"
205 import { computed, ref, watch } from "vue"
206 import Api from "@/api"
207 import Icon from "@/components/common/Icon.vue"
198 -import { formatDate } from "@/utils"
208 +import { useSettingsStore } from "@/stores/settings"
209 +import { formatDate } from "@/utils/format"
210 import GitHubAuditExclusionForm from "./GitHubAuditExclusionForm.vue"
211 import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
212 import GitHubAuditReportCard from "./GitHubAuditReportCard.vue"
213 import GitHubAuditReportDetail from "./GitHubAuditReportDetail.vue"
214
215 const props = defineProps<{
205 - show: boolean
206 - config: GitHubAuditConfig | null
216 + show: boolean
217 + config: GitHubAuditConfig | null
218 }>()
219 +
220 const emit = defineEmits<{
209 - (e: "update:show", value: boolean): void
210 - (e: "updated"): void
211 - (e: "edit", config: GitHubAuditConfig): void
221 + (e: "update:show", value: boolean): void
222 + (e: "updated"): void
223 + (e: "edit", config: GitHubAuditConfig): void
224 }>()
225 +
226 const GithubIcon = "mdi:github"
227 const PlayIcon = "ion:play"
228 const EditIcon = "ion:create-outline"
@@ -217,6 +230,7 @@ const DeleteIcon = "ion:trash-outline"
230 const AddIcon = "ion:add"
231
232 const message = useMessage()
233 +const dFormats = useSettingsStore().dateFormat
234 const activeTab = ref("overview")
235 const running = ref(false)
236
@@ -235,149 +249,149 @@ const exclusions = ref<GitHubAuditCheckExclusion[]>([])
249 const showExclusionForm = ref(false)
250
251 const showDrawer = computed({
238 - get: () => props.show,
239 - set: (value) => emit("update:show", value)
252 + get: () => props.show,
253 + set: value => emit("update:show", value)
254 })
255
256 // Watch for drawer opening
257 watch(
244 - () => props.show,
245 - (show) => {
246 - if (show && props.config) {
247 - activeTab.value = "overview"
248 - // Reset state
249 - reports.value = []
250 - exclusions.value = []
251 - currentPage.value = 1
252 - // Load data
253 - loadReports()
254 - loadExclusions()
255 - }
256 - },
257 - { immediate: true }
258 + () => props.show,
259 + show => {
260 + if (show && props.config) {
261 + activeTab.value = "overview"
262 + // Reset state
263 + reports.value = []
264 + exclusions.value = []
265 + currentPage.value = 1
266 + // Load data
267 + loadReports()
268 + loadExclusions()
269 + }
270 + },
271 + { immediate: true }
272 )
273
274 // Watch for config changes while drawer is open
275 watch(
262 - () => props.config?.id,
263 - (newId, oldId) => {
264 - if (newId && newId !== oldId && props.show) {
265 - reports.value = []
266 - exclusions.value = []
267 - currentPage.value = 1
268 - loadReports()
269 - loadExclusions()
270 - }
271 - }
276 + () => props.config?.id,
277 + (newId, oldId) => {
278 + if (newId && newId !== oldId && props.show) {
279 + reports.value = []
280 + exclusions.value = []
281 + currentPage.value = 1
282 + loadReports()
283 + loadExclusions()
284 + }
285 + }
286 )
287
288 async function loadReports() {
275 - if (!props.config) {
276 - console.warn("loadReports called but config is null")
277 - return
278 - }
279 - if (loadingReports.value) {
280 - return
281 - }
282 -
283 - loadingReports.value = true
284 - try {
285 - const response = await Api.githubAudit.getReports({
286 - configId: props.config.id,
287 - limit: pageSize,
288 - offset: (currentPage.value - 1) * pageSize
289 - })
290 - reports.value = response.data.reports || []
291 - totalReports.value = response.data.total_count || 0
292 - } catch (error: any) {
293 - console.error("Failed to load reports:", error)
294 - message.error("Failed to load reports")
295 - } finally {
296 - loadingReports.value = false
297 - }
289 + if (!props.config) {
290 + console.warn("loadReports called but config is null")
291 + return
292 + }
293 + if (loadingReports.value) {
294 + return
295 + }
296 +
297 + loadingReports.value = true
298 + try {
299 + const response = await Api.githubAudit.getReports({
300 + configId: props.config.id,
301 + limit: pageSize,
302 + offset: (currentPage.value - 1) * pageSize
303 + })
304 + reports.value = response.data.reports || []
305 + totalReports.value = response.data.total_count || 0
306 + } catch (error: any) {
307 + console.error("Failed to load reports:", error)
308 + message.error("Failed to load reports")
309 + } finally {
310 + loadingReports.value = false
311 + }
312 }
313
314 async function loadExclusions() {
301 - if (!props.config || loadingExclusions.value) return
302 -
303 - loadingExclusions.value = true
304 - try {
305 - const response = await Api.githubAudit.getExclusions(props.config.id)
306 - exclusions.value = response.data.exclusions || []
307 - } catch (error: any) {
308 - console.error("Failed to load exclusions:", error)
309 - message.error("Failed to load exclusions")
310 - } finally {
311 - loadingExclusions.value = false
312 - }
315 + if (!props.config || loadingExclusions.value) return
316 +
317 + loadingExclusions.value = true
318 + try {
319 + const response = await Api.githubAudit.getExclusions(props.config.id)
320 + exclusions.value = response.data.exclusions || []
321 + } catch (error: any) {
322 + console.error("Failed to load exclusions:", error)
323 + message.error("Failed to load exclusions")
324 + } finally {
325 + loadingExclusions.value = false
326 + }
327 }
328
329 async function runAudit() {
316 - if (!props.config) return
317 -
318 - running.value = true
319 - try {
320 - await Api.githubAudit.runAuditFromConfig(props.config.id)
321 - message.success("Audit completed successfully")
322 - // Reload reports after audit completes
323 - await loadReports()
324 - emit("updated")
325 - } catch (error: any) {
326 - message.error(error.response?.data?.detail || "Failed to run audit")
327 - } finally {
328 - running.value = false
329 - }
330 + if (!props.config) return
331 +
332 + running.value = true
333 + try {
334 + await Api.githubAudit.runAuditFromConfig(props.config.id)
335 + message.success("Audit completed successfully")
336 + // Reload reports after audit completes
337 + await loadReports()
338 + emit("updated")
339 + } catch (error: any) {
340 + message.error(error.response?.data?.detail || "Failed to run audit")
341 + } finally {
342 + running.value = false
343 + }
344 }
345
346 async function deleteConfig() {
333 - if (!props.config) return
334 -
335 - try {
336 - await Api.githubAudit.deleteConfig(props.config.id)
337 - message.success("Configuration deleted")
338 - showDrawer.value = false
339 - emit("updated")
340 - } catch (error: any) {
341 - message.error(error.response?.data?.detail || "Failed to delete configuration")
342 - }
347 + if (!props.config) return
348 +
349 + try {
350 + await Api.githubAudit.deleteConfig(props.config.id)
351 + message.success("Configuration deleted")
352 + showDrawer.value = false
353 + emit("updated")
354 + } catch (error: any) {
355 + message.error(error.response?.data?.detail || "Failed to delete configuration")
356 + }
357 }
358
359 async function deleteExclusion(exclusionId: number) {
346 - try {
347 - await Api.githubAudit.deleteExclusion(exclusionId)
348 - message.success("Exclusion deleted")
349 - loadExclusions()
350 - } catch (error: any) {
351 - message.error("Failed to delete exclusion")
352 - }
360 + try {
361 + await Api.githubAudit.deleteExclusion(exclusionId)
362 + message.success("Exclusion deleted")
363 + loadExclusions()
364 + } catch {
365 + message.error("Failed to delete exclusion")
366 + }
367 }
368
369 function handleEdit() {
356 - if (props.config) {
357 - showDrawer.value = false
358 - emit("edit", props.config)
359 - }
370 + if (props.config) {
371 + showDrawer.value = false
372 + emit("edit", props.config)
373 + }
374 }
375
376 async function openReportDetail(report: GitHubAuditReportSummary) {
363 - try {
364 - const response = await Api.githubAudit.getReport(report.id)
365 - if (response.data.report) {
366 - selectedReport.value = response.data.report
367 - showReportDetail.value = true
368 - }
369 - } catch (error: any) {
370 - message.error("Failed to load report details")
371 - }
377 + try {
378 + const response = await Api.githubAudit.getReport(report.id)
379 + if (response.data.report) {
380 + selectedReport.value = response.data.report
381 + showReportDetail.value = true
382 + }
383 + } catch {
384 + message.error("Failed to load report details")
385 + }
386 }
387 </script>
388
389 <style scoped>
390 .space-y-4 > * + * {
377 - margin-top: 1rem;
391 + margin-top: 1rem;
392 }
393
394 .space-y-3 > * + * {
381 - margin-top: 0.75rem;
395 + margin-top: 0.75rem;
396 }
397 </style>
frontend/src/components/githubAudit/GitHubAuditExclusionForm.vue
+53 -62
@@ -43,30 +43,21 @@
43 </template>
44
45 <script setup lang="ts">
46 +// TODO: refactor
47 import type { FormInst, FormRules } from "naive-ui"
48 import type { GitHubAuditExclusionCreate } from "@/types/githubAudit.d"
48 -import {
49 -
50 - NButton,
51 - NDatePicker,
52 - NForm,
53 - NFormItem,
54 - NInput,
55 - NModal,
56 - NSelect,
57 - useMessage
58 -} from "naive-ui"
49 +import { NButton, NDatePicker, NForm, NFormItem, NInput, NModal, NSelect, useMessage } from "naive-ui"
50 import { computed, onMounted, reactive, ref } from "vue"
51 import Api from "@/api"
52
53 const props = defineProps<{
63 - show: boolean
64 - configId: number
54 + show: boolean
55 + configId: number
56 }>()
57
58 const emit = defineEmits<{
68 - (e: "update:show", value: boolean): void
69 - (e: "saved"): void
59 + (e: "update:show", value: boolean): void
60 + (e: "saved"): void
61 }>()
62
63 const message = useMessage()
@@ -76,64 +67,64 @@ const checkOptions = ref<{ label: string; value: string }[]>([])
67 const expiresAtTimestamp = ref<number | null>(null)
68
69 const showModal = computed({
79 - get: () => props.show,
80 - set: (value) => emit("update:show", value)
70 + get: () => props.show,
71 + set: value => emit("update:show", value)
72 })
73
74 const formData = reactive<GitHubAuditExclusionCreate>({
84 - check_id: "",
85 - resource_name: null,
86 - reason: "",
87 - approved_by: null,
88 - expires_at: null,
89 - created_by: "current_user" // TODO: Get from auth context
75 + check_id: "",
76 + resource_name: null,
77 + reason: "",
78 + approved_by: null,
79 + expires_at: null,
80 + created_by: "current_user" // TODO: Get from auth context
81 })
82
83 const rules: FormRules = {
93 - check_id: { required: true, message: "Please select a check", trigger: "blur" },
94 - reason: { required: true, message: "Please provide a reason", trigger: "blur" }
84 + check_id: { required: true, message: "Please select a check", trigger: "blur" },
85 + reason: { required: true, message: "Please provide a reason", trigger: "blur" }
86 }
87
88 async function handleSubmit() {
98 - try {
99 - await formRef.value?.validate()
100 - } catch {
101 - return
102 - }
103 -
104 - saving.value = true
105 - try {
106 - const data = {
107 - ...formData,
108 - expires_at: expiresAtTimestamp.value ? new Date(expiresAtTimestamp.value).toISOString() : null
109 - }
110 - await Api.githubAudit.createExclusion(props.configId, data)
111 - message.success("Exclusion created successfully")
112 - emit("saved")
113 - showModal.value = false
114 -
115 - // Reset form
116 - formData.check_id = ""
117 - formData.resource_name = null
118 - formData.reason = ""
119 - formData.approved_by = null
120 - expiresAtTimestamp.value = null
121 - } catch (error: any) {
122 - message.error(error.response?.data?.detail || "Failed to create exclusion")
123 - } finally {
124 - saving.value = false
125 - }
89 + try {
90 + await formRef.value?.validate()
91 + } catch {
92 + return
93 + }
94 +
95 + saving.value = true
96 + try {
97 + const data = {
98 + ...formData,
99 + expires_at: expiresAtTimestamp.value ? new Date(expiresAtTimestamp.value).toISOString() : null
100 + }
101 + await Api.githubAudit.createExclusion(props.configId, data)
102 + message.success("Exclusion created successfully")
103 + emit("saved")
104 + showModal.value = false
105 +
106 + // Reset form
107 + formData.check_id = ""
108 + formData.resource_name = null
109 + formData.reason = ""
110 + formData.approved_by = null
111 + expiresAtTimestamp.value = null
112 + } catch (error: any) {
113 + message.error(error.response?.data?.detail || "Failed to create exclusion")
114 + } finally {
115 + saving.value = false
116 + }
117 }
118
119 onMounted(async () => {
129 - try {
130 - const response = await Api.githubAudit.getAvailableChecks()
131 - checkOptions.value = response.data.checks.map((check) => ({
132 - label: `${check.name} (${check.severity})`,
133 - value: check.id
134 - }))
135 - } catch (error) {
136 - console.error("Failed to load available checks:", error)
137 - }
120 + try {
121 + const response = await Api.githubAudit.getAvailableChecks()
122 + checkOptions.value = response.data.checks.map(check => ({
123 + label: `${check.name} (${check.severity})`,
124 + value: check.id
125 + }))
126 + } catch (error) {
127 + console.error("Failed to load available checks:", error)
128 + }
129 })
130 </script>
frontend/src/components/githubAudit/GitHubAuditFilters.vue
+46 -51
@@ -1,8 +1,8 @@
1 <template>
2 <div class="github-audit-list">
3 <n-card>
4 - <div class="flex justify-between items-center mb-4">
5 - <h2 class="text-xl font-semibold m-0">GitHub Audit Configurations</h2>
4 + <div class="mb-4 flex items-center justify-between">
5 + <h2 class="m-0 text-xl font-semibold">GitHub Audit Configurations</h2>
6 <n-button type="primary" @click="openCreateForm">
7 <template #icon>
8 <Icon :name="AddIcon" :size="16" />
@@ -11,7 +11,7 @@
11 </n-button>
12 </div>
13
14 - <div class="github-audit-filters flex gap-4 flex-wrap items-center mb-4">
14 + <div class="github-audit-filters mb-4 flex flex-wrap items-center gap-4">
15 <n-select
16 v-model:value="filterCustomerCode"
17 placeholder="Filter by Customer"
@@ -44,7 +44,7 @@
44 </div>
45
46 <n-spin :show="loading">
47 - <div v-if="configs.length === 0 && !loading" class="text-center py-8">
47 + <div v-if="configs.length === 0 && !loading" class="py-8 text-center">
48 <n-empty description="No configurations found">
49 <template #extra>
50 <n-button type="primary" @click="openCreateForm">Create your first configuration</n-button>
@@ -83,6 +83,7 @@
83 </template>
84
85 <script setup lang="ts">
86 +// TODO: refactor
87 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
88 import { NButton, NCard, NEmpty, NGi, NGrid, NInput, NSelect, NSpin, useMessage } from "naive-ui"
89 import { onMounted, ref } from "vue"
@@ -110,73 +111,67 @@ const customerOptions = ref<{ label: string; value: string }[]>([])
111 const loadingCustomers = ref(false)
112
113 const statusOptions = [
113 - { label: "Enabled", value: "enabled" },
114 - { label: "Disabled", value: "disabled" }
114 + { label: "Enabled", value: "enabled" },
115 + { label: "Disabled", value: "disabled" }
116 ]
117
118 async function loadCustomers() {
118 - if (loadingCustomers.value) return
119 -
120 - loadingCustomers.value = true
121 - try {
122 - const response = await Api.customers.getCustomers()
123 - if (response.data.customers) {
124 - customerOptions.value = response.data.customers.map((c: any) => ({
125 - label: `${c.customer_name} (${c.customer_code})`,
126 - value: c.customer_code
127 - }))
128 - }
129 - } catch (error) {
130 - console.error("Failed to load customers:", error)
131 - } finally {
132 - loadingCustomers.value = false
133 - }
119 + if (loadingCustomers.value) return
120 +
121 + loadingCustomers.value = true
122 + try {
123 + const response = await Api.customers.getCustomers()
124 + if (response.data.customers) {
125 + customerOptions.value = response.data.customers.map((c: any) => ({
126 + label: `${c.customer_name} (${c.customer_code})`,
127 + value: c.customer_code
128 + }))
129 + }
130 + } catch (error) {
131 + console.error("Failed to load customers:", error)
132 + } finally {
133 + loadingCustomers.value = false
134 + }
135 }
136
137 async function loadConfigs() {
137 - if (loading.value) return
138 -
139 - loading.value = true
140 - try {
141 - const response = await Api.githubAudit.getConfigs({
142 - customerCode: filterCustomerCode.value || undefined,
143 - enabled: filterStatus.value === "enabled"
144 -? true :
145 - filterStatus.value === "disabled" ? false : undefined,
146 - organization: filterOrganization.value || undefined
147 - })
148 - configs.value = response.data.configs || []
149 - } catch (error: any) {
150 - message.error(error.response?.data?.detail || "Failed to load configurations")
151 - configs.value = []
152 - } finally {
153 - loading.value = false
154 - }
138 + if (loading.value) return
139 +
140 + loading.value = true
141 + try {
142 + const response = await Api.githubAudit.getConfigs(filterCustomerCode.value || undefined)
143 + configs.value = response.data.configs || []
144 + } catch (error: any) {
145 + message.error(error.response?.data?.detail || "Failed to load configurations")
146 + configs.value = []
147 + } finally {
148 + loading.value = false
149 + }
150 }
151
152 function openCreateForm() {
158 - selectedConfig.value = null
159 - showForm.value = true
153 + selectedConfig.value = null
154 + showForm.value = true
155 }
156
157 function openEditForm(config: GitHubAuditConfig) {
163 - selectedConfig.value = config
164 - showDetail.value = false
165 - showForm.value = true
158 + selectedConfig.value = config
159 + showDetail.value = false
160 + showForm.value = true
161 }
162
163 function openDetail(config: GitHubAuditConfig) {
169 - selectedConfig.value = config
170 - showDetail.value = true
164 + selectedConfig.value = config
165 + showDetail.value = true
166 }
167
168 function onConfigSaved() {
174 - showForm.value = false
175 - loadConfigs()
169 + showForm.value = false
170 + loadConfigs()
171 }
172
173 onMounted(() => {
179 - loadCustomers()
180 - loadConfigs()
174 + loadCustomers()
175 + loadConfigs()
176 })
177 </script>
frontend/src/components/githubAudit/GitHubAuditGradeBadge.vue
+20 -19
@@ -5,31 +5,32 @@
5 </template>
6
7 <script setup lang="ts">
8 +// TODO: refactor
9 import { NTag } from "naive-ui"
10 import { computed } from "vue"
11
12 const props = defineProps<{
12 - grade: string
13 - score?: number
13 + grade: string
14 + score?: number
15 }>()
16
17 const gradeType = computed(() => {
17 - switch (props.grade) {
18 - case "A":
19 - case "A+":
20 - return "success"
21 - case "B":
22 - case "B+":
23 - return "info"
24 - case "C":
25 - case "C+":
26 - return "warning"
27 - case "D":
28 - case "D+":
29 - case "F":
30 - return "error"
31 - default:
32 - return "default"
33 - }
18 + switch (props.grade) {
19 + case "A":
20 + case "A+":
21 + return "success"
22 + case "B":
23 + case "B+":
24 + return "info"
25 + case "C":
26 + case "C+":
27 + return "warning"
28 + case "D":
29 + case "D+":
30 + case "F":
31 + return "error"
32 + default:
33 + return "default"
34 + }
35 })
36 </script>
frontend/src/components/githubAudit/GitHubAuditInfo.vue
+185 -165
@@ -34,10 +34,19 @@
34 <n-divider />
35 <div class="text-sm text-gray-500">
36 <p class="mb-2"><strong>Skip Reasons:</strong></p>
37 - <ul class="list-disc list-inside space-y-1">
38 - <li><code>not_authorized</code> — Token/user missing permission</li>
39 - <li><code>not_supported</code> — Plan/feature not available</li>
40 - <li><code>error</code> — Transient/API error; retry or inspect details</li>
37 + <ul class="list-inside list-disc space-y-1">
38 + <li>
39 + <code>not_authorized</code>
40 + — Token/user missing permission
41 + </li>
42 + <li>
43 + <code>not_supported</code>
44 + — Plan/feature not available
45 + </li>
46 + <li>
47 + <code>error</code>
48 + — Transient/API error; retry or inspect details
49 + </li>
50 </ul>
51 </div>
52 </n-card>
@@ -54,7 +63,9 @@
63 <n-list-item v-for="control in orgControls" :key="control.id">
64 <template #prefix>
65 <n-icon :color="control.critical ? '#e88080' : '#63e2b7'">
57 - <Icon :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'" />
66 + <Icon
67 + :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
68 + />
69 </n-icon>
70 </template>
71 <div>
@@ -70,7 +81,9 @@
81 <n-list-item v-for="control in repoControls" :key="control.id">
82 <template #prefix>
83 <n-icon :color="control.critical ? '#e88080' : '#63e2b7'">
73 - <Icon :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'" />
84 + <Icon
85 + :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
86 + />
87 </n-icon>
88 </template>
89 <div>
@@ -89,15 +102,17 @@
102 </template>
103
104 <n-alert type="info" class="mb-4">
92 - This audit is intentionally <strong>read-only</strong>. No write or admin scopes are required.
105 + This audit is intentionally
106 + <strong>read-only</strong>
107 + . No write or admin scopes are required.
108 </n-alert>
109
110 <n-tabs type="segment" animated>
111 <n-tab-pane name="fine-grained" tab="Fine-Grained PAT (Recommended)">
112 <div class="space-y-4">
113 <p class="text-sm">
99 - Create a fine-grained PAT restricted to only the target organization and repos you intend to
100 - audit.
114 + Create a fine-grained PAT restricted to only the target organization and repos
115 + you intend to audit.
116 </p>
117
118 <n-card size="small" title="Organization Permissions (READ)">
@@ -173,7 +188,9 @@
188 <span class="text-green-500">GET</span>
189 {{ endpoint.path }}
190 </div>
176 - <div v-if="endpoint.note" class="text-xs text-gray-500 mt-1">{{ endpoint.note }}</div>
191 + <div v-if="endpoint.note" class="mt-1 text-xs text-gray-500">
192 + {{ endpoint.note }}
193 + </div>
194 </n-list-item>
195 </n-list>
196 </n-card>
@@ -185,7 +202,9 @@
202 <span class="text-green-500">GET</span>
203 {{ endpoint.path }}
204 </div>
188 - <div v-if="endpoint.note" class="text-xs text-gray-500 mt-1">{{ endpoint.note }}</div>
205 + <div v-if="endpoint.note" class="mt-1 text-xs text-gray-500">
206 + {{ endpoint.note }}
207 + </div>
208 </n-list-item>
209 </n-list>
210 </n-card>
@@ -197,209 +216,210 @@
216 </template>
217
218 <script setup lang="ts">
219 +// TODO: refactor
220 import {
201 - NAlert,
202 - NCard,
203 - NCollapse,
204 - NCollapseItem,
205 - NDivider,
206 - NDrawer,
207 - NDrawerContent,
208 - NIcon,
209 - NList,
210 - NListItem,
211 - NTabPane,
212 - NTabs,
213 - NTag
221 + NAlert,
222 + NCard,
223 + NCollapse,
224 + NCollapseItem,
225 + NDivider,
226 + NDrawer,
227 + NDrawerContent,
228 + NIcon,
229 + NList,
230 + NListItem,
231 + NTabPane,
232 + NTabs,
233 + NTag
234 } from "naive-ui"
235 import { computed } from "vue"
236 import Icon from "@/components/common/Icon.vue"
237
238 const props = defineProps<{
219 - show: boolean
239 + show: boolean
240 }>()
241
242 const emit = defineEmits<{
223 - (e: "update:show", value: boolean): void
243 + (e: "update:show", value: boolean): void
244 }>()
245
246 const InfoIcon = "ion:information-circle-outline"
247
248 const showDrawer = computed({
229 - get: () => props.show,
230 - set: (value) => emit("update:show", value)
249 + get: () => props.show,
250 + set: value => emit("update:show", value)
251 })
252
253 // Controls data
254 const orgControls = [
235 - {
236 - id: "mfa",
237 - name: "MFA Enforcement",
238 - description: "Require two-factor authentication for all organization members",
239 - critical: true
240 - },
241 - {
242 - id: "org-owners",
243 - name: "Org Owner Minimization",
244 - description: "Limit the number of organization owners to reduce risk",
245 - critical: false
246 - },
247 - {
248 - id: "outside-collab",
249 - name: "Outside Collaborator Monitoring",
250 - description: "Track external collaborators with access to repositories",
251 - critical: false
252 - },
253 - {
254 - id: "saml-sso",
255 - name: "SAML SSO Enforced",
256 - description: "Enforce single sign-on for centralized authentication",
257 - critical: true
258 - },
259 - {
260 - id: "pat-expiry",
261 - name: "PAT Expiration Enforced",
262 - description: "Require personal access tokens to have expiration dates",
263 - critical: false
264 - },
265 - {
266 - id: "actions-policy",
267 - name: "GitHub Actions Policy",
268 - description: "Control allowed actions, default permissions, and fork PR approvals",
269 - critical: false
270 - },
271 - {
272 - id: "audit-log",
273 - name: "Audit Log Monitoring",
274 - description: "Monitor control-plane changes via audit log detection pack",
275 - critical: false
276 - }
255 + {
256 + id: "mfa",
257 + name: "MFA Enforcement",
258 + description: "Require two-factor authentication for all organization members",
259 + critical: true
260 + },
261 + {
262 + id: "org-owners",
263 + name: "Org Owner Minimization",
264 + description: "Limit the number of organization owners to reduce risk",
265 + critical: false
266 + },
267 + {
268 + id: "outside-collab",
269 + name: "Outside Collaborator Monitoring",
270 + description: "Track external collaborators with access to repositories",
271 + critical: false
272 + },
273 + {
274 + id: "saml-sso",
275 + name: "SAML SSO Enforced",
276 + description: "Enforce single sign-on for centralized authentication",
277 + critical: true
278 + },
279 + {
280 + id: "pat-expiry",
281 + name: "PAT Expiration Enforced",
282 + description: "Require personal access tokens to have expiration dates",
283 + critical: false
284 + },
285 + {
286 + id: "actions-policy",
287 + name: "GitHub Actions Policy",
288 + description: "Control allowed actions, default permissions, and fork PR approvals",
289 + critical: false
290 + },
291 + {
292 + id: "audit-log",
293 + name: "Audit Log Monitoring",
294 + description: "Monitor control-plane changes via audit log detection pack",
295 + critical: false
296 + }
297 ]
298
299 const repoControls = [
280 - {
281 - id: "visibility",
282 - name: "Repository Visibility",
283 - description: "Ensure appropriate public/private visibility settings",
284 - critical: false
285 - },
286 - {
287 - id: "branch-protection",
288 - name: "Branch Protection",
289 - description: "Enforce approvals, status checks, and prevent force pushes",
290 - critical: true
291 - },
292 - {
293 - id: "secret-scanning",
294 - name: "Secret Scanning + Push Protection",
295 - description: "Detect and block secrets in code before they're exposed",
296 - critical: true
297 - },
298 - {
299 - id: "dependabot",
300 - name: "Dependabot Alerts",
301 - description: "Monitor dependencies for known vulnerabilities",
302 - critical: true
303 - },
304 - {
305 - id: "code-scanning",
306 - name: "Code Scanning",
307 - description: "Identify security vulnerabilities in source code",
308 - critical: false
309 - },
310 - {
311 - id: "environments",
312 - name: "Deployment Environments",
313 - description: "Protect deployment environments with reviewers and rules",
314 - critical: false
315 - }
300 + {
301 + id: "visibility",
302 + name: "Repository Visibility",
303 + description: "Ensure appropriate public/private visibility settings",
304 + critical: false
305 + },
306 + {
307 + id: "branch-protection",
308 + name: "Branch Protection",
309 + description: "Enforce approvals, status checks, and prevent force pushes",
310 + critical: true
311 + },
312 + {
313 + id: "secret-scanning",
314 + name: "Secret Scanning + Push Protection",
315 + description: "Detect and block secrets in code before they're exposed",
316 + critical: true
317 + },
318 + {
319 + id: "dependabot",
320 + name: "Dependabot Alerts",
321 + description: "Monitor dependencies for known vulnerabilities",
322 + critical: true
323 + },
324 + {
325 + id: "code-scanning",
326 + name: "Code Scanning",
327 + description: "Identify security vulnerabilities in source code",
328 + critical: false
329 + },
330 + {
331 + id: "environments",
332 + name: "Deployment Environments",
333 + description: "Protect deployment environments with reviewers and rules",
334 + critical: false
335 + }
336 ]
337
338 // Fine-grained PAT permissions
339 const fineGrainedOrgPerms = [
320 - {
321 - name: "Administration",
322 - description: "Required for org policy, Actions settings, SSO indicator, PAT policy",
323 - required: true
324 - },
325 - {
326 - name: "Members",
327 - description: "Required for outside collaborator visibility and membership endpoints",
328 - required: true
329 - },
330 - {
331 - name: "Audit Log",
332 - description: "Required for audit-log detections; otherwise those checks will SKIP",
333 - required: false
334 - }
340 + {
341 + name: "Administration",
342 + description: "Required for org policy, Actions settings, SSO indicator, PAT policy",
343 + required: true
344 + },
345 + {
346 + name: "Members",
347 + description: "Required for outside collaborator visibility and membership endpoints",
348 + required: true
349 + },
350 + {
351 + name: "Audit Log",
352 + description: "Required for audit-log detections; otherwise those checks will SKIP",
353 + required: false
354 + }
355 ]
356
357 const fineGrainedRepoPerms = [
338 - {
339 - name: "Administration",
340 - description: "Required for branch protection, environments, and repo settings",
341 - required: true
342 - },
343 - {
344 - name: "Contents",
345 - description: "Often required for workflow-related metadata visibility",
346 - required: true
347 - },
348 - {
349 - name: "Actions",
350 - description: "Recommended for Actions-related repo metadata",
351 - required: false
352 - },
353 - {
354 - name: "Security events",
355 - description: "Required for secret scanning, Dependabot alerts, code scanning status",
356 - required: true
357 - }
358 + {
359 + name: "Administration",
360 + description: "Required for branch protection, environments, and repo settings",
361 + required: true
362 + },
363 + {
364 + name: "Contents",
365 + description: "Often required for workflow-related metadata visibility",
366 + required: true
367 + },
368 + {
369 + name: "Actions",
370 + description: "Recommended for Actions-related repo metadata",
371 + required: false
372 + },
373 + {
374 + name: "Security events",
375 + description: "Required for secret scanning, Dependabot alerts, code scanning status",
376 + required: true
377 + }
378 ]
379
380 // Classic PAT scopes
381 const classicScopes = [
362 - {
363 - name: "read:org",
364 - description: "Needed for org membership and org settings",
365 - required: true
366 - },
367 - {
368 - name: "repo",
369 - description: "Required to read branch protection and repo config on private repos",
370 - required: true
371 - }
382 + {
383 + name: "read:org",
384 + description: "Needed for org membership and org settings",
385 + required: true
386 + },
387 + {
388 + name: "repo",
389 + description: "Required to read branch protection and repo config on private repos",
390 + required: true
391 + }
392 ]
393
394 // API endpoints
395 const orgEndpoints = [
376 - { path: "/orgs/{org}", note: null },
377 - { path: "/orgs/{org}/repos", note: null },
378 - { path: "/orgs/{org}/actions/permissions", note: null },
379 - { path: "/orgs/{org}/actions/permissions/workflow", note: null },
380 - { path: "/orgs/{org}/actions/permissions/selected-actions", note: null },
381 - { path: "/orgs/{org}/personal-access-tokens/policies", note: "May be plan/role gated" },
382 - { path: "/orgs/{org}/audit-log", note: "Detections; plan/role gated" }
396 + { path: "/orgs/{org}", note: null },
397 + { path: "/orgs/{org}/repos", note: null },
398 + { path: "/orgs/{org}/actions/permissions", note: null },
399 + { path: "/orgs/{org}/actions/permissions/workflow", note: null },
400 + { path: "/orgs/{org}/actions/permissions/selected-actions", note: null },
401 + { path: "/orgs/{org}/personal-access-tokens/policies", note: "May be plan/role gated" },
402 + { path: "/orgs/{org}/audit-log", note: "Detections; plan/role gated" }
403 ]
404
405 const repoEndpoints = [
386 - { path: "/repos/{org}/{repo}", note: "Includes security_and_analysis where available" },
387 - { path: "/repos/{org}/{repo}/branches/{branch}/protection", note: null },
388 - { path: "/repos/{org}/{repo}/environments", note: null }
406 + { path: "/repos/{org}/{repo}", note: "Includes security_and_analysis where available" },
407 + { path: "/repos/{org}/{repo}/branches/{branch}/protection", note: null },
408 + { path: "/repos/{org}/{repo}/environments", note: null }
409 ]
410 </script>
411
412 <style scoped>
413 .space-y-6 > * + * {
394 - margin-top: 1.5rem;
414 + margin-top: 1.5rem;
415 }
416
417 .space-y-4 > * + * {
398 - margin-top: 1rem;
418 + margin-top: 1rem;
419 }
420
421 .grid-cols-2 {
402 - display: grid;
403 - grid-template-columns: repeat(2, 1fr);
422 + display: grid;
423 + grid-template-columns: repeat(2, 1fr);
424 }
425 </style>
frontend/src/components/githubAudit/GitHubAuditList.vue
+62 -61
@@ -1,8 +1,8 @@
1 <template>
2 <div class="github-audit-list">
3 <n-card>
4 - <div class="flex justify-between items-center mb-4">
5 - <h2 class="text-xl font-semibold m-0">GitHub Audit Configurations</h2>
4 + <div class="mb-4 flex items-center justify-between">
5 + <h2 class="m-0 text-xl font-semibold">GitHub Audit Configurations</h2>
6 <n-space>
7 <n-button quaternary @click="showInfo = true">
8 <template #icon>
@@ -19,7 +19,7 @@
19 </n-space>
20 </div>
21
22 - <div class="github-audit-filters flex gap-4 flex-wrap items-center mb-4">
22 + <div class="github-audit-filters mb-4 flex flex-wrap items-center gap-4">
23 <n-select
24 v-model:value="filterCustomerCode"
25 placeholder="Filter by Customer"
@@ -52,7 +52,7 @@
52 </div>
53
54 <n-spin :show="loading">
55 - <div v-if="configs.length === 0 && !loading" class="text-center py-8">
55 + <div v-if="configs.length === 0 && !loading" class="py-8 text-center">
56 <n-empty description="No configurations found">
57 <template #extra>
58 <n-button type="primary" @click="openCreateForm">Create your first configuration</n-button>
@@ -93,6 +93,7 @@
93 </template>
94
95 <script setup lang="ts">
96 +// TODO: refactor
97 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
98 import { NButton, NCard, NEmpty, NGi, NGrid, NIcon, NInput, NSelect, NSpace, NSpin, useMessage } from "naive-ui"
99 import { onMounted, ref } from "vue"
@@ -123,83 +124,83 @@ const customerOptions = ref<{ label: string; value: string }[]>([])
124 const loadingCustomers = ref(false)
125
126 const statusOptions = [
126 - { label: "Enabled", value: "enabled" },
127 - { label: "Disabled", value: "disabled" }
127 + { label: "Enabled", value: "enabled" },
128 + { label: "Disabled", value: "disabled" }
129 ]
130
131 async function loadCustomers() {
131 - if (loadingCustomers.value) return
132 -
133 - loadingCustomers.value = true
134 - try {
135 - const response = await Api.customers.getCustomers()
136 - if (response.data.customers) {
137 - customerOptions.value = response.data.customers.map((c: any) => ({
138 - label: `${c.customer_name} (${c.customer_code})`,
139 - value: c.customer_code
140 - }))
141 - }
142 - } catch (error) {
143 - console.error("Failed to load customers:", error)
144 - } finally {
145 - loadingCustomers.value = false
146 - }
132 + if (loadingCustomers.value) return
133 +
134 + loadingCustomers.value = true
135 + try {
136 + const response = await Api.customers.getCustomers()
137 + if (response.data.customers) {
138 + customerOptions.value = response.data.customers.map((c: any) => ({
139 + label: `${c.customer_name} (${c.customer_code})`,
140 + value: c.customer_code
141 + }))
142 + }
143 + } catch (error) {
144 + console.error("Failed to load customers:", error)
145 + } finally {
146 + loadingCustomers.value = false
147 + }
148 }
149
150 async function loadConfigs() {
150 - if (loading.value) return
151 -
152 - loading.value = true
153 - try {
154 - const response = await Api.githubAudit.getConfigs(filterCustomerCode.value || undefined)
155 - if (response.data.configs) {
156 - let filteredConfigs = response.data.configs
157 -
158 - if (filterStatus.value) {
159 - const isEnabled = filterStatus.value === "enabled"
160 - filteredConfigs = filteredConfigs.filter((c: GitHubAuditConfig) => c.enabled === isEnabled)
161 - }
162 -
163 - if (filterOrganization.value) {
164 - const searchTerm = filterOrganization.value.toLowerCase()
165 - filteredConfigs = filteredConfigs.filter((c: GitHubAuditConfig) =>
166 - c.organization.toLowerCase().includes(searchTerm)
167 - )
168 - }
169 -
170 - configs.value = filteredConfigs
171 - }
172 - } catch (error: any) {
173 - message.error(error.response?.data?.detail || "Failed to load configurations")
174 - configs.value = []
175 - } finally {
176 - loading.value = false
177 - }
151 + if (loading.value) return
152 +
153 + loading.value = true
154 + try {
155 + const response = await Api.githubAudit.getConfigs(filterCustomerCode.value || undefined)
156 + if (response.data.configs) {
157 + let filteredConfigs = response.data.configs
158 +
159 + if (filterStatus.value) {
160 + const isEnabled = filterStatus.value === "enabled"
161 + filteredConfigs = filteredConfigs.filter((c: GitHubAuditConfig) => c.enabled === isEnabled)
162 + }
163 +
164 + if (filterOrganization.value) {
165 + const searchTerm = filterOrganization.value.toLowerCase()
166 + filteredConfigs = filteredConfigs.filter((c: GitHubAuditConfig) =>
167 + c.organization.toLowerCase().includes(searchTerm)
168 + )
169 + }
170 +
171 + configs.value = filteredConfigs
172 + }
173 + } catch (error: any) {
174 + message.error(error.response?.data?.detail || "Failed to load configurations")
175 + configs.value = []
176 + } finally {
177 + loading.value = false
178 + }
179 }
180
181 function openCreateForm() {
181 - selectedConfig.value = null
182 - showForm.value = true
182 + selectedConfig.value = null
183 + showForm.value = true
184 }
185
186 function openEditForm(config: GitHubAuditConfig) {
186 - selectedConfig.value = config
187 - showDetail.value = false
188 - showForm.value = true
187 + selectedConfig.value = config
188 + showDetail.value = false
189 + showForm.value = true
190 }
191
192 function openDetail(config: GitHubAuditConfig) {
192 - selectedConfig.value = config
193 - showDetail.value = true
193 + selectedConfig.value = config
194 + showDetail.value = true
195 }
196
197 function onConfigSaved() {
197 - showForm.value = false
198 - loadConfigs()
198 + showForm.value = false
199 + loadConfigs()
200 }
201
202 onMounted(() => {
202 - loadCustomers()
203 - loadConfigs()
203 + loadCustomers()
204 + loadConfigs()
205 })
206 </script>
frontend/src/components/githubAudit/GitHubAuditReportCard.vue
+33 -35
@@ -1,16 +1,14 @@
1 <template>
2 <n-card class="github-audit-report-card" hoverable size="small" @click="$emit('click', report)">
3 - <div class="flex justify-between items-center">
3 + <div class="flex items-center justify-between">
4 <div class="flex-1">
5 - <div class="flex items-center gap-2 mb-1">
5 + <div class="mb-1 flex items-center gap-2">
6 <span class="font-medium">{{ report.report_name }}</span>
7 <n-tag :type="statusType" size="small">{{ report.status }}</n-tag>
8 </div>
9 <div class="text-secondary text-xs">
10 - {{ formatDate(report.audit_started_at) }}
11 - <span v-if="report.audit_duration_seconds">
12 - • {{ report.audit_duration_seconds.toFixed(1) }}s
13 - </span>
10 + {{ formatDate(report.audit_started_at, dFormats.datetime) }}
11 + <span v-if="report.audit_duration_seconds">• {{ report.audit_duration_seconds.toFixed(1) }}s</span>
12 </div>
13 </div>
14
@@ -24,12 +22,8 @@
22 <div v-if="report.critical_findings" class="text-error">
23 {{ report.critical_findings }} Critical
24 </div>
27 - <div v-if="report.high_findings" class="text-warning">
28 - {{ report.high_findings }} High
29 - </div>
30 - <div class="text-secondary">
31 - {{ report.passed_checks }}/{{ report.total_checks }} Passed
32 - </div>
25 + <div v-if="report.high_findings" class="text-warning">{{ report.high_findings }} High</div>
26 + <div class="text-secondary">{{ report.passed_checks }}/{{ report.total_checks }} Passed</div>
27 </div>
28 </div>
29 </div>
@@ -37,63 +31,67 @@
31 </template>
32
33 <script setup lang="ts">
34 +// TODO: refactor
35 import type { GitHubAuditReportSummary } from "@/types/githubAudit.d"
36 import { NCard, NTag } from "naive-ui"
37 import { computed } from "vue"
43 -import { formatDate } from "@/utils"
38 +import { useSettingsStore } from "@/stores/settings"
39 +import { formatDate } from "@/utils/format"
40 import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
41
42 const props = defineProps<{
47 - report: GitHubAuditReportSummary
43 + report: GitHubAuditReportSummary
44 }>()
45
46 defineEmits<{
51 - (e: "click", report: GitHubAuditReportSummary): void
47 + (e: "click", report: GitHubAuditReportSummary): void
48 }>()
49
50 +const dFormats = useSettingsStore().dateFormat
51 +
52 const statusType = computed(() => {
55 - switch (props.report.status) {
56 - case "completed":
57 - return "success"
58 - case "running":
59 - return "info"
60 - case "failed":
61 - return "error"
62 - default:
63 - return "default"
64 - }
53 + switch (props.report.status) {
54 + case "completed":
55 + return "success"
56 + case "running":
57 + return "info"
58 + case "failed":
59 + return "error"
60 + default:
61 + return "default"
62 + }
63 })
64
65 const scoreClass = computed(() => {
68 - if (props.report.score >= 80) return "text-success"
69 - if (props.report.score >= 60) return "text-warning"
70 - return "text-error"
66 + if (props.report.score >= 80) return "text-success"
67 + if (props.report.score >= 60) return "text-warning"
68 + return "text-error"
69 })
70 </script>
71
72 <style scoped>
73 .github-audit-report-card {
76 - cursor: pointer;
77 - transition: all 0.2s ease;
74 + cursor: pointer;
75 + transition: all 0.2s ease;
76 }
77
78 .github-audit-report-card:hover {
81 - transform: translateY(-1px);
79 + transform: translateY(-1px);
80 }
81
82 .text-secondary {
85 - color: var(--text-color-3);
83 + color: var(--text-color-3);
84 }
85
86 .text-success {
89 - color: var(--success-color);
87 + color: var(--success-color);
88 }
89
90 .text-warning {
93 - color: var(--warning-color);
91 + color: var(--warning-color);
92 }
93
94 .text-error {
97 - color: var(--error-color);
95 + color: var(--error-color);
96 }
97 </style>
frontend/src/components/githubAudit/GitHubAuditReportDetail.vue
+95 -106
@@ -27,16 +27,12 @@
27 </n-gi>
28 <n-gi>
29 <n-statistic label="Checks Passed">
30 - <template #default>
31 - {{ report.passed_checks }} / {{ report.total_checks }}
32 - </template>
30 + <template #default>{{ report.passed_checks }} / {{ report.total_checks }}</template>
31 </n-statistic>
32 </n-gi>
33 <n-gi>
34 <n-statistic label="Duration">
37 - <template #default>
38 - {{ report.audit_duration_seconds?.toFixed(1) || "N/A" }}s
39 - </template>
35 + <template #default>{{ report.audit_duration_seconds?.toFixed(1) || "N/A" }}s</template>
36 </n-statistic>
37 </n-gi>
38 </n-grid>
@@ -99,11 +95,7 @@
95 </n-card>
96
97 <!-- Organization Results -->
102 - <n-card
103 - v-if="report.full_report?.organization_results"
104 - title="Organization Settings"
105 - size="small"
106 - >
98 + <n-card v-if="report.full_report?.organization_results" title="Organization Settings" size="small">
99 <n-table :bordered="false" :single-line="false" size="small">
100 <thead>
101 <tr>
@@ -127,11 +119,7 @@
119 </n-card>
120
121 <!-- Repository Results -->
130 - <n-card
131 - v-if="report.full_report?.repository_results?.length"
132 - title="Repository Results"
133 - size="small"
134 - >
122 + <n-card v-if="report.full_report?.repository_results?.length" title="Repository Results" size="small">
123 <n-collapse>
124 <n-collapse-item
125 v-for="repo in report.full_report.repository_results"
@@ -193,23 +181,24 @@
181 </template>
182
183 <script setup lang="ts">
184 +// TODO: refactor
185 import type { GitHubAuditReport } from "@/types/githubAudit.d"
186 import {
198 - NAlert,
199 - NButton,
200 - NCard,
201 - NCollapse,
202 - NCollapseItem,
203 - NDrawer,
204 - NDrawerContent,
205 - NGi,
206 - NGrid,
207 - NPopconfirm,
208 - NSpace,
209 - NStatistic,
210 - NTable,
211 - NTag,
212 - useMessage
187 + NAlert,
188 + NButton,
189 + NCard,
190 + NCollapse,
191 + NCollapseItem,
192 + NDrawer,
193 + NDrawerContent,
194 + NGi,
195 + NGrid,
196 + NPopconfirm,
197 + NSpace,
198 + NStatistic,
199 + NTable,
200 + NTag,
201 + useMessage
202 } from "naive-ui"
203 import { computed } from "vue"
204 import Api from "@/api"
@@ -217,150 +206,150 @@ import { AuditStatus, SeverityLevel } from "@/types/githubAudit.d"
206 import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
207
208 const props = defineProps<{
220 - show: boolean
221 - report: GitHubAuditReport | null
209 + show: boolean
210 + report: GitHubAuditReport | null
211 }>()
212
213 const emit = defineEmits<{
225 - (e: "update:show", value: boolean): void
226 - (e: "deleted"): void
214 + (e: "update:show", value: boolean): void
215 + (e: "deleted"): void
216 }>()
217
218 const message = useMessage()
219
220 const showDrawer = computed({
232 - get: () => props.show,
233 - set: (value) => emit("update:show", value)
221 + get: () => props.show,
222 + set: value => emit("update:show", value)
223 })
224
225 const statusType = computed(() => {
237 - switch (props.report?.status) {
238 - case "completed":
239 - return "success"
240 - case "running":
241 - return "info"
242 - case "failed":
243 - return "error"
244 - default:
245 - return "default"
246 - }
226 + switch (props.report?.status) {
227 + case "completed":
228 + return "success"
229 + case "running":
230 + return "info"
231 + case "failed":
232 + return "error"
233 + default:
234 + return "default"
235 + }
236 })
237
238 const scoreClass = computed(() => {
250 - const score = props.report?.score ?? 0
251 - if (score >= 80) return "text-success"
252 - if (score >= 60) return "text-warning"
253 - return "text-error"
239 + const score = props.report?.score ?? 0
240 + if (score >= 80) return "text-success"
241 + if (score >= 60) return "text-warning"
242 + return "text-error"
243 })
244
245 function getStatusType(status: AuditStatus | string) {
257 - switch (status) {
258 - case AuditStatus.PASS:
259 - case "pass":
260 - return "success"
261 - case AuditStatus.FAIL:
262 - case "fail":
263 - return "error"
264 - case AuditStatus.WARNING:
265 - case "warning":
266 - return "warning"
267 - default:
268 - return "default"
269 - }
246 + switch (status) {
247 + case AuditStatus.PASS:
248 + case "pass":
249 + return "success"
250 + case AuditStatus.FAIL:
251 + case "fail":
252 + return "error"
253 + case AuditStatus.WARNING:
254 + case "warning":
255 + return "warning"
256 + default:
257 + return "default"
258 + }
259 }
260
261 function getSeverityType(severity: SeverityLevel | string) {
273 - switch (severity) {
274 - case SeverityLevel.CRITICAL:
275 - case "critical":
276 - return "error"
277 - case SeverityLevel.HIGH:
278 - case "high":
279 - return "warning"
280 - case SeverityLevel.MEDIUM:
281 - case "medium":
282 - return "info"
283 - default:
284 - return "default"
285 - }
262 + switch (severity) {
263 + case SeverityLevel.CRITICAL:
264 + case "critical":
265 + return "error"
266 + case SeverityLevel.HIGH:
267 + case "high":
268 + return "warning"
269 + case SeverityLevel.MEDIUM:
270 + case "medium":
271 + return "info"
272 + default:
273 + return "default"
274 + }
275 }
276
277 async function deleteReport() {
289 - if (!props.report) return
290 -
291 - try {
292 - await Api.githubAudit.deleteReport(props.report.id)
293 - message.success("Report deleted")
294 - showDrawer.value = false
295 - emit("deleted")
296 - } catch (error: any) {
297 - message.error("Failed to delete report")
298 - }
278 + if (!props.report) return
279 +
280 + try {
281 + await Api.githubAudit.deleteReport(props.report.id)
282 + message.success("Report deleted")
283 + showDrawer.value = false
284 + emit("deleted")
285 + } catch {
286 + message.error("Failed to delete report")
287 + }
288 }
289 </script>
290
291 <style scoped>
292 .space-y-6 > * + * {
304 - margin-top: 1.5rem;
293 + margin-top: 1.5rem;
294 }
295
296 .finding-stat {
308 - text-align: center;
309 - padding: 16px;
310 - border-radius: 8px;
297 + text-align: center;
298 + padding: 16px;
299 + border-radius: 8px;
300 }
301
302 .finding-stat .number {
314 - font-size: 2rem;
315 - font-weight: bold;
303 + font-size: 2rem;
304 + font-weight: bold;
305 }
306
307 .finding-stat .label {
319 - font-size: 0.875rem;
320 - color: var(--text-color-3);
308 + font-size: 0.875rem;
309 + color: var(--text-color-3);
310 }
311
312 .finding-stat.critical {
324 - background: rgba(208, 48, 80, 0.1);
313 + background: rgba(208, 48, 80, 0.1);
314 }
315
316 .finding-stat.critical .number {
328 - color: #d03050;
317 + color: #d03050;
318 }
319
320 .finding-stat.high {
332 - background: rgba(240, 160, 32, 0.1);
321 + background: rgba(240, 160, 32, 0.1);
322 }
323
324 .finding-stat.high .number {
336 - color: #f0a020;
325 + color: #f0a020;
326 }
327
328 .finding-stat.medium {
340 - background: rgba(32, 128, 240, 0.1);
329 + background: rgba(32, 128, 240, 0.1);
330 }
331
332 .finding-stat.medium .number {
344 - color: #2080f0;
333 + color: #2080f0;
334 }
335
336 .finding-stat.low {
348 - background: rgba(24, 160, 88, 0.1);
337 + background: rgba(24, 160, 88, 0.1);
338 }
339
340 .finding-stat.low .number {
352 - color: #18a058;
341 + color: #18a058;
342 }
343
344 .text-success {
356 - color: var(--success-color);
345 + color: var(--success-color);
346 }
347
348 .text-warning {
360 - color: var(--warning-color);
349 + color: var(--warning-color);
350 }
351
352 .text-error {
364 - color: var(--error-color);
353 + color: var(--error-color);
354 }
355 </style>
frontend/src/components/githubAudit/GitHubAuditStats.vue
+13 -12
@@ -22,33 +22,34 @@
22 </template>
23
24 <script setup lang="ts">
25 +// TODO: refactor
26 import { NGi, NGrid, NStatistic } from "naive-ui"
27 import { computed } from "vue"
28
29 const props = defineProps<{
29 - stats: {
30 - totalConfigs: number
31 - activeConfigs: number
32 - totalReports: number
33 - avgScore: number
34 - }
30 + stats: {
31 + totalConfigs: number
32 + activeConfigs: number
33 + totalReports: number
34 + avgScore: number
35 + }
36 }>()
37
38 const scoreClass = computed(() => {
38 - if (props.stats.avgScore >= 80) return "text-success"
39 - if (props.stats.avgScore >= 60) return "text-warning"
40 - return "text-error"
39 + if (props.stats.avgScore >= 80) return "text-success"
40 + if (props.stats.avgScore >= 60) return "text-warning"
41 + return "text-error"
42 })
43 </script>
44
45 <style scoped>
46 .text-success {
46 - color: var(--success-color);
47 + color: var(--success-color);
48 }
49 .text-warning {
49 - color: var(--warning-color);
50 + color: var(--warning-color);
51 }
52 .text-error {
52 - color: var(--error-color);
53 + color: var(--error-color);
54 }
55 </style>
frontend/src/components/graylog/Alerts/Item.vue
+7 -4
@@ -29,7 +29,10 @@
29 </div>
30 <div class="box">
31 index_name:
32 - <code class="text-primary cursor-pointer" @click="gotoIndex(alertsEvent.index_name)">
32 + <code
33 + class="text-primary cursor-pointer"
34 + @click="routeIndex(alertsEvent.index_name).navigate()"
35 + >
36 {{ alertsEvent.index_name }}
37 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
38 </code>
@@ -82,9 +85,9 @@ import type { AlertsEventElement } from "@/types/graylog/alerts.d"
85 import { NPopover, NTimeline, NTimelineItem } from "naive-ui"
86 import CardEntity from "@/components/common/cards/CardEntity.vue"
87 import Icon from "@/components/common/Icon.vue"
85 -import { useGoto } from "@/composables/useGoto"
88 +import { useNavigation } from "@/composables/useNavigation"
89 import { useSettingsStore } from "@/stores/settings"
87 -import { formatDate } from "@/utils"
90 +import { formatDate } from "@/utils/format"
91
92 const { alertsEvent } = defineProps<{ alertsEvent: AlertsEventElement }>()
93
@@ -97,7 +100,7 @@ const TimeIcon = "carbon:time"
100 const LinkIcon = "carbon:launch"
101
102 const dFormats = useSettingsStore().dateFormat
100 -const { gotoIndex } = useGoto()
103 +const { routeIndex } = useNavigation()
104
105 function formatDateTime(timestamp: string): string {
106 return formatDate(timestamp, dFormats.datetimesec).toString()
frontend/src/components/graylog/Alerts/List.vue
+4 -1
@@ -76,6 +76,7 @@
76 </template>
77
78 <script setup lang="ts">
79 +// TODO: refactor
80 import type { AlertsEventElement, AlertsQuery } from "@/types/graylog/alerts.d"
81 import { useResizeObserver } from "@vueuse/core"
82 import { NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
@@ -187,11 +188,13 @@ function getData(page: number, pageSize: number, timerange: number) {
188
189 useResizeObserver(header, entries => {
190 const entry = entries[0]
191 + if (!entry) return
192 +
193 const { width } = entry.contentRect
194
195 if (width < 650) {
196 compactMode.value = true
194 - pageSize.value = pageSizes[0]
197 + pageSize.value = pageSizes[0] ?? 25
198 pageSlot.value = 5
199 } else {
200 compactMode.value = false
frontend/src/components/graylog/Inputs/Item.vue
+1 -1
@@ -121,7 +121,7 @@ import Badge from "@/components/common/Badge.vue"
121 import CardEntity from "@/components/common/cards/CardEntity.vue"
122 import Icon from "@/components/common/Icon.vue"
123 import { useSettingsStore } from "@/stores/settings"
124 -import { formatDate } from "@/utils"
124 +import { formatDate } from "@/utils/format"
125 import "@/assets/scss/overrides/vuesjv-override.scss"
126
127 const { input, embedded } = defineProps<{ input: InputExtended; embedded?: boolean }>()
frontend/src/components/graylog/Messages/Item.vue
+1 -1
@@ -10,7 +10,7 @@
10 import type { Message } from "@/types/graylog/messages.d"
11 import CardEntity from "@/components/common/cards/CardEntity.vue"
12 import { useSettingsStore } from "@/stores/settings"
13 -import { formatDate } from "@/utils"
13 +import { formatDate } from "@/utils/format"
14
15 const { message } = defineProps<{ message: Message }>()
16
frontend/src/components/graylog/Metrics/List.vue
+2 -1
@@ -32,6 +32,7 @@
32 </template>
33
34 <script setup lang="ts">
35 +// TODO: refactor
36 import type { ThroughputMetric } from "@/types/graylog/metrics.d"
37 import _groupBy from "lodash/groupBy"
38 import _map from "lodash/map"
@@ -75,7 +76,7 @@ function sanitizeMetrics(metrics: ThroughputMetric[]): Metrics[] {
76 }
77
78 const groupObj: Metrics = {
78 - groupName: group[0].name,
79 + groupName: group[0]?.name ?? "",
80 throughputMetrics: group
81 }
82 return groupObj
frontend/src/components/graylog/Metrics/UncommittedEntries.vue
+13 -6
@@ -13,7 +13,12 @@
13 </div>
14 </div>
15 <div class="footer">
16 - <n-button type="primary" ghost icon-placement="right" @click="gotoGraylogManagement('messages')">
16 + <n-button
17 + type="primary"
18 + ghost
19 + icon-placement="right"
20 + @click="routeGraylogManagement('messages').navigate()"
21 + >
22 <template #icon>
23 <Icon :name="LinkIcon" :size="14" />
24 </template>
@@ -29,7 +34,7 @@ import { NButton } from "naive-ui"
34 import { computed, ref, toRefs, watch } from "vue"
35 import apexchart from "vue3-apexcharts"
36 import Icon from "@/components/common/Icon.vue"
32 -import { useGoto } from "@/composables/useGoto"
37 +import { useNavigation } from "@/composables/useNavigation"
38 import { useHealthcheckStore } from "@/stores/healthcheck"
39 import { useThemeStore } from "@/stores/theme"
40 import dayjs from "@/utils/dayjs"
@@ -48,7 +53,7 @@ const DangerIcon = "majesticons:exclamation-line"
53
54 const style = computed(() => useThemeStore().style)
55 const isThemeDark = computed(() => useThemeStore().isThemeDark)
51 -const { gotoGraylogManagement } = useGoto()
56 +const { routeGraylogManagement } = useNavigation()
57
58 const isWarning = computed<boolean>(() => {
59 return value.value > UNCOMMITTED_JOURNAL_ENTRIES_THRESHOLD
@@ -61,7 +66,7 @@ const series = ref<{ name: string; data: [Date, number][] }[]>([
66 }
67 ])
68
64 -const serieLength = computed<number>(() => series.value[0].data.length || 0)
69 +const serieLength = computed<number>(() => series.value[0]?.data.length ?? 0)
70
71 function getOptions(): ApexOptions {
72 return {
@@ -118,13 +123,15 @@ const options = ref(getOptions())
123
124 watch(value, val => {
125 if (serieLength.value > 100) {
121 - series.value[0].data.shift()
126 + series.value[0]?.data.shift()
127
128 if (options.value.chart?.animations?.enabled) {
129 options.value = getOptions()
130 }
131 }
127 - series.value[0].data.push([new Date(), val])
132 + if (series.value[0]) {
133 + series.value[0].data.push([new Date(), val])
134 + }
135 })
136
137 watch(isThemeDark, () => {
frontend/src/components/graylog/MonitoringAlerts/Item.vue
+2 -1
@@ -76,6 +76,7 @@
76 </template>
77
78 <script setup lang="ts">
79 +// TODO: refactor
80 import type { FormRules, FormValidationError } from "naive-ui"
81 import type { ProvisionsMonitoringAlertParams } from "@/api/endpoints/monitoringAlerts"
82 import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
@@ -143,7 +144,7 @@ function validateForm(e: MouseEvent) {
144 provisionsMonitoringAlert()
145 } else {
146 for (const err of errors) {
146 - message.error(err[0].message || "Invalid fields")
147 + message.error(err[0]?.message ?? "Invalid fields")
148 }
149 }
150 })
frontend/src/components/graylog/MonitoringAlerts/List.vue
+2 -2
@@ -1,6 +1,6 @@
1 <template>
2 <div class="monitoring-alert-list">
3 - <div ref="header" class="header flex items-center justify-end gap-2">
3 + <div class="header flex items-center justify-end gap-2">
4 <div class="info flex grow gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -64,6 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 +// TODO: refactor
68 import type { EventDefinition } from "@/types/graylog/event-definition.d"
69 import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
70 import { NButton, NEmpty, NPagination, NPopover, NSpin, useMessage } from "naive-ui"
@@ -88,7 +89,6 @@ const currentPage = ref(1)
89 const simpleMode = ref(false)
90 const showSizePicker = ref(true)
91 const pageSizes = [10, 25, 50, 100]
91 -const header = ref()
92 const pageSlot = ref(8)
93
94 const InfoIcon = "carbon:information"
frontend/src/components/graylog/Pipelines/PipeDetails.vue
+4 -3
@@ -41,13 +41,14 @@
41 </template>
42
43 <script setup lang="ts">
44 +// TODO: refactor
45 import type { RuleExtended } from "./RulesSmallList.vue"
46 import type { PipelineFull, PipelineFullStage } from "@/types/graylog/pipelines.d"
47 import { NButton, NPopover, NScrollbar, NTimeline, NTimelineItem } from "naive-ui"
48 import { computed, toRefs } from "vue"
49 import Icon from "@/components/common/Icon.vue"
50 import { useSettingsStore } from "@/stores/settings"
50 -import { formatDate } from "@/utils"
51 +import { formatDate } from "@/utils/format"
52 import RulesSmallList from "./RulesSmallList.vue"
53
54 interface PipelineFullStageExt extends Omit<PipelineFullStage, "rules" | "rule_ids"> {
@@ -71,8 +72,8 @@ function sanitizeStage(stage: PipelineFullStage): PipelineFullStageExt {
72
73 for (const i in stage.rules) {
74 rules.push({
74 - title: stage.rules[i],
75 - id: stage.rule_ids[i]
75 + title: stage.rules[i] ?? "",
76 + id: stage.rule_ids[i] ?? ""
77 })
78 }
79
frontend/src/components/graylog/Pipelines/PipeInfo.vue
+1 -1
@@ -47,7 +47,7 @@ import type { Pipeline } from "@/types/graylog/pipelines.d"
47 import { NInput, NTabPane, NTabs } from "naive-ui"
48 import { toRefs } from "vue"
49 import { useSettingsStore } from "@/stores/settings"
50 -import { formatDate } from "@/utils"
50 +import { formatDate } from "@/utils/format"
51
52 const props = defineProps<{ pipeline?: Pipeline }>()
53 const { pipeline } = toRefs(props)
frontend/src/components/graylog/Pipelines/PipeList.vue
+2 -1
@@ -46,6 +46,7 @@
46 </template>
47
48 <script setup lang="ts">
49 +// TODO: refactor
50 import type { PipelineFull } from "@/types/graylog/pipelines.d"
51 import { NButton, NCard, NCollapse, NCollapseItem, NEmpty, NModal, NSpin, useMessage } from "naive-ui"
52 import { onBeforeMount, ref, watch } from "vue"
@@ -88,7 +89,7 @@ function getPipelines() {
89 if (res.data.success) {
90 pipelines.value = res.data.pipelines || []
91 if (pipelines.value.length && !selectedPipeline.value) {
91 - selectedPipeline.value = pipelines.value[0].id
92 + selectedPipeline.value = pipelines.value[0]?.id ?? null
93 }
94 } else {
95 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/graylog/Pipelines/Rule.vue
+1 -1
@@ -80,7 +80,7 @@ import { ref, toRefs } from "vue"
80 import CardEntity from "@/components/common/cards/CardEntity.vue"
81 import Icon from "@/components/common/Icon.vue"
82 import { useSettingsStore } from "@/stores/settings"
83 -import { formatDate } from "@/utils"
83 +import { formatDate } from "@/utils/format"
84
85 const props = defineProps<{ rule: PipelineRule; embedded?: boolean; highlight: boolean | null | undefined }>()
86 const { rule, highlight, embedded } = toRefs(props)
frontend/src/components/graylog/Streams/Item.vue
+1 -1
@@ -109,7 +109,7 @@ import Badge from "@/components/common/Badge.vue"
109 import CardEntity from "@/components/common/cards/CardEntity.vue"
110 import Icon from "@/components/common/Icon.vue"
111 import { useSettingsStore } from "@/stores/settings"
112 -import { formatDate } from "@/utils"
112 +import { formatDate } from "@/utils/format"
113 import "@/assets/scss/overrides/vuesjv-override.scss"
114
115 const props = defineProps<{ stream: Stream }>()
frontend/src/components/graylog/Streams/List.vue
+3
@@ -91,6 +91,7 @@
91 </template>
92
93 <script setup lang="ts">
94 +// TODO: refactor
95 import type { Stream } from "@/types/graylog/stream.d"
96 import { useResizeObserver } from "@vueuse/core"
97 import { NButton, NDivider, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
@@ -176,6 +177,8 @@ function getData() {
177
178 useResizeObserver(header, entries => {
179 const entry = entries[0]
180 + if (!entry) return
181 +
182 const { width } = entry.contentRect
183
184 pageSlot.value = width < 650 ? 5 : 8
frontend/src/components/healthcheck/HealthcheckList.vue
+3
@@ -90,6 +90,7 @@
90 </template>
91
92 <script setup lang="ts">
93 +// TODO: refactor
94 import type { InfluxDBAlert, InfluxDBAlertResponse } from "@/types/healthchecks.d"
95 import { useResizeObserver } from "@vueuse/core"
96 import _orderBy from "lodash/orderBy"
@@ -209,6 +210,8 @@ function getData() {
210
211 useResizeObserver(header, entries => {
212 const entry = entries[0]
213 + if (!entry) return
214 +
215 const { width } = entry.contentRect
216
217 pageSlot.value = width < 650 ? 5 : 8
frontend/src/components/incidentManagement/alerts/AlertAsset.vue
+56 -41
@@ -16,7 +16,7 @@
16 <div class="flex h-full items-center">
17 <code
18 class="text-primary cursor-pointer leading-none"
19 - @click.stop="gotoIndex(asset.index_name)"
19 + @click.stop="routeIndex(asset.index_name).navigate()"
20 >
21 {{ asset.index_name }}
22 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -31,7 +31,7 @@
31 <div class="flex h-full items-center">
32 <code
33 class="text-primary cursor-pointer leading-none"
34 - @click.stop="gotoAgent(asset.agent_id)"
34 + @click.stop="routeAgent(asset.agent_id).navigate()"
35 >
36 {{ asset.agent_id }}
37 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -135,7 +135,12 @@
135 </div>
136 </div>
137 </n-tab-pane>
138 - <n-tab-pane v-if="isWazuhSource" name="Artifact Collection" tab="Artifact Collection" display-directive="show:lazy">
138 + <n-tab-pane
139 + v-if="isWazuhSource"
140 + name="Artifact Collection"
141 + tab="Artifact Collection"
142 + display-directive="show:lazy"
143 + >
144 <div class="p-7 pt-2">
145 <ArtifactsCollect
146 :hostname="asset.asset_name"
@@ -146,12 +151,22 @@
151 />
152 </div>
153 </n-tab-pane>
149 - <n-tab-pane v-if="isWazuhSource" name="Alert Timeline" tab="Alert Timeline" display-directive="show:lazy">
154 + <n-tab-pane
155 + v-if="isWazuhSource"
156 + name="Alert Timeline"
157 + tab="Alert Timeline"
158 + display-directive="show:lazy"
159 + >
160 <div class="p-7 pt-2">
161 <AlertDetailTimeline :asset />
162 </div>
163 </n-tab-pane>
154 - <n-tab-pane v-if="isWazuhSource" name="File Collection" tab="File Collection" display-directive="show:lazy">
164 + <n-tab-pane
165 + v-if="isWazuhSource"
166 + name="File Collection"
167 + tab="File Collection"
168 + display-directive="show:lazy"
169 + >
170 <div class="p-7 pt-2">
171 <FileCollectionForm v-if="asset.agent_id" :agent-id="asset.agent_id" />
172
@@ -178,7 +193,7 @@ import Api from "@/api"
193 import Badge from "@/components/common/Badge.vue"
194 import CardEntity from "@/components/common/cards/CardEntity.vue"
195 import Icon from "@/components/common/Icon.vue"
181 -import { useGoto } from "@/composables/useGoto"
196 +import { useNavigation } from "@/composables/useNavigation"
197
198 const { asset, embedded, badge } = defineProps<{ asset: AlertAsset; embedded?: boolean; badge?: boolean }>()
199
@@ -187,27 +202,27 @@ const AlertDetailTimeline = defineAsyncComponent(() => import("./AlertDetailTime
202 // const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
203 const AIAnalystButton = defineAsyncComponent(() => import("@/components/threatIntel/AIAnalystButton.vue"))
204 const AIWazuhExclusionRuleButton = defineAsyncComponent(
190 - () => import("@/components/threatIntel/AIWazuhExclusionRuleButton.vue")
205 + () => import("@/components/threatIntel/AIWazuhExclusionRuleButton.vue")
206 )
207 const AIVelociraptorArtifactRecommendationButton = defineAsyncComponent(
193 - () => import("@/components/threatIntel/AIVelociraptorArtifactRecommendationButton.vue")
208 + () => import("@/components/threatIntel/AIVelociraptorArtifactRecommendationButton.vue")
209 )
210 const ThreatIntelProcessEvaluationProvider = defineAsyncComponent(
196 - () => import("@/components/threatIntel/ThreatIntelProcessEvaluationProvider.vue")
211 + () => import("@/components/threatIntel/ThreatIntelProcessEvaluationProvider.vue")
212 )
213 const ArtifactsCollect = defineAsyncComponent(() => import("@/components/artifacts/ArtifactsCollect.vue"))
214 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
215 const LicenseFeatureCheck = defineAsyncComponent(() => import("@/components/license/LicenseFeatureCheck.vue"))
216 const AgentDataStoreTabCompact = defineAsyncComponent(
202 - () => import("@/components/agents/dataStore/AgentDataStoreTabCompact.vue")
217 + () => import("@/components/agents/dataStore/AgentDataStoreTabCompact.vue")
218 )
219 const FileCollectionForm = defineAsyncComponent(
205 - () => import("@/components/agents/fileCollection/FileCollectionForm.vue")
220 + () => import("@/components/agents/fileCollection/FileCollectionForm.vue")
221 )
222
223 const ViewIcon = "iconoir:eye-solid"
224 const LinkIcon = "carbon:launch"
210 -const { gotoAgent, gotoIndex } = useGoto()
225 +const { routeAgent, routeIndex } = useNavigation()
226 const message = useMessage()
227 const loading = ref(false)
228 const showDetails = ref(false)
@@ -220,47 +235,47 @@ const licenseChecked = ref(false)
235 const licenseResponse = ref(false)
236
237 const isWazuhSource = computed(() => {
223 - return alertContext.value?.source?.toLowerCase() === 'wazuh'
238 + return alertContext.value?.source?.toLowerCase() === "wazuh"
239 })
240
241 watch(showDetails, val => {
227 - if (val && !alertContext.value) {
228 - getAlertContext(asset.alert_context_id)
229 - }
242 + if (val && !alertContext.value) {
243 + getAlertContext(asset.alert_context_id)
244 + }
245 })
246
247 function getAlertContext(alertContextId: number) {
233 - loading.value = true
248 + loading.value = true
249
235 - Api.incidentManagement.alerts
236 - .getAlertContext(alertContextId)
237 - .then(res => {
238 - if (res.data.success) {
239 - alertContext.value = res.data?.alert_context || null
240 - } else {
241 - message.warning(res.data?.message || "An error occurred. Please try again later.")
242 - }
243 - })
244 - .catch(err => {
245 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
246 - })
247 - .finally(() => {
248 - loading.value = false
249 - })
250 + Api.incidentManagement.alerts
251 + .getAlertContext(alertContextId)
252 + .then(res => {
253 + if (res.data.success) {
254 + alertContext.value = res.data?.alert_context || null
255 + } else {
256 + message.warning(res.data?.message || "An error occurred. Please try again later.")
257 + }
258 + })
259 + .catch(err => {
260 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
261 + })
262 + .finally(() => {
263 + loading.value = false
264 + })
265 }
266 </script>
267
268 <style lang="scss" scoped>
269 .alert-assets-badge {
255 - color: var(--primary-color);
256 - line-height: 1;
257 - cursor: pointer;
270 + color: var(--primary-color);
271 + line-height: 1;
272 + cursor: pointer;
273
259 - code {
260 - display: flex;
261 - align-items: center;
262 - gap: 7px;
263 - padding: 2px 5px;
264 - }
274 + code {
275 + display: flex;
276 + align-items: center;
277 + gap: 7px;
278 + padding: 2px 5px;
279 + }
280 }
281 </style>
frontend/src/components/incidentManagement/alerts/AlertAssetInfo.vue
+9 -6
@@ -7,19 +7,22 @@
7 <template #value>
8 <div v-if="key === 'id'">#{{ value }}</div>
9 <div v-else-if="key === 'customer_code'">
10 - <code class="text-primary cursor-pointer" @click.stop="gotoCustomer({ code: asset.customer_code })">
10 + <code
11 + class="text-primary cursor-pointer"
12 + @click.stop="routeCustomer({ code: asset.customer_code }).navigate()"
13 + >
14 #{{ asset.customer_code }}
15 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
16 </code>
17 </div>
18 <div v-else-if="key === 'agent_id'">
16 - <code class="text-primary cursor-pointer" @click.stop="gotoAgent(asset.agent_id)">
19 + <code class="text-primary cursor-pointer" @click.stop="routeAgent(asset.agent_id).navigate()">
20 {{ asset.agent_id }}
21 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
22 </code>
23 </div>
24 <div v-else-if="key === 'index_name'">
22 - <code class="text-primary cursor-pointer" @click.stop="gotoIndex(asset.index_name)">
25 + <code class="text-primary cursor-pointer" @click.stop="routeIndex(asset.index_name).navigate()">
26 {{ asset.index_name }}
27 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
28 </code>
@@ -58,7 +61,7 @@
61 <div v-if="key === '_index'">
62 <code
63 class="text-primary cursor-pointer"
61 - @click.stop="gotoIndex(alertDetailsInfo._index)"
64 + @click.stop="routeIndex(alertDetailsInfo._index).navigate()"
65 >
66 {{ alertDetailsInfo._index }}
67 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -89,7 +92,7 @@ import { computed, defineAsyncComponent, ref, toRefs, watch } from "vue"
92 import Api from "@/api"
93 import CardKV from "@/components/common/cards/CardKV.vue"
94 import Icon from "@/components/common/Icon.vue"
92 -import { useGoto } from "@/composables/useGoto"
95 +import { useNavigation } from "@/composables/useNavigation"
96
97 const props = defineProps<{ asset: AlertAsset }>()
98
@@ -99,7 +102,7 @@ const { asset } = toRefs(props)
102
103 const LinkIcon = "carbon:launch"
104 const ViewIcon = "iconoir:eye-solid"
102 -const { gotoAgent, gotoIndex, gotoCustomer } = useGoto()
105 +const { routeAgent, routeIndex, routeCustomer } = useNavigation()
106 const message = useMessage()
107 const loading = ref(false)
108 const showAlertDetails = ref(false)
frontend/src/components/incidentManagement/alerts/AlertComment.vue
+2 -1
@@ -83,7 +83,8 @@ import { defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
83 import Api from "@/api"
84 import Icon from "@/components/common/Icon.vue"
85 import { useSettingsStore } from "@/stores/settings"
86 -import { formatDate, getAvatar, getNameInitials } from "@/utils"
86 +import { getAvatar, getNameInitials } from "@/utils"
87 +import { formatDate } from "@/utils/format"
88
89 type Mode = "view" | "edit"
90
frontend/src/components/incidentManagement/alerts/AlertDetailTimeline.vue
+1 -1
@@ -21,7 +21,7 @@ import { NSpin, NTimeline, NTimelineItem, useMessage } from "naive-ui"
21 import { onBeforeMount, ref } from "vue"
22 import Api from "@/api"
23 import { useSettingsStore } from "@/stores/settings"
24 -import { formatDate } from "@/utils"
24 +import { formatDate } from "@/utils/format"
25 import AlertDetailTimelineItem from "./AlertDetailTimelineItem.vue"
26
27 const { asset } = defineProps<{ asset: AlertAsset }>()
frontend/src/components/incidentManagement/alerts/AlertDetailTimelineItem.vue
+3 -3
@@ -24,7 +24,7 @@
24 <div v-if="key === '_index'">
25 <code
26 class="text-primary cursor-pointer"
27 - @click.stop="gotoIndex(timelineDetailsInfo._index)"
27 + @click.stop="routeIndex(timelineDetailsInfo._index).navigate()"
28 >
29 {{ timelineDetailsInfo._index }}
30 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -55,7 +55,7 @@ import { computed, defineAsyncComponent, ref, toRefs } from "vue"
55 import CardEntity from "@/components/common/cards/CardEntity.vue"
56 import CardKV from "@/components/common/cards/CardKV.vue"
57 import Icon from "@/components/common/Icon.vue"
58 -import { useGoto } from "@/composables/useGoto"
58 +import { useNavigation } from "@/composables/useNavigation"
59
60 const props = defineProps<{ timelineData: AlertTimeline; embedded?: boolean }>()
61
@@ -64,7 +64,7 @@ const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSo
64 const { timelineData, embedded } = toRefs(props)
65
66 const LinkIcon = "carbon:launch"
67 -const { gotoIndex } = useGoto()
67 +const { routeIndex } = useNavigation()
68 const showDetails = ref(false)
69 const timelineDetailsInfo = computed(() => _omit(timelineData.value, ["_source"]))
70 const timelineDetailsSource = computed(() => timelineData.value?._source)
frontend/src/components/incidentManagement/alerts/AlertItem.vue
+4 -4
@@ -152,7 +152,7 @@
152 <div class="flex h-full items-center">
153 <code
154 class="text-primary cursor-pointer leading-none"
155 - @click.stop="gotoCustomer({ code: alert.customer_code })"
155 + @click.stop="routeCustomer({ code: alert.customer_code }).navigate()"
156 >
157 #{{ alert.customer_code }}
158 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -279,9 +279,9 @@ import Api from "@/api"
279 import Badge from "@/components/common/Badge.vue"
280 import CardEntity from "@/components/common/cards/CardEntity.vue"
281 import Icon from "@/components/common/Icon.vue"
282 -import { useGoto } from "@/composables/useGoto"
282 +import { useNavigation } from "@/composables/useNavigation"
283 import { useSettingsStore } from "@/stores/settings"
284 -import { formatDate } from "@/utils"
284 +import { formatDate } from "@/utils/format"
285 import AssigneeIcon from "../common/AssigneeIcon.vue"
286 import StatusIcon from "../common/StatusIcon.vue"
287 import AlertAssignUser from "./AlertAssignUser.vue"
@@ -321,7 +321,7 @@ const CommentsIcon = "carbon:chat"
321 const AssetsIcon = "carbon:document-security"
322 const IoCsIcon = "carbon:ibm-watson-discovery"
323
324 -const { gotoCustomer } = useGoto()
324 +const { routeCustomer } = useNavigation()
325 const dialog = useDialog()
326 const message = useMessage()
327 const loading = ref(false)
frontend/src/components/incidentManagement/alerts/AlertLinkedCases.vue
+3 -3
@@ -11,7 +11,7 @@
11 :case-data="{ ...linkedCase, alerts: alert ? [alert] : [] }"
12 compact
13 embedded
14 - @click="gotoIncidentManagementCases(linkedCase.id)"
14 + @click="routeIncidentManagementCases(linkedCase.id).navigate()"
15 />
16 <div class="flex items-center justify-end gap-3">
17 <n-button
@@ -37,7 +37,7 @@ import { NButton, NPopover, useMessage } from "naive-ui"
37 import { computed, defineAsyncComponent, ref, watch } from "vue"
38 import Api from "@/api"
39 import Icon from "@/components/common/Icon.vue"
40 -import { useGoto } from "@/composables/useGoto"
40 +import { useNavigation } from "@/composables/useNavigation"
41
42 const props = defineProps<{ alert: Alert }>()
43
@@ -53,7 +53,7 @@ const MenuIcon = "carbon:overflow-menu-horizontal"
53 const loadingId = ref<number | false>(false)
54 const alert = ref<Alert>(props.alert)
55 const message = useMessage()
56 -const { gotoIncidentManagementCases } = useGoto()
56 +const { routeIncidentManagementCases } = useNavigation()
57 const linkedCases = computed(() => alert.value?.linked_cases || [])
58
59 function updateAlert(updatedAlert: Alert) {
frontend/src/components/incidentManagement/alerts/AlertOverview.vue
+3 -3
@@ -107,7 +107,7 @@
107 <template #value>
108 <code
109 class="text-primary cursor-pointer"
110 - @click="gotoCustomer({ code: alert.customer_code })"
110 + @click="routeCustomer({ code: alert.customer_code }).navigate()"
111 >
112 {{ alert.customer_code }}
113 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -167,7 +167,7 @@ import { NButton, NSpin, useDialog, useMessage } from "naive-ui"
167 import { computed, defineAsyncComponent, ref, toRefs } from "vue"
168 import CardKV from "@/components/common/cards/CardKV.vue"
169 import Icon from "@/components/common/Icon.vue"
170 -import { useGoto } from "@/composables/useGoto"
170 +import { useNavigation } from "@/composables/useNavigation"
171 import AssigneeIcon from "../common/AssigneeIcon.vue"
172 import StatusIcon from "../common/StatusIcon.vue"
173 import AlertAssignUser from "./AlertAssignUser.vue"
@@ -190,7 +190,7 @@ const TrashIcon = "carbon:trash-can"
190 const LinkIcon = "carbon:launch"
191 const EditIcon = "uil:edit-alt"
192
193 -const { gotoCustomer } = useGoto()
193 +const { routeCustomer } = useNavigation()
194 const dialog = useDialog()
195 const message = useMessage()
196 const loading = ref(false)
frontend/src/components/incidentManagement/alerts/AlertTimeline.vue
+1 -1
@@ -9,7 +9,7 @@
9 import type { Alert } from "@/types/incidentManagement/alerts.d"
10 import { NTimeline, NTimelineItem } from "naive-ui"
11 import { useSettingsStore } from "@/stores/settings"
12 -import { formatDate } from "@/utils"
12 +import { formatDate } from "@/utils/format"
13
14 const { alert } = defineProps<{ alert: Alert }>()
15
frontend/src/components/incidentManagement/alerts/AlertsFilters.vue
+2 -1
@@ -153,6 +153,7 @@
153 </template>
154
155 <script setup lang="ts">
156 +// TODO: refactor
157 import type { Ref } from "vue"
158 import type { AlertsListFilter } from "./types.d"
159 import type { AlertsFilterTypes, AlertsListFilterValue } from "@/api/endpoints/incidentManagement/alerts"
@@ -240,7 +241,7 @@ function setFilter(newFilters: AlertsListFilter[]) {
241 const filterIndex = filters.value.findIndex(o => o.type === newFilter.type)
242
243 if (filterIndex !== -1) {
243 - if (newFilter.value) {
244 + if (newFilter.value && filters.value[filterIndex]) {
245 filters.value[filterIndex].value = newFilter.value
246 } else {
247 delFilter(newFilter.type)
frontend/src/components/incidentManagement/alerts/AlertsList.vue
+223 -220
@@ -258,6 +258,7 @@
258 </template>
259
260 <script setup lang="ts">
261 +// TODO: refactor
262 import type { AlertsListFilter } from "./types.d"
263 import type { AlertsQuery } from "@/api/endpoints/incidentManagement/alerts"
264 import type { Alert } from "@/types/incidentManagement/alerts.d"
@@ -266,21 +267,21 @@ import { useResizeObserver, useStorage } from "@vueuse/core"
267 import axios from "axios"
268 import _orderBy from "lodash/orderBy"
269 import {
269 - NAlert,
270 - NBadge,
271 - NButton,
272 - NCard,
273 - NCollapseTransition,
274 - NEmpty,
275 - NInput,
276 - NModal,
277 - NPagination,
278 - NPopconfirm,
279 - NPopover,
280 - NScrollbar,
281 - NSelect,
282 - NSpin,
283 - useMessage
270 + NAlert,
271 + NBadge,
272 + NButton,
273 + NCard,
274 + NCollapseTransition,
275 + NEmpty,
276 + NInput,
277 + NModal,
278 + NPagination,
279 + NPopconfirm,
280 + NPopover,
281 + NScrollbar,
282 + NSelect,
283 + NSpin,
284 + useMessage
285 } from "naive-ui"
286 import { computed, defineAsyncComponent, nextTick, onBeforeMount, provide, ref, watch } from "vue"
287 import Api from "@/api"
@@ -290,13 +291,13 @@ import AlertItem from "./AlertItem.vue"
291 import AlertsFilters from "./AlertsFilters.vue"
292
293 const {
293 - highlight,
294 - preset,
295 - showFilters = true
294 + highlight,
295 + preset,
296 + showFilters = true
297 } = defineProps<{
297 - highlight?: string | null
298 - preset?: AlertsListFilter[]
299 - showFilters?: boolean
298 + highlight?: string | null
299 + preset?: AlertsListFilter[]
300 + showFilters?: boolean
301 }>()
302
303 const AlertMergeCaseButton = defineAsyncComponent(() => import("./AlertMergeCaseButton.vue"))
@@ -328,8 +329,8 @@ const header = ref()
329 const pageSlot = ref(8)
330 const sort = defineModel<"asc" | "desc">("sort", { default: "desc" })
331 const sortOptions = [
331 - { label: "Desc", value: "desc" },
332 - { label: "Asc", value: "asc" }
332 + { label: "Desc", value: "desc" },
333 + { label: "Asc", value: "asc" }
334 ]
335
336 const totalFiltered = ref(0)
@@ -342,43 +343,43 @@ const filtersCTX = ref<{ setFilter: (payload: AlertsListFilter[]) => void } | nu
343 const filters = ref<AlertsListFilter[]>([])
344
345 const filtered = computed<boolean>(() => {
345 - return !!filters.value.length
346 + return !!filters.value.length
347 })
348
349 const highlightedItemFound = ref(!highlight)
350 const highlightedItemOpened = ref(!highlight)
351
352 watch([currentPage, sort], () => {
352 - getData()
353 + getData()
354 })
355
356 watch(pageSize, () => {
356 - if (currentPage.value === 1) {
357 - getData()
358 - } else {
359 - currentPage.value = 1
360 - }
357 + if (currentPage.value === 1) {
358 + getData()
359 + } else {
360 + currentPage.value = 1
361 + }
362 })
363
364 watch(
364 - alertsList,
365 - () => {
366 - if (
367 - alertsList.value.length &&
368 - !alertsList.value.find(o => o.id.toString() === highlight) &&
369 - currentPage.value < totalFiltered.value &&
370 - !highlightedItemFound.value
371 - ) {
372 - nextTick(() => {
373 - currentPage.value++
374 - })
375 - }
376 -
377 - if (alertsList.value.find(o => o.id.toString() === highlight)) {
378 - highlightedItemFound.value = true
379 - }
380 - },
381 - { immediate: true }
365 + alertsList,
366 + () => {
367 + if (
368 + alertsList.value.length &&
369 + !alertsList.value.find(o => o.id.toString() === highlight) &&
370 + currentPage.value < totalFiltered.value &&
371 + !highlightedItemFound.value
372 + ) {
373 + nextTick(() => {
374 + currentPage.value++
375 + })
376 + }
377 +
378 + if (alertsList.value.find(o => o.id.toString() === highlight)) {
379 + highlightedItemFound.value = true
380 + }
381 + },
382 + { immediate: true }
383 )
384
385 provide("assignable-users", availableUsers)
@@ -386,223 +387,225 @@ provide("assignable-users", availableUsers)
387 provide("linkable-cases", linkableCases)
388
389 function applyFilters(newFilters: AlertsListFilter[]) {
389 - filters.value = newFilters
390 - getData()
390 + filters.value = newFilters
391 + getData()
392 }
393
394 function updateAlert(updatedAlert: Alert) {
394 - const alertIndex = alertsList.value.findIndex(o => o.id === updatedAlert.id)
395 - if (alertIndex !== -1) {
396 - alertsList.value[alertIndex] = updatedAlert
397 - }
395 + const alertIndex = alertsList.value.findIndex(o => o.id === updatedAlert.id)
396 + if (alertIndex !== -1) {
397 + alertsList.value[alertIndex] = updatedAlert
398 + }
399 }
400
401 function isChecked(alert: Alert) {
401 - return !!checkedAlerts.value.find(o => o.id === alert.id)
402 + return !!checkedAlerts.value.find(o => o.id === alert.id)
403 }
404
405 function toggleCheck(alert: Alert) {
405 - const alertIndex = checkedAlerts.value.findIndex(o => o.id === alert.id)
406 + const alertIndex = checkedAlerts.value.findIndex(o => o.id === alert.id)
407
407 - if (checkedAlerts.value.find(o => o.id === alert.id)) {
408 - checkedAlerts.value.splice(alertIndex, 1)
409 - } else {
410 - checkedAlerts.value.push(alert)
411 - }
408 + if (checkedAlerts.value.find(o => o.id === alert.id)) {
409 + checkedAlerts.value.splice(alertIndex, 1)
410 + } else {
411 + checkedAlerts.value.push(alert)
412 + }
413 }
414
415 function resetChecked() {
415 - checkedAlerts.value = []
416 + checkedAlerts.value = []
417 }
418
419 function setALlChecked() {
419 - for (const alert of alertsList.value) {
420 - if (!isChecked(alert)) {
421 - toggleCheck(alert)
422 - }
423 - }
420 + for (const alert of alertsList.value) {
421 + if (!isChecked(alert)) {
422 + toggleCheck(alert)
423 + }
424 + }
425 }
426
427 function removeChecked(alert: Alert) {
427 - const alertIndex = checkedAlerts.value.findIndex(o => o.id === alert.id)
428 + const alertIndex = checkedAlerts.value.findIndex(o => o.id === alert.id)
429
429 - if (checkedAlerts.value.find(o => o.id === alert.id)) {
430 - checkedAlerts.value.splice(alertIndex, 1)
431 - }
430 + if (checkedAlerts.value.find(o => o.id === alert.id)) {
431 + checkedAlerts.value.splice(alertIndex, 1)
432 + }
433 }
434
435 function deleted(alert: Alert) {
435 - removeChecked(alert)
436 - getData()
436 + removeChecked(alert)
437 + getData()
438 }
439
440 function getData() {
440 - abortController?.abort()
441 - abortController = new AbortController()
442 -
443 - loading.value = true
444 -
445 - const query: Partial<AlertsQuery> = {
446 - page: currentPage.value,
447 - pageSize: pageSize.value,
448 - sort: sort.value
449 - }
450 -
451 - if (filtered.value) {
452 - query.filters = filters.value
453 - }
454 -
455 - Api.incidentManagement.alerts
456 - .getAlertsList(query, abortController.signal)
457 - .then(res => {
458 - if (res.data.success) {
459 - alertsList.value = res.data?.alerts || []
460 - total.value = res.data.total || 0
461 - totalFiltered.value = res.data.total_filtered ?? total.value ?? 0
462 - statusCloseTotal.value = res.data.closed || 0
463 - statusInProgressTotal.value = res.data.in_progress || 0
464 - statusOpenTotal.value = res.data.open || 0
465 - } else {
466 - message.warning(res.data?.message || "An error occurred. Please try again later.")
467 - }
468 - loading.value = false
469 - })
470 - .catch(err => {
471 - if (!axios.isCancel(err)) {
472 - alertsList.value = []
473 -
474 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
475 - loading.value = false
476 - }
477 - })
441 + abortController?.abort()
442 + abortController = new AbortController()
443 +
444 + loading.value = true
445 +
446 + const query: Partial<AlertsQuery> = {
447 + page: currentPage.value,
448 + pageSize: pageSize.value,
449 + sort: sort.value
450 + }
451 +
452 + if (filtered.value) {
453 + query.filters = filters.value
454 + }
455 +
456 + Api.incidentManagement.alerts
457 + .getAlertsList(query, abortController.signal)
458 + .then(res => {
459 + if (res.data.success) {
460 + alertsList.value = res.data?.alerts || []
461 + total.value = res.data.total || 0
462 + totalFiltered.value = res.data.total_filtered ?? total.value ?? 0
463 + statusCloseTotal.value = res.data.closed || 0
464 + statusInProgressTotal.value = res.data.in_progress || 0
465 + statusOpenTotal.value = res.data.open || 0
466 + } else {
467 + message.warning(res.data?.message || "An error occurred. Please try again later.")
468 + }
469 + loading.value = false
470 + })
471 + .catch(err => {
472 + if (!axios.isCancel(err)) {
473 + alertsList.value = []
474 +
475 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
476 + loading.value = false
477 + }
478 + })
479 }
480
481 function getAvailableUsers() {
481 - Api.incidentManagement.alerts
482 - .getAvailableUsers()
483 - .then(res => {
484 - if (res.data.success) {
485 - availableUsers.value = res.data?.available_users || []
486 - } else {
487 - message.warning(res.data?.message || "An error occurred. Please try again later.")
488 - }
489 - })
490 - .catch(err => {
491 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
492 - })
482 + Api.incidentManagement.alerts
483 + .getAvailableUsers()
484 + .then(res => {
485 + if (res.data.success) {
486 + availableUsers.value = res.data?.available_users || []
487 + } else {
488 + message.warning(res.data?.message || "An error occurred. Please try again later.")
489 + }
490 + })
491 + .catch(err => {
492 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
493 + })
494 }
495
496 function getCases() {
496 - Api.incidentManagement.cases
497 - .getCasesList()
498 - .then(res => {
499 - if (res.data.success) {
500 - linkableCases.value = _orderBy(res.data?.cases || [], ["id"], ["desc"])
501 - } else {
502 - message.warning(res.data?.message || "An error occurred. Please try again later.")
503 - }
504 - })
505 - .catch(err => {
506 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
507 - })
497 + Api.incidentManagement.cases
498 + .getCasesList()
499 + .then(res => {
500 + if (res.data.success) {
501 + linkableCases.value = _orderBy(res.data?.cases || [], ["id"], ["desc"])
502 + } else {
503 + message.warning(res.data?.message || "An error occurred. Please try again later.")
504 + }
505 + })
506 + .catch(err => {
507 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
508 + })
509 }
510
511 function deleteAlerts() {
511 - deleting.value = true
512 -
513 - Api.incidentManagement.alerts
514 - .deleteAlerts(checkedAlerts.value.map(o => o.id))
515 - .then(res => {
516 - if (res.data.success) {
517 - if (res.data.deleted_alert_ids.length) {
518 - for (const id of res.data.deleted_alert_ids) {
519 - toggleCheck({ id } as Alert)
520 - }
521 -
522 - if (res.data.not_deleted_alert_ids.length) {
523 - message.warning("Some alerts could not be deleted.")
524 - } else {
525 - message.success(res.data?.message || "Alerts deleted successfully.")
526 - }
527 - } else {
528 - message.warning("The selected alerts could not be deleted")
529 - }
530 -
531 - getData()
532 - } else {
533 - message.warning(res.data?.message || "An error occurred. Please try again later.")
534 - }
535 - })
536 - .catch(err => {
537 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
538 - })
539 - .finally(() => {
540 - deleting.value = false
541 - })
512 + deleting.value = true
513 +
514 + Api.incidentManagement.alerts
515 + .deleteAlerts(checkedAlerts.value.map(o => o.id))
516 + .then(res => {
517 + if (res.data.success) {
518 + if (res.data.deleted_alert_ids.length) {
519 + for (const id of res.data.deleted_alert_ids) {
520 + toggleCheck({ id } as Alert)
521 + }
522 +
523 + if (res.data.not_deleted_alert_ids.length) {
524 + message.warning("Some alerts could not be deleted.")
525 + } else {
526 + message.success(res.data?.message || "Alerts deleted successfully.")
527 + }
528 + } else {
529 + message.warning("The selected alerts could not be deleted")
530 + }
531 +
532 + getData()
533 + } else {
534 + message.warning(res.data?.message || "An error occurred. Please try again later.")
535 + }
536 + })
537 + .catch(err => {
538 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
539 + })
540 + .finally(() => {
541 + deleting.value = false
542 + })
543 }
544
545 function deleteAlertsByTitle() {
545 - if (!titleFilterInput.value) return
546 -
547 - deletingByTitle.value = true
548 -
549 - Api.incidentManagement.alerts
550 - .deleteAlertsByTitle(titleFilterInput.value)
551 - .then(res => {
552 - if (res.data.success) {
553 - const deletedCount = res.data.deleted_alert_ids.length
554 - const skippedCount = res.data.not_deleted_alert_ids.length
555 -
556 - if (deletedCount > 0) {
557 - if (skippedCount > 0) {
558 - message.warning(
559 - `Deleted ${deletedCount} alert(s). ${skippedCount} alert(s) were skipped (linked to cases).`
560 - )
561 - } else {
562 - message.success(`Successfully deleted ${deletedCount} alert(s).`)
563 - }
564 -
565 - // Clear any checked alerts that were deleted
566 - checkedAlerts.value = checkedAlerts.value.filter(
567 - alert => !res.data.deleted_alert_ids.includes(alert.id)
568 - )
569 -
570 - // Refresh the list
571 - getData()
572 - } else {
573 - message.info("No alerts were deleted. They may be linked to cases or not found.")
574 - }
575 -
576 - // Close modal and reset input
577 - showDeleteByTitleModal.value = false
578 - titleFilterInput.value = ""
579 - } else {
580 - message.warning(res.data?.message || "An error occurred. Please try again later.")
581 - }
582 - })
583 - .catch(err => {
584 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
585 - })
586 - .finally(() => {
587 - deletingByTitle.value = false
588 - })
546 + if (!titleFilterInput.value) return
547 +
548 + deletingByTitle.value = true
549 +
550 + Api.incidentManagement.alerts
551 + .deleteAlertsByTitle(titleFilterInput.value)
552 + .then(res => {
553 + if (res.data.success) {
554 + const deletedCount = res.data.deleted_alert_ids.length
555 + const skippedCount = res.data.not_deleted_alert_ids.length
556 +
557 + if (deletedCount > 0) {
558 + if (skippedCount > 0) {
559 + message.warning(
560 + `Deleted ${deletedCount} alert(s). ${skippedCount} alert(s) were skipped (linked to cases).`
561 + )
562 + } else {
563 + message.success(`Successfully deleted ${deletedCount} alert(s).`)
564 + }
565 +
566 + // Clear any checked alerts that were deleted
567 + checkedAlerts.value = checkedAlerts.value.filter(
568 + alert => !res.data.deleted_alert_ids.includes(alert.id)
569 + )
570 +
571 + // Refresh the list
572 + getData()
573 + } else {
574 + message.info("No alerts were deleted. They may be linked to cases or not found.")
575 + }
576 +
577 + // Close modal and reset input
578 + showDeleteByTitleModal.value = false
579 + titleFilterInput.value = ""
580 + } else {
581 + message.warning(res.data?.message || "An error occurred. Please try again later.")
582 + }
583 + })
584 + .catch(err => {
585 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
586 + })
587 + .finally(() => {
588 + deletingByTitle.value = false
589 + })
590 }
591
592 useResizeObserver(header, entries => {
592 - const entry = entries[0]
593 - const { width } = entry.contentRect
593 + const entry = entries[0]
594 + if (!entry) return
595
595 - pageSlot.value = width < 700 ? 5 : 8
596 - simpleMode.value = width < 550
596 + const { width } = entry.contentRect
597 +
598 + pageSlot.value = width < 700 ? 5 : 8
599 + simpleMode.value = width < 550
600 })
601
602 onBeforeMount(() => {
600 - if (!showFilters && preset?.length) {
601 - filters.value = preset
602 - }
603 + if (!showFilters && preset?.length) {
604 + filters.value = preset
605 + }
606
604 - getData()
605 - getAvailableUsers()
606 - getCases()
607 + getData()
608 + getAvailableUsers()
609 + getCases()
610 })
611 </script>
frontend/src/components/incidentManagement/cases/CaseComment.vue
+2 -1
@@ -83,7 +83,8 @@ import { defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
83 import Api from "@/api"
84 import Icon from "@/components/common/Icon.vue"
85 import { useSettingsStore } from "@/stores/settings"
86 -import { formatDate, getAvatar, getNameInitials } from "@/utils"
86 +import { getAvatar, getNameInitials } from "@/utils"
87 +import { formatDate } from "@/utils/format"
88
89 type Mode = "view" | "edit"
90
frontend/src/components/incidentManagement/cases/CaseDataStoreItem.vue
+1 -1
@@ -77,7 +77,7 @@ import Badge from "@/components/common/Badge.vue"
77 import CardEntity from "@/components/common/cards/CardEntity.vue"
78 import Icon from "@/components/common/Icon.vue"
79 import { useSettingsStore } from "@/stores/settings"
80 -import { formatDate } from "@/utils"
80 +import { formatDate } from "@/utils/format"
81
82 const { dataStoreFile, embedded } = defineProps<{
83 dataStoreFile: CaseDataStore
frontend/src/components/incidentManagement/cases/CaseItem.vue
+4 -4
@@ -138,7 +138,7 @@
138 <div class="flex h-full items-center">
139 <code
140 class="text-primary cursor-pointer leading-none"
141 - @click.stop="gotoCustomer({ code: caseEntity.customer_code })"
141 + @click.stop="routeCustomer({ code: caseEntity.customer_code }).navigate()"
142 >
143 #{{ caseEntity.customer_code }}
144 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -232,9 +232,9 @@ import Api from "@/api"
232 import Badge from "@/components/common/Badge.vue"
233 import CardEntity from "@/components/common/cards/CardEntity.vue"
234 import Icon from "@/components/common/Icon.vue"
235 -import { useGoto } from "@/composables/useGoto"
235 +import { useNavigation } from "@/composables/useNavigation"
236 import { useSettingsStore } from "@/stores/settings"
237 -import { formatDate } from "@/utils"
237 +import { formatDate } from "@/utils/format"
238 import AlertItem from "../alerts/AlertItem.vue"
239 import AssigneeIcon from "../common/AssigneeIcon.vue"
240 import StatusIcon from "../common/StatusIcon.vue"
@@ -265,7 +265,7 @@ const LinkIcon = "carbon:launch"
265 const InfoIcon = "carbon:information"
266 const EditIcon = "uil:edit-alt"
267
268 -const { gotoCustomer } = useGoto()
268 +const { routeCustomer } = useNavigation()
269 const dialog = useDialog()
270 const message = useMessage()
271 const dFormats = useSettingsStore().dateFormat
frontend/src/components/incidentManagement/cases/CaseOverview.vue
+4 -4
@@ -120,7 +120,7 @@
120 <code
121 v-if="caseData.customer_code"
122 class="text-primary cursor-pointer"
123 - @click="gotoCustomer({ code: caseData.customer_code })"
123 + @click="routeCustomer({ code: caseData.customer_code }).navigate()"
124 >
125 {{ caseData.customer_code }}
126 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -150,9 +150,9 @@ import { NButton, NSpin, useDialog, useMessage } from "naive-ui"
150 import { ref, toRefs } from "vue"
151 import CardKV from "@/components/common/cards/CardKV.vue"
152 import Icon from "@/components/common/Icon.vue"
153 -import { useGoto } from "@/composables/useGoto"
153 +import { useNavigation } from "@/composables/useNavigation"
154 import { useSettingsStore } from "@/stores/settings"
155 -import { formatDate } from "@/utils"
155 +import { formatDate } from "@/utils/format"
156 import AssigneeIcon from "../common/AssigneeIcon.vue"
157 import StatusIcon from "../common/StatusIcon.vue"
158 import CaseAssignUser from "./CaseAssignUser.vue"
@@ -172,7 +172,7 @@ const TrashIcon = "carbon:trash-can"
172 const LinkIcon = "carbon:launch"
173 const EditIcon = "uil:edit-alt"
174
175 -const { gotoCustomer } = useGoto()
175 +const { routeCustomer } = useNavigation()
176 const dialog = useDialog()
177 const message = useMessage()
178 const dFormats = useSettingsStore().dateFormat
frontend/src/components/incidentManagement/cases/CaseReportButton.vue
+1 -1
@@ -73,7 +73,7 @@ import { computed, ref } from "vue"
73 import Api from "@/api"
74 import Icon from "@/components/common/Icon.vue"
75 import { useSettingsStore } from "@/stores/settings"
76 -import { formatDate } from "@/utils"
76 +import { formatDate } from "@/utils/format"
77 import CaseReportTemplateSelect from "./CaseReportTemplateSelect.vue"
78
79 const { size, caseId } = defineProps<{ size?: Size; caseId: number }>()
frontend/src/components/incidentManagement/cases/CasesExport.vue
+1 -1
@@ -21,7 +21,7 @@ import { computed, h, inject, ref } from "vue"
21 import Api from "@/api"
22 import Icon from "@/components/common/Icon.vue"
23 import { useSettingsStore } from "@/stores/settings"
24 -import { formatDate } from "@/utils"
24 +import { formatDate } from "@/utils/format"
25
26 const { size, showIcon } = defineProps<{ size?: Size; showIcon?: boolean }>()
27
frontend/src/components/incidentManagement/cases/CasesList.vue
+3
@@ -179,6 +179,7 @@
179 </template>
180
181 <script setup lang="ts">
182 +// TODO: refactor
183 import type { CasesFilter, CasesFilterTypes } from "@/api/endpoints/incidentManagement/cases"
184 import type { Customer } from "@/types/customers.d"
185 import type { Case, CaseStatus } from "@/types/incidentManagement/cases.d"
@@ -410,6 +411,8 @@ function getCustomers() {
411
412 useResizeObserver(header, entries => {
413 const entry = entries[0]
414 + if (!entry) return
415 +
416 const { width } = entry.contentRect
417
418 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/incidentManagement/exclusionRules/ExclusionRuleDetails.vue
+4 -4
@@ -68,7 +68,7 @@
68 <template #value>
69 <code
70 class="text-primary cursor-pointer leading-none"
71 - @click.stop="gotoCustomer({ code: entity.customer_code })"
71 + @click.stop="routeCustomer({ code: entity.customer_code }).navigate()"
72 >
73 #{{ entity.customer_code }}
74 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -106,9 +106,9 @@ import Badge from "@/components/common/Badge.vue"
106 import CardKV from "@/components/common/cards/CardKV.vue"
107 import CodeSource from "@/components/common/CodeSource.vue"
108 import Icon from "@/components/common/Icon.vue"
109 -import { useGoto } from "@/composables/useGoto"
109 +import { useNavigation } from "@/composables/useNavigation"
110 import { useSettingsStore } from "@/stores/settings"
111 -import { formatDate } from "@/utils"
111 +import { formatDate } from "@/utils/format"
112 import ExclusionRuleStatusToggler from "./ExclusionRuleStatusToggler.vue"
113
114 const props = defineProps<{
@@ -121,7 +121,7 @@ const TimeIcon = "carbon:time"
121 const LinkIcon = "carbon:launch"
122 const TargetIcon = "zondicons:target"
123 const dFormats = useSettingsStore().dateFormat
124 -const { gotoCustomer } = useGoto()
124 +const { routeCustomer } = useNavigation()
125 const updatingStatus = ref(false)
126 const loading = computed(() => updatingStatus.value)
127 const properties = computed(() => _pick(entity.value, ["description", "channel", "title"]))
frontend/src/components/incidentManagement/exclusionRules/ExclusionRuleItem.vue
+4 -4
@@ -39,7 +39,7 @@
39 <template #value>
40 <code
41 class="text-primary cursor-pointer leading-none"
42 - @click.stop="gotoCustomer({ code: entity.customer_code })"
42 + @click.stop="routeCustomer({ code: entity.customer_code }).navigate()"
43 >
44 #{{ entity.customer_code }}
45 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
@@ -121,9 +121,9 @@ import Api from "@/api"
121 import Badge from "@/components/common/Badge.vue"
122 import CardEntity from "@/components/common/cards/CardEntity.vue"
123 import Icon from "@/components/common/Icon.vue"
124 -import { useGoto } from "@/composables/useGoto"
124 +import { useNavigation } from "@/composables/useNavigation"
125 import { useSettingsStore } from "@/stores/settings"
126 -import { formatDate } from "@/utils"
126 +import { formatDate } from "@/utils/format"
127 import ExclusionRuleDetails from "./ExclusionRuleDetails.vue"
128 import ExclusionRuleForm from "./ExclusionRuleForm.vue"
129 import ExclusionRuleStatusToggler from "./ExclusionRuleStatusToggler.vue"
@@ -154,7 +154,7 @@ const loadingDelete = ref(false)
154 const loading = computed(() => updatingStatus.value || loadingDelete.value)
155 const editing = ref(false)
156 const showDetails = ref(false)
157 -const { gotoCustomer } = useGoto()
157 +const { routeCustomer } = useNavigation()
158 const dFormats = useSettingsStore().dateFormat
159
160 function openDetails() {
frontend/src/components/incidentManagement/exclusionRules/ExclusionRulesList.vue
+5 -2
@@ -93,6 +93,7 @@
93 </template>
94
95 <script setup lang="ts">
96 +// TODO: refactor
97 import type { ExclusionRulesQuery } from "@/api/endpoints/incidentManagement/exclusionRules"
98 import type { ExclusionRule } from "@/types/incidentManagement/exclusionRules.d"
99 import { useResizeObserver } from "@vueuse/core"
@@ -139,8 +140,8 @@ function getData() {
140
141 const query: Partial<ExclusionRulesQuery> = {
142 pagination: {
142 - limit: pageSize.value,
143 - skip: (currentPage.value - 1) * pageSize.value
143 + limit: pageSize.value ?? 25,
144 + skip: (currentPage.value - 1) * (pageSize.value ?? 25)
145 }
146 }
147
@@ -169,6 +170,8 @@ function getData() {
170
171 useResizeObserver(header, entries => {
172 const entry = entries[0]
173 + if (!entry) return
174 +
175 const { width } = entry.contentRect
176
177 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/indices/CustomerIndicesSize.vue
+98 -101
@@ -8,13 +8,9 @@
8 <n-text strong>{{ customer.customer }}</n-text>
9 <n-popover trigger="click" placement="bottom" :width="300">
10 <template #trigger>
11 - <n-tag
12 - size="small"
13 - :bordered="false"
14 - type="info"
15 - class="clickable-tag"
16 - >
17 - {{ customer.index_count }} {{ customer.index_count === 1 ? "index" : "indices" }}
11 + <n-tag size="small" :bordered="false" type="info" class="clickable-tag">
12 + {{ customer.index_count }}
13 + {{ customer.index_count === 1 ? "index" : "indices" }}
14 </n-tag>
15 </template>
16 <div class="indices-popover">
@@ -54,24 +50,25 @@
50 </template>
51
52 <script lang="ts" setup>
53 +// TODO: refactor
54 import { NCard, NEmpty, NPopover, NProgress, NScrollbar, NSpin, NTag, NText, useMessage, useThemeVars } from "naive-ui"
55 import { computed, onBeforeMount, ref } from "vue"
56 import Api from "@/api"
57
58 interface CustomerIndicesSize {
62 - customer: string
63 - total_size_bytes: number
64 - total_size_human: string
65 - index_count: number
66 - indices: string[]
59 + customer: string
60 + total_size_bytes: number
61 + total_size_human: string
62 + index_count: number
63 + indices: string[]
64 }
65
66 defineProps<{
70 - bordered?: boolean
67 + bordered?: boolean
68 }>()
69
70 const emit = defineEmits<{
74 - (e: "click", value: string): void
71 + (e: "click", value: string): void
72 }>()
73
74 const message = useMessage()
@@ -81,115 +78,115 @@ const loading = ref(false)
78 const customerSizes = ref<CustomerIndicesSize[]>([])
79
80 const maxSize = computed(() => {
84 - if (!customerSizes.value.length) return 0
85 - return Math.max(...customerSizes.value.map(c => c.total_size_bytes))
81 + if (!customerSizes.value.length) return 0
82 + return Math.max(...customerSizes.value.map(c => c.total_size_bytes))
83 })
84
85 function getPercentage(sizeBytes: number): number {
89 - if (!maxSize.value) return 0
90 - return Math.round((sizeBytes / maxSize.value) * 100)
86 + if (!maxSize.value) return 0
87 + return Math.round((sizeBytes / maxSize.value) * 100)
88 }
89
90 function getProgressColor(sizeBytes: number): string {
94 - const percentage = getPercentage(sizeBytes)
95 - if (percentage >= 80) return themeVars.value.errorColor
96 - if (percentage >= 60) return themeVars.value.warningColor
97 - return themeVars.value.primaryColor
91 + const percentage = getPercentage(sizeBytes)
92 + if (percentage >= 80) return themeVars.value.errorColor
93 + if (percentage >= 60) return themeVars.value.warningColor
94 + return themeVars.value.primaryColor
95 }
96
97 function selectIndex(indexName: string) {
101 - emit("click", indexName)
98 + emit("click", indexName)
99 }
100
101 function getCustomerIndicesSize() {
105 - loading.value = true
106 -
107 - Api.wazuh.indices
108 - .getIndicesSizePerCustomer()
109 - .then(res => {
110 - if (res.data.success) {
111 - customerSizes.value = res.data.customer_sizes || []
112 - } else {
113 - message.error(res.data?.message || "An error occurred. Please try again later.")
114 - }
115 - })
116 - .catch(err => {
117 - message.error(err.response?.data?.message || "Failed to retrieve customer indices size.")
118 - })
119 - .finally(() => {
120 - loading.value = false
121 - })
102 + loading.value = true
103 +
104 + Api.wazuh.indices
105 + .getIndicesSizePerCustomer()
106 + .then(res => {
107 + if (res.data.success) {
108 + customerSizes.value = res.data.customer_sizes || []
109 + } else {
110 + message.error(res.data?.message || "An error occurred. Please try again later.")
111 + }
112 + })
113 + .catch(err => {
114 + message.error(err.response?.data?.message || "Failed to retrieve customer indices size.")
115 + })
116 + .finally(() => {
117 + loading.value = false
118 + })
119 }
120
121 onBeforeMount(() => {
125 - getCustomerIndicesSize()
122 + getCustomerIndicesSize()
123 })
124 </script>
125
126 <style lang="scss" scoped>
127 .customer-list {
131 - display: flex;
132 - flex-direction: column;
133 - gap: 16px;
134 - max-height: 400px;
135 - overflow-y: auto;
136 -
137 - .customer-item {
138 - .customer-header {
139 - display: flex;
140 - justify-content: space-between;
141 - align-items: center;
142 - margin-bottom: 6px;
143 -
144 - .customer-name {
145 - display: flex;
146 - align-items: center;
147 - gap: 8px;
148 -
149 - .clickable-tag {
150 - cursor: pointer;
151 - transition: opacity 0.2s;
152 -
153 - &:hover {
154 - opacity: 0.8;
155 - }
156 - }
157 - }
158 -
159 - .customer-size {
160 - font-weight: 600;
161 - font-family: var(--font-family-mono);
162 - }
163 - }
164 - }
128 + display: flex;
129 + flex-direction: column;
130 + gap: 16px;
131 + max-height: 400px;
132 + overflow-y: auto;
133 +
134 + .customer-item {
135 + .customer-header {
136 + display: flex;
137 + justify-content: space-between;
138 + align-items: center;
139 + margin-bottom: 6px;
140 +
141 + .customer-name {
142 + display: flex;
143 + align-items: center;
144 + gap: 8px;
145 +
146 + .clickable-tag {
147 + cursor: pointer;
148 + transition: opacity 0.2s;
149 +
150 + &:hover {
151 + opacity: 0.8;
152 + }
153 + }
154 + }
155 +
156 + .customer-size {
157 + font-weight: 600;
158 + font-family: var(--font-family-mono);
159 + }
160 + }
161 + }
162 }
163
164 .indices-popover {
168 - .popover-header {
169 - margin-bottom: 8px;
170 - padding-bottom: 8px;
171 - border-bottom: 1px solid var(--border-color);
172 - }
173 -
174 - .indices-list {
175 - display: flex;
176 - flex-direction: column;
177 - gap: 4px;
178 -
179 - .index-item {
180 - padding: 4px 0;
181 - cursor: pointer;
182 - border-radius: 4px;
183 - transition: background-color 0.2s;
184 -
185 - &:hover {
186 - background-color: var(--hover-color);
187 - }
188 -
189 - .index-link {
190 - cursor: pointer;
191 - }
192 - }
193 - }
165 + .popover-header {
166 + margin-bottom: 8px;
167 + padding-bottom: 8px;
168 + border-bottom: 1px solid var(--border-color);
169 + }
170 +
171 + .indices-list {
172 + display: flex;
173 + flex-direction: column;
174 + gap: 4px;
175 +
176 + .index-item {
177 + padding: 4px 0;
178 + cursor: pointer;
179 + border-radius: 4px;
180 + transition: background-color 0.2s;
181 +
182 + &:hover {
183 + background-color: var(--hover-color);
184 + }
185 +
186 + .index-link {
187 + cursor: pointer;
188 + }
189 + }
190 + }
191 }
192 </style>
frontend/src/components/indices/Marquee.vue
+2 -1
@@ -42,6 +42,7 @@
42 </template>
43
44 <script setup lang="ts">
45 +// TODO: refactor
46 import type { IndexStats } from "@/types/indices.d"
47 import { NCard, NEmpty, NSpin, useMessage } from "naive-ui"
48 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
@@ -62,7 +63,7 @@ const { indices } = toRefs(props)
63 const list = ref(indices.value)
64 const message = useMessage()
65 const themeStore = useThemeStore()
65 -const gradientColor = computed(() => themeStore.style["bg-default-color-rgb"].split(" "))
66 +const gradientColor = computed(() => themeStore.style["bg-default-color-rgb"]?.split(" ") ?? [])
67 const loading = ref(false)
68
69 function getIndices() {
frontend/src/components/indices/TopIndices.vue
+4 -1
@@ -9,6 +9,7 @@
9 </template>
10
11 <script setup lang="ts">
12 +// TODO: refactor
13 import type { ECharts } from "echarts/core"
14 import type { IndexStats } from "@/types/indices.d"
15 import bytes from "bytes"
@@ -155,7 +156,9 @@ function getOptions() {
156 const isLeft = chartCtx.value ? params.labelRect.x < chartCtx.value.getWidth() / 2 : false
157 const points = params.labelLinePoints
158 // Update the end point.
158 - points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width
159 + if (points[2]) {
160 + points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width
161 + }
162 return {
163 labelLinePoints: points,
164 hideOverlap: false,
frontend/src/components/license/LicenseCheckoutResponse.vue
+3 -3
@@ -19,7 +19,7 @@
19 <h1 class="text-center">Checkout canceled</h1>
20 </template>
21 <div>
22 - <n-button @click="gotoLicense()">
22 + <n-button @click="routeLicense().navigate()">
23 <template #icon>
24 <Icon :name="LicenseIcon" />
25 </template>
@@ -35,7 +35,7 @@ import { NButton, NCard, NSpin, useMessage } from "naive-ui"
35 import { onBeforeMount, ref, toRefs } from "vue"
36 import Api from "@/api"
37 import Icon from "@/components/common/Icon.vue"
38 -import { useGoto } from "@/composables/useGoto"
38 +import { useNavigation } from "@/composables/useNavigation"
39
40 const props = defineProps<{ type: "success" | "error"; data?: { email?: string } }>()
41 const { type, data } = toRefs(props)
@@ -43,7 +43,7 @@ const { type, data } = toRefs(props)
43 const ErrorIcon = "majesticons:exclamation-line"
44 const LicenseIcon = "carbon:license"
45 const CheckIcon = "carbon:checkmark-outline"
46 -const { gotoLicense } = useGoto()
46 +const { routeLicense } = useNavigation()
47 const message = useMessage()
48 const loadingLicense = ref(false)
49 const license = ref<LicenseKey | null>(null)
frontend/src/components/license/LicenseDetails.vue
+1 -1
@@ -101,7 +101,7 @@ import Badge from "@/components/common/Badge.vue"
101 import CardKV from "@/components/common/cards/CardKV.vue"
102 import Icon from "@/components/common/Icon.vue"
103 import { useSettingsStore } from "@/stores/settings"
104 -import { formatDate } from "@/utils"
104 +import { formatDate } from "@/utils/format"
105
106 const props = defineProps<{
107 licenseData?: License
frontend/src/components/license/LicenseFeatureCheck.vue
+5 -5
@@ -14,7 +14,7 @@
14 need to enable this feature. You can manage your features from the License page.
15 </div>
16 <div class="flex justify-end">
17 - <n-button @click="gotoLicense()">
17 + <n-button @click="routeLicense().navigate()">
18 <template #icon>
19 <Icon :name="LicenseIcon" />
20 </template>
@@ -38,7 +38,7 @@
38 </div>
39 <template #footer>
40 <div class="flex justify-end">
41 - <n-button @click="gotoLicense()">
41 + <n-button @click="routeLicense().navigate()">
42 <template #icon>
43 <Icon :name="LicenseIcon" />
44 </template>
@@ -62,7 +62,7 @@
62 </div>
63 <template #footer>
64 <div class="flex justify-end">
65 - <n-button @click="gotoLicense()">
65 + <n-button @click="routeLicense().navigate()">
66 <template #icon>
67 <Icon :name="LicenseIcon" />
68 </template>
@@ -80,7 +80,7 @@ import { NButton, NCard, NModal, NTooltip } from "naive-ui"
80 import { ref, watch, watchEffect } from "vue"
81 import Api from "@/api"
82 import Icon from "@/components/common/Icon.vue"
83 -import { useGoto } from "@/composables/useGoto"
83 +import { useNavigation } from "@/composables/useNavigation"
84
85 const { feature, feedback, disabled, forceShowFeedback } = defineProps<{
86 feature: LicenseFeatures
@@ -99,7 +99,7 @@ const LockIcon = "carbon:locked"
99 const LicenseIcon = "carbon:license"
100 const AlertIcon = "mdi:alert-outline"
101 const loading = ref(false)
102 -const { gotoLicense } = useGoto()
102 +const { routeLicense } = useNavigation()
103 const showFeedback = ref(forceShowFeedback ?? false)
104 const showModal = ref(false)
105
frontend/src/components/logs/LogItem.vue
+3 -2
@@ -62,6 +62,7 @@
62 </template>
63
64 <script setup lang="ts">
65 +// TODO: refactor
66 import type { Log } from "@/types/logs.d"
67 import type { User } from "@/types/user.d"
68 import { computed } from "vue"
@@ -92,8 +93,8 @@ const username = computed(() => {
93 })
94
95 function formatDate(timestamp: string | number | Date): string {
95 - // Parse as UTC (since backend stores in UTC without 'Z' suffix - maybe address later), then convert to local
96 - return dayjs.utc(timestamp).local().format(dFormats.datetime)
96 + // Parse as UTC (since backend stores in UTC without 'Z' suffix - maybe address later), then convert to local
97 + return dayjs.utc(timestamp).local().format(dFormats.datetime)
98 }
99 </script>
100
frontend/src/components/logs/LogsFilters.vue
+2 -1
@@ -56,6 +56,7 @@
56 </template>
57
58 <script setup lang="ts">
59 +// TODO: refactor
60 import type { LogsQueryEventType, LogsQueryTimeRange, LogsQueryTypes, LogsQueryValues } from "@/types/logs.d"
61 import type { User } from "@/types/user.d"
62 import _cloneDeep from "lodash/cloneDeep"
@@ -183,7 +184,7 @@ onBeforeMount(() => {
184
185 if (filterValue.value) {
186 if (filterType.value === "timeRange") {
186 - filterTimeRange.value.unit = filterValue.value[filterValue.value?.length - 1]
187 + filterTimeRange.value.unit = filterValue.value?.[filterValue.value?.length - 1] ?? "h"
188 filterTimeRange.value.time = _toSafeInteger(filterValue.value.slice(0, -1))
189 }
190 if (filterType.value === "eventType") {
frontend/src/components/logs/LogsList.vue
+3
@@ -116,6 +116,7 @@
116 </template>
117
118 <script setup lang="ts">
119 +// TODO: refactor
120 import type { Log, LogsQuery, LogsQueryTimeRange, LogsQueryTypes, LogsQueryValues } from "@/types/logs.d"
121 import type { User } from "@/types/user.d"
122 import { useResizeObserver } from "@vueuse/core"
@@ -260,6 +261,8 @@ function getUsers() {
261
262 useResizeObserver(header, entries => {
263 const entry = entries[0]
264 + if (!entry) return
265 +
266 const { width } = entry.contentRect
267
268 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/mitre/AtomicTests/List.vue
+3
@@ -65,6 +65,7 @@
65 </template>
66
67 <script setup lang="ts">
68 +// TODO: refactor
69 import type { MitreAtomicOsCategory, MitreAtomicTestsQuery } from "@/api/endpoints/wazuh/mitre"
70 import type { MitreAtomicTest } from "@/types/mitre.d"
71 import { useResizeObserver, watchDebounced } from "@vueuse/core"
@@ -131,6 +132,8 @@ function getList() {
132
133 useResizeObserver(header, entries => {
134 const entry = entries[0]
135 + if (!entry) return
136 +
137 const { width } = entry.contentRect
138
139 if (width < 650) {
frontend/src/components/mitre/AttackSimulator/AgentsList.vue
+3 -3
@@ -16,7 +16,7 @@
16 {{ item.hostname }}
17 </template>
18 <template #headerExtra>
19 - <code class="text-primary cursor-pointer" @click.stop="gotoAgent(item.agent_id)">
19 + <code class="text-primary cursor-pointer" @click.stop="routeAgent(item.agent_id).navigate()">
20 {{ item.agent_id }}
21 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
22 </code>
@@ -49,7 +49,7 @@ import { onBeforeMount, ref } from "vue"
49 import Api from "@/api"
50 import CardEntity from "@/components/common/cards/CardEntity.vue"
51 import Icon from "@/components/common/Icon.vue"
52 -import { useGoto } from "@/composables/useGoto"
52 +import { useNavigation } from "@/composables/useNavigation"
53 import { iconFromOs } from "@/utils"
54
55 const { agentsList, filter } = defineProps<{
@@ -65,7 +65,7 @@ const selected = defineModel<Agent | null>("selected", { default: null })
65
66 const LinkIcon = "carbon:launch"
67
68 -const { gotoAgent } = useGoto()
68 +const { routeAgent } = useNavigation()
69 const message = useMessage()
70 const loading = ref(false)
71 const list = ref<Agent[]>([])
frontend/src/components/mitre/AttackSimulator/SimulatorWizard.vue
+5 -4
@@ -57,7 +57,7 @@
57 <template #headerExtra>
58 <code
59 class="text-primary cursor-pointer"
60 - @click.stop="gotoAgent(selectedAgent.agent_id)"
60 + @click.stop="routeAgent(selectedAgent.agent_id).navigate()"
61 >
62 {{ selectedAgent.agent_id }}
63 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -148,9 +148,10 @@ import { computed, onMounted, ref, watch } from "vue"
148 import Api from "@/api"
149 import CardEntity from "@/components/common/cards/CardEntity.vue"
150 import Icon from "@/components/common/Icon.vue"
151 -import { useGoto } from "@/composables/useGoto"
151 +import { useNavigation } from "@/composables/useNavigation"
152 import { useSettingsStore } from "@/stores/settings"
153 -import { formatDate, getOS, iconFromOs } from "@/utils"
153 +import { getOS, iconFromOs } from "@/utils"
154 +import { formatDate } from "@/utils/format"
155 import AgentsList from "./AgentsList.vue"
156 import ParametersList from "./ParametersList.vue"
157
@@ -182,7 +183,7 @@ const ArrowLeftIcon = "carbon:arrow-left"
183 const AttackIcon = "mdi:target"
184 const LinkIcon = "carbon:launch"
185
185 -const { gotoAgent } = useGoto()
186 +const { routeAgent } = useNavigation()
187 const dFormats = useSettingsStore().dateFormat
188 const message = useMessage()
189 const current = ref<number>(1)
frontend/src/components/mitre/Group/GroupCard.vue
+4 -1
@@ -64,6 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 +// TODO: refactor
68 import type { MitreGroupDetails } from "@/types/mitre.d"
69 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
70 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
@@ -96,7 +97,9 @@ function getDetails(id: string) {
97 .getMitreGroups({ id })
98 .then(res => {
99 if (res.data.success) {
99 - groupDetails.value = res.data.results?.[0] || null
100 + if (res.data.results?.[0]) {
101 + groupDetails.value = res.data.results[0]
102 + }
103 if (groupDetails.value) emit("loaded", groupDetails.value)
104 } else {
105 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/mitre/Group/GroupDetails.vue
+1 -1
@@ -97,7 +97,7 @@ import Api from "@/api"
97 import Badge from "@/components/common/Badge.vue"
98 import CardKV from "@/components/common/cards/CardKV.vue"
99 import { useSettingsStore } from "@/stores/settings"
100 -import { formatDate } from "@/utils"
100 +import { formatDate } from "@/utils/format"
101 import References from "../common/References.vue"
102
103 const { externalId, entity } = defineProps<{
frontend/src/components/mitre/Mitigation/MitigationCard.vue
+4 -1
@@ -55,6 +55,7 @@
55 </template>
56
57 <script setup lang="ts">
58 +// TODO: refactor
59 import type { MitreMitigationDetails } from "@/types/mitre.d"
60 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
61 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
@@ -86,7 +87,9 @@ function getDetails(id: string) {
87 .getMitreMitigations({ id })
88 .then(res => {
89 if (res.data.success) {
89 - mitigationDetails.value = res.data.results?.[0] || null
90 + if (res.data.results?.[0]) {
91 + mitigationDetails.value = res.data.results[0]
92 + }
93 if (mitigationDetails.value) emit("loaded", mitigationDetails.value)
94 } else {
95 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/mitre/Mitigation/MitigationDetails.vue
+1 -1
@@ -82,7 +82,7 @@ import Api from "@/api"
82 import Badge from "@/components/common/Badge.vue"
83 import CardKV from "@/components/common/cards/CardKV.vue"
84 import { useSettingsStore } from "@/stores/settings"
85 -import { formatDate } from "@/utils"
85 +import { formatDate } from "@/utils/format"
86 import References from "../common/References.vue"
87
88 const { externalId, entity } = defineProps<{
frontend/src/components/mitre/Software/SoftwareCard.vue
+4 -1
@@ -64,6 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 +// TODO: refactor
68 import type { MitreSoftwareDetails } from "@/types/mitre.d"
69 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
70 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
@@ -96,7 +97,9 @@ function getDetails(id: string) {
97 .getMitreSoftware({ id })
98 .then(res => {
99 if (res.data.success) {
99 - softwareDetails.value = res.data.results?.[0] || null
100 + if (res.data.results?.[0]) {
101 + softwareDetails.value = res.data.results[0]
102 + }
103 if (softwareDetails.value) emit("loaded", softwareDetails.value)
104 } else {
105 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/mitre/Software/SoftwareDetails.vue
+1 -1
@@ -108,7 +108,7 @@ import Api from "@/api"
108 import Badge from "@/components/common/Badge.vue"
109 import CardKV from "@/components/common/cards/CardKV.vue"
110 import { useSettingsStore } from "@/stores/settings"
111 -import { formatDate } from "@/utils"
111 +import { formatDate } from "@/utils/format"
112 import References from "../common/References.vue"
113
114 const { externalId, entity } = defineProps<{
frontend/src/components/mitre/Tactic/TacticCard.vue
+4 -1
@@ -55,6 +55,7 @@
55 </template>
56
57 <script setup lang="ts">
58 +// TODO: refactor
59 import type { MitreTacticDetails } from "@/types/mitre.d"
60 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
61 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
@@ -86,7 +87,9 @@ function getDetails(id: string) {
87 .getMitreTactics({ id })
88 .then(res => {
89 if (res.data.success) {
89 - tacticDetails.value = res.data.results?.[0] || null
90 + if (res.data.results?.[0]) {
91 + tacticDetails.value = res.data.results[0]
92 + }
93 if (tacticDetails.value) emit("loaded", tacticDetails.value)
94 } else {
95 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/mitre/Tactic/TacticDetails.vue
+1 -1
@@ -75,7 +75,7 @@ import { defineAsyncComponent, onBeforeMount, ref, watch } from "vue"
75 import Api from "@/api"
76 import CardKV from "@/components/common/cards/CardKV.vue"
77 import { useSettingsStore } from "@/stores/settings"
78 -import { formatDate } from "@/utils"
78 +import { formatDate } from "@/utils/format"
79 import References from "../common/References.vue"
80
81 const { externalId, entity } = defineProps<{
frontend/src/components/mitre/Technique/TechniqueCard.vue
+4 -1
@@ -92,6 +92,7 @@
92 </template>
93
94 <script setup lang="ts">
95 +// TODO: refactor
96 import type { MitreTechniqueDetails } from "@/types/mitre.d"
97 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
98 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
@@ -127,7 +128,9 @@ function getDetails(id: string) {
128 .getMitreTechniques({ id })
129 .then(res => {
130 if (res.data.success) {
130 - techniqueDetails.value = res.data.results?.[0] || null
131 + if (res.data.results?.[0]) {
132 + techniqueDetails.value = res.data.results[0]
133 + }
134 if (techniqueDetails.value) emit("loaded", techniqueDetails.value)
135 } else {
136 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/mitre/TechniqueAlert/TechniqueAlertCard.vue
+1 -1
@@ -47,7 +47,7 @@ import { ref } from "vue"
47 import CardEntity from "@/components/common/cards/CardEntity.vue"
48 import Icon from "@/components/common/Icon.vue"
49 import { useSettingsStore } from "@/stores/settings"
50 -import { formatDate } from "@/utils"
50 +import { formatDate } from "@/utils/format"
51 import TechniqueAlertOverview from "./TechniqueAlertOverview.vue"
52
53 const { entity } = defineProps<{
frontend/src/components/mitre/TechniqueAlert/TechniqueAlertDetails.vue
+1 -1
@@ -120,7 +120,7 @@ import Api from "@/api"
120 import Badge from "@/components/common/Badge.vue"
121 import CardKV from "@/components/common/cards/CardKV.vue"
122 import { useSettingsStore } from "@/stores/settings"
123 -import { formatDate } from "@/utils"
123 +import { formatDate } from "@/utils/format"
124 import References from "../common/References.vue"
125
126 const { externalId, id, entity } = defineProps<{
frontend/src/components/mitre/TechniqueAlert/TechniqueAlertOverview.vue
+4 -1
@@ -57,6 +57,7 @@
57 </template>
58
59 <script setup lang="ts">
60 +// TODO: refactor
61 import type { MitreTechniqueDetails } from "@/types/mitre.d"
62 import { NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
63 import { onBeforeMount, ref } from "vue"
@@ -84,7 +85,9 @@ function getDetails(id: string) {
85 .getMitreTechniques({ external_id: id })
86 .then(res => {
87 if (res.data.success) {
87 - techniqueDetails.value = res.data.results?.[0] || null
88 + if (res.data.results?.[0]) {
89 + techniqueDetails.value = res.data.results[0]
90 + }
91 } else {
92 message.warning(res.data?.message || "An error occurred. Please try again later.")
93 }
frontend/src/components/mitre/TechniqueEvents/List.vue
+3
@@ -64,6 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 +// TODO: refactor
68 import type { MitreEventsQuery, MitreTechniquesAlertsQueryTimeRange } from "@/api/endpoints/wazuh/mitre"
69 import type { MitreEventDetails } from "@/types/mitre.d"
70 import { useResizeObserver, watchDebounced } from "@vueuse/core"
@@ -137,6 +138,8 @@ function getList() {
138
139 useResizeObserver(header, entries => {
140 const entry = entries[0]
141 + if (!entry) return
142 +
143 const { width } = entry.contentRect
144
145 if (width < 650) {
frontend/src/components/mitre/TechniqueEvents/TechniqueEventCard.vue
+15 -7
@@ -43,7 +43,10 @@
43 <div class="flex flex-col gap-1">
44 <div class="box">
45 agent_id:
46 - <code class="text-primary cursor-pointer" @click.stop="gotoAgent(alert.agent_id)">
46 + <code
47 + class="text-primary cursor-pointer"
48 + @click.stop="routeAgent(alert.agent_id).navigate()"
49 + >
50 {{ alert.agent_id }}
51 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
52 </code>
@@ -60,7 +63,7 @@
63 agent_labels_customer:
64 <code
65 class="text-primary cursor-pointer"
63 - @click.stop="gotoCustomer({ code: alert.agent_labels_customer })"
66 + @click.stop="routeCustomer({ code: alert.agent_labels_customer }).navigate()"
67 >
68 {{ alert.agent_labels_customer }}
69 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -112,7 +115,10 @@
115 </template>
116 <template #value>
117 <template v-if="key === 'agent_id'">
115 - <code class="text-primary cursor-pointer" @click.stop="gotoAgent(`${value}`)">
118 + <code
119 + class="text-primary cursor-pointer"
120 + @click.stop="routeAgent(`${value}`).navigate()"
121 + >
122 {{ value }}
123 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
124 </code>
@@ -120,7 +126,9 @@
126 <template v-else-if="key === 'agent_labels_customer'">
127 <code
128 class="text-primary cursor-pointer"
123 - @click.stop="gotoCustomer(value ? { code: value.toString() } : undefined)"
129 + @click.stop="
130 + routeCustomer(value ? { code: value.toString() } : undefined).navigate()
131 + "
132 >
133 {{ value }}
134 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -238,9 +246,9 @@ import Badge from "@/components/common/Badge.vue"
246 import CardEntity from "@/components/common/cards/CardEntity.vue"
247 import CardKV from "@/components/common/cards/CardKV.vue"
248 import Icon from "@/components/common/Icon.vue"
241 -import { useGoto } from "@/composables/useGoto"
249 +import { useNavigation } from "@/composables/useNavigation"
250 import { useSettingsStore } from "@/stores/settings"
243 -import { formatDate } from "@/utils"
251 +import { formatDate } from "@/utils/format"
252
253 const props = defineProps<{
254 alert: MitreEventDetails
@@ -257,7 +265,7 @@ const TargetIcon = "zondicons:target"
265 const AgentIcon = "carbon:police"
266 const LinkIcon = "carbon:launch"
267
260 -const { gotoCustomer, gotoAgent } = useGoto()
268 +const { routeCustomer, routeAgent } = useNavigation()
269 const showDetails = ref(false)
270 const dFormats = useSettingsStore().dateFormat
271
frontend/src/components/mitre/TechniqueEvents/TechniqueEventDetails.vue
+1 -1
@@ -108,7 +108,7 @@ import Api from "@/api"
108 import Badge from "@/components/common/Badge.vue"
109 import CardKV from "@/components/common/cards/CardKV.vue"
110 import { useSettingsStore } from "@/stores/settings"
111 -import { formatDate } from "@/utils"
111 +import { formatDate } from "@/utils/format"
112 import References from "../common/References.vue"
113
114 const { externalId, entity } = defineProps<{
frontend/src/components/monitoringAlerts/Item.vue
+8 -4
@@ -6,14 +6,18 @@
6 </template>
7 <template #mainExtra>
8 <div class="flex flex-wrap items-center gap-3">
9 - <Badge type="active" class="cursor-pointer" @click.stop="gotoIndex(alert.alert_index)">
9 + <Badge type="active" class="cursor-pointer" @click.stop="routeIndex(alert.alert_index).navigate()">
10 <template #iconRight>
11 <Icon :name="LinkIcon" :size="14" />
12 </template>
13 <template #label>Index / {{ alert.alert_index }}</template>
14 </Badge>
15
16 - <Badge type="active" class="cursor-pointer" @click.stop="gotoCustomer({ code: alert.customer_code })">
16 + <Badge
17 + type="active"
18 + class="cursor-pointer"
19 + @click.stop="routeCustomer({ code: alert.customer_code }).navigate()"
20 + >
21 <template #iconRight>
22 <Icon :name="LinkIcon" :size="14" />
23 </template>
@@ -50,7 +54,7 @@ import { computed, ref } from "vue"
54 import Badge from "@/components/common/Badge.vue"
55 import CardEntity from "@/components/common/cards/CardEntity.vue"
56 import Icon from "@/components/common/Icon.vue"
53 -import { useGoto } from "@/composables/useGoto"
57 +import { useNavigation } from "@/composables/useNavigation"
58 import AlertActions from "./ItemActions.vue"
59
60 const { alert, embedded } = defineProps<{
@@ -65,7 +69,7 @@ const emit = defineEmits<{
69
70 const LinkIcon = "carbon:launch"
71
68 -const { gotoCustomer, gotoIndex } = useGoto()
72 +const { routeCustomer, routeIndex } = useNavigation()
73 const loadingDelete = ref(false)
74 const loadingInvoke = ref(false)
75 const loading = computed(() => loadingDelete.value || loadingInvoke.value)
frontend/src/components/monitoringAlerts/List.vue
+3
@@ -74,6 +74,7 @@
74 </template>
75
76 <script setup lang="ts">
77 +// TODO: refactor
78 import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
79 import { useResizeObserver } from "@vueuse/core"
80 import { NButton, NEmpty, NPagination, NPopover, NSpin, useDialog, useMessage } from "naive-ui"
@@ -170,6 +171,8 @@ function purge() {
171
172 useResizeObserver(header, entries => {
173 const entry = entries[0]
174 + if (!entry) return
175 +
176 const { width } = entry.contentRect
177
178 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/overview/AgentsCard.vue
+3 -3
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading">
3 - <CardStatsMulti title="Agents" hovered class="h-full cursor-pointer" :values @click="gotoAgent()">
3 + <CardStatsMulti title="Agents" hovered class="h-full cursor-pointer" :values @click="routeAgent().navigate()">
4 <template #icon>
5 <CardStatsIcon :icon-name="AgentsIcon" boxed :box-size="30"></CardStatsIcon>
6 </template>
@@ -16,11 +16,11 @@ import { computed, onBeforeMount, ref } from "vue"
16 import Api from "@/api"
17 import CardStatsIcon from "@/components/common/cards/CardStatsIcon.vue"
18 import CardStatsMulti from "@/components/common/cards/CardStatsMulti.vue"
19 -import { useGoto } from "@/composables/useGoto"
19 +import { useNavigation } from "@/composables/useNavigation"
20 import { AgentStatus } from "@/types/agents.d"
21
22 const AgentsIcon = "carbon:network-3"
23 -const { gotoAgent } = useGoto()
23 +const { routeAgent } = useNavigation()
24 const message = useMessage()
25 const loading = ref(false)
26 const agents = ref<Agent[]>([])
frontend/src/components/overview/CustomersCard.vue
+3 -3
@@ -5,7 +5,7 @@
5 hovered
6 class="h-full cursor-pointer"
7 :values="[{ value: total, label: 'Total' }]"
8 - @click="gotoCustomer()"
8 + @click="routeCustomer().navigate()"
9 >
10 <template #icon>
11 <CardStatsIcon :icon-name="CustomersIcon" boxed :box-size="30"></CardStatsIcon>
@@ -21,10 +21,10 @@ import { computed, onBeforeMount, ref } from "vue"
21 import Api from "@/api"
22 import CardStatsIcon from "@/components/common/cards/CardStatsIcon.vue"
23 import CardStatsMulti from "@/components/common/cards/CardStatsMulti.vue"
24 -import { useGoto } from "@/composables/useGoto"
24 +import { useNavigation } from "@/composables/useNavigation"
25
26 const CustomersIcon = "carbon:user-multiple"
27 -const { gotoCustomer } = useGoto()
27 +const { routeCustomer } = useNavigation()
28 const message = useMessage()
29 const loading = ref(false)
30 const customers = ref<Customer[]>([])
frontend/src/components/overview/HealthcheckCard.vue
+9 -3
@@ -1,6 +1,12 @@
1 <template>
2 <n-spin :show="loading">
3 - <CardStatsMulti title="Healthcheck" hovered class="h-full cursor-pointer" :values @click="gotoHealthcheck()">
3 + <CardStatsMulti
4 + title="Healthcheck"
5 + hovered
6 + class="h-full cursor-pointer"
7 + :values
8 + @click="routeHealthcheck().navigate()"
9 + >
10 <template #icon>
11 <CardStatsIcon
12 :icon-name="HealthcheckIcon"
@@ -21,12 +27,12 @@ import { computed, onBeforeMount, ref } from "vue"
27 import Api from "@/api"
28 import CardStatsIcon from "@/components/common/cards/CardStatsIcon.vue"
29 import CardStatsMulti from "@/components/common/cards/CardStatsMulti.vue"
24 -import { useGoto } from "@/composables/useGoto"
30 +import { useNavigation } from "@/composables/useNavigation"
31 import { useThemeStore } from "@/stores/theme"
32 import { InfluxDBAlertSeverity } from "@/types/healthchecks.d"
33
34 const HealthcheckIcon = "ph:heartbeat"
29 -const { gotoHealthcheck } = useGoto()
35 +const { routeHealthcheck } = useNavigation()
36 const message = useMessage()
37 const loading = ref(false)
38 const healthcheck = ref<InfluxDBAlert[]>([])
frontend/src/components/overview/IncidentAlerts.vue
+3 -3
@@ -5,7 +5,7 @@
5 hovered
6 class="h-full cursor-pointer"
7 :values
8 - @click="gotoIncidentManagementAlerts()"
8 + @click="routeIncidentManagementAlerts().navigate()"
9 >
10 <template #icon>
11 <CardStatsIcon :icon-name="AlertsIcon" boxed :box-size="30"></CardStatsIcon>
@@ -21,10 +21,10 @@ import { computed, onBeforeMount, ref } from "vue"
21 import Api from "@/api"
22 import CardStatsBars from "@/components/common/cards/CardStatsBars.vue"
23 import CardStatsIcon from "@/components/common/cards/CardStatsIcon.vue"
24 -import { useGoto } from "@/composables/useGoto"
24 +import { useNavigation } from "@/composables/useNavigation"
25
26 const AlertsIcon = "carbon:warning-hex"
27 -const { gotoIncidentManagementAlerts } = useGoto()
27 +const { routeIncidentManagementAlerts } = useNavigation()
28 const message = useMessage()
29 const loading = ref(false)
30 const total = ref(0)
frontend/src/components/overview/IncidentCases.vue
+3 -3
@@ -5,7 +5,7 @@
5 hovered
6 class="h-full cursor-pointer"
7 :values
8 - @click="gotoIncidentManagementCases()"
8 + @click="routeIncidentManagementCases().navigate()"
9 >
10 <template #icon>
11 <CardStatsIcon :icon-name="CasesIcon" boxed :box-size="30"></CardStatsIcon>
@@ -22,10 +22,10 @@ import { computed, onBeforeMount, ref } from "vue"
22 import Api from "@/api"
23 import CardStatsBars from "@/components/common/cards/CardStatsBars.vue"
24 import CardStatsIcon from "@/components/common/cards/CardStatsIcon.vue"
25 -import { useGoto } from "@/composables/useGoto"
25 +import { useNavigation } from "@/composables/useNavigation"
26
27 const CasesIcon = "carbon:ibm-secure-infrastructure-on-vpc-for-regulated-industries"
28 -const { gotoIncidentManagementCases } = useGoto()
28 +const { routeIncidentManagementCases } = useNavigation()
29 const message = useMessage()
30 const loading = ref(false)
31 const casesList = ref<Case[]>([])
frontend/src/components/overview/SocAlertsCard.vue
+3 -3
@@ -6,7 +6,7 @@
6 :vertical="vertical"
7 hovered
8 class="h-full cursor-pointer"
9 - @click="gotoSocAlerts()"
9 + @click="routeSocAlerts().navigate()"
10 >
11 <template #icon>
12 <CardStatsIcon :icon-name="SOCIcon" boxed :box-size="40"></CardStatsIcon>
@@ -22,7 +22,7 @@ import { computed, onBeforeMount, ref, toRefs } from "vue"
22 import Api from "@/api"
23 import CardStats from "@/components/common/cards/CardStats.vue"
24 import CardStatsIcon from "@/components/common/cards/CardStatsIcon.vue"
25 -import { useGoto } from "@/composables/useGoto"
25 +import { useNavigation } from "@/composables/useNavigation"
26
27 const props = defineProps<{
28 vertical?: boolean
@@ -30,7 +30,7 @@ const props = defineProps<{
30 const { vertical } = toRefs(props)
31
32 const SOCIcon = "carbon:security"
33 -const { gotoSocAlerts } = useGoto()
33 +const { routeSocAlerts } = useNavigation()
34 const message = useMessage()
35 const loading = ref(false)
36 const alerts = ref<SocAlert[]>([])
frontend/src/components/patchTuesday/PatchTuesdayCard.vue
+143 -142
@@ -72,173 +72,174 @@
72 </template>
73
74 <script setup lang="ts">
75 +// TODO: refactor
76 import type { PatchTuesdayItem } from "@/types/patchTuesday.d"
77 import { NCard, NTag } from "naive-ui"
78 import Icon from "@/components/common/Icon.vue"
79 import PatchTuesdayPriorityBadge from "./PatchTuesdayPriorityBadge.vue"
80
81 defineProps<{
81 - item: PatchTuesdayItem
82 + item: PatchTuesdayItem
83 }>()
84 const AlertIcon = "carbon:warning"
85 const ClockIcon = "carbon:time"
86 const LinkIcon = "carbon:link"
87
88 function getSeverityType(severity: string): "error" | "warning" | "info" | "default" {
88 - const s = severity.toLowerCase()
89 - if (s === "critical") return "error"
90 - if (s === "important") return "warning"
91 - if (s === "moderate") return "info"
92 - return "default"
89 + const s = severity.toLowerCase()
90 + if (s === "critical") return "error"
91 + if (s === "important") return "warning"
92 + if (s === "moderate") return "info"
93 + return "default"
94 }
95
96 function getCvssClass(score: number): string {
96 - if (score >= 9.0) return "critical"
97 - if (score >= 7.0) return "high"
98 - if (score >= 4.0) return "medium"
99 - return "low"
97 + if (score >= 9.0) return "critical"
98 + if (score >= 7.0) return "high"
99 + if (score >= 4.0) return "medium"
100 + return "low"
101 }
102
103 function truncateProduct(product: string): string {
103 - if (product.length <= 40) return product
104 - return `${product.substring(0, 40)}...`
104 + if (product.length <= 40) return product
105 + return `${product.substring(0, 40)}...`
106 }
107
108 function getSlaHint(sla: string): string {
108 - if (sla.includes("immediately") || sla.includes("24h")) return "Patch immediately"
109 - if (sla.includes("72h")) return "Patch within 72 hours"
110 - if (sla.includes("7-14")) return "Patch within 7-14 days"
111 - return "Patch within 30 days"
109 + if (sla.includes("immediately") || sla.includes("24h")) return "Patch immediately"
110 + if (sla.includes("72h")) return "Patch within 72 hours"
111 + if (sla.includes("7-14")) return "Patch within 7-14 days"
112 + return "Patch within 30 days"
113 }
114 </script>
115
116 <style scoped lang="scss">
117 .patch-tuesday-card {
117 - border-radius: 8px;
118 - cursor: pointer;
119 - transition: all 0.2s ease;
120 - border-left: 4px solid transparent;
121 - background: var(--bg-secondary-color);
122 -
123 - &.priority-p0 {
124 - border-left-color: #ef4444;
125 - }
126 -
127 - &.priority-p1 {
128 - border-left-color: #f97316;
129 - }
130 -
131 - &.priority-p2 {
132 - border-left-color: #eab308;
133 - }
134 -
135 - &.priority-p3 {
136 - border-left-color: #22c55e;
137 - }
138 -
139 - .card-header {
140 - display: flex;
141 - justify-content: space-between;
142 - align-items: flex-start;
143 - margin-bottom: 8px;
144 -
145 - .cve-info {
146 - display: flex;
147 - align-items: center;
148 - gap: 8px;
149 -
150 - .cve-id {
151 - font-weight: 600;
152 - font-size: 0.95rem;
153 - font-family: monospace;
154 - }
155 - }
156 -
157 - .badges {
158 - display: flex;
159 - gap: 4px;
160 - }
161 - }
162 -
163 - .card-title {
164 - font-size: 0.875rem;
165 - line-height: 1.4;
166 - margin-bottom: 12px;
167 - opacity: 0.9;
168 - display: -webkit-box;
169 - -webkit-line-clamp: 2;
170 - -webkit-box-orient: vertical;
171 - overflow: hidden;
172 - }
173 -
174 - .product-info {
175 - display: flex;
176 - align-items: center;
177 - gap: 8px;
178 - margin-bottom: 12px;
179 -
180 - .product-name {
181 - font-size: 0.8rem;
182 - opacity: 0.7;
183 - }
184 - }
185 -
186 - .scores-row {
187 - display: flex;
188 - gap: 16px;
189 - margin-bottom: 12px;
190 -
191 - .score-item {
192 - display: flex;
193 - flex-direction: column;
194 -
195 - .score-label {
196 - font-size: 0.7rem;
197 - text-transform: uppercase;
198 - opacity: 0.6;
199 - }
200 -
201 - .score-value {
202 - font-weight: 600;
203 - font-size: 0.9rem;
204 -
205 - &.critical {
206 - color: #ef4444;
207 - }
208 - &.high {
209 - color: #f97316;
210 - }
211 - &.medium {
212 - color: #eab308;
213 - }
214 - &.low {
215 - color: #22c55e;
216 - }
217 - }
218 - }
219 - }
220 -
221 - .kb-row {
222 - display: flex;
223 - align-items: center;
224 - gap: 6px;
225 - font-size: 0.8rem;
226 - opacity: 0.7;
227 - margin-bottom: 8px;
228 -
229 - .kb-more {
230 - opacity: 0.6;
231 - }
232 - }
233 -
234 - .sla-hint {
235 - display: flex;
236 - align-items: center;
237 - gap: 6px;
238 - font-size: 0.75rem;
239 - opacity: 0.6;
240 - padding-top: 8px;
241 - border-top: 1px solid var(--border-color);
242 - }
118 + border-radius: 8px;
119 + cursor: pointer;
120 + transition: all 0.2s ease;
121 + border-left: 4px solid transparent;
122 + background: var(--bg-secondary-color);
123 +
124 + &.priority-p0 {
125 + border-left-color: #ef4444;
126 + }
127 +
128 + &.priority-p1 {
129 + border-left-color: #f97316;
130 + }
131 +
132 + &.priority-p2 {
133 + border-left-color: #eab308;
134 + }
135 +
136 + &.priority-p3 {
137 + border-left-color: #22c55e;
138 + }
139 +
140 + .card-header {
141 + display: flex;
142 + justify-content: space-between;
143 + align-items: flex-start;
144 + margin-bottom: 8px;
145 +
146 + .cve-info {
147 + display: flex;
148 + align-items: center;
149 + gap: 8px;
150 +
151 + .cve-id {
152 + font-weight: 600;
153 + font-size: 0.95rem;
154 + font-family: monospace;
155 + }
156 + }
157 +
158 + .badges {
159 + display: flex;
160 + gap: 4px;
161 + }
162 + }
163 +
164 + .card-title {
165 + font-size: 0.875rem;
166 + line-height: 1.4;
167 + margin-bottom: 12px;
168 + opacity: 0.9;
169 + display: -webkit-box;
170 + -webkit-line-clamp: 2;
171 + -webkit-box-orient: vertical;
172 + overflow: hidden;
173 + }
174 +
175 + .product-info {
176 + display: flex;
177 + align-items: center;
178 + gap: 8px;
179 + margin-bottom: 12px;
180 +
181 + .product-name {
182 + font-size: 0.8rem;
183 + opacity: 0.7;
184 + }
185 + }
186 +
187 + .scores-row {
188 + display: flex;
189 + gap: 16px;
190 + margin-bottom: 12px;
191 +
192 + .score-item {
193 + display: flex;
194 + flex-direction: column;
195 +
196 + .score-label {
197 + font-size: 0.7rem;
198 + text-transform: uppercase;
199 + opacity: 0.6;
200 + }
201 +
202 + .score-value {
203 + font-weight: 600;
204 + font-size: 0.9rem;
205 +
206 + &.critical {
207 + color: #ef4444;
208 + }
209 + &.high {
210 + color: #f97316;
211 + }
212 + &.medium {
213 + color: #eab308;
214 + }
215 + &.low {
216 + color: #22c55e;
217 + }
218 + }
219 + }
220 + }
221 +
222 + .kb-row {
223 + display: flex;
224 + align-items: center;
225 + gap: 6px;
226 + font-size: 0.8rem;
227 + opacity: 0.7;
228 + margin-bottom: 8px;
229 +
230 + .kb-more {
231 + opacity: 0.6;
232 + }
233 + }
234 +
235 + .sla-hint {
236 + display: flex;
237 + align-items: center;
238 + gap: 6px;
239 + font-size: 0.75rem;
240 + opacity: 0.6;
241 + padding-top: 8px;
242 + border-top: 1px solid var(--border-color);
243 + }
244 }
245 </style>
frontend/src/components/patchTuesday/PatchTuesdayDetail.vue
+145 -164
@@ -114,13 +114,7 @@
114 <div v-if="item.remediation.kbs.length > 0" class="kb-list">
115 <strong>Related KB Articles:</strong>
116 <div class="kb-tags">
117 - <n-tag
118 - v-for="kb in item.remediation.kbs"
119 - :key="kb"
120 - size="small"
121 - class="kb-tag"
122 - @click="openKB(kb)"
123 - >
117 + <n-tag v-for="kb in item.remediation.kbs" :key="kb" size="small" class="kb-tag" @click="openKB(kb)">
118 <template #icon>
119 <Icon :name="ExternalLinkIcon" />
120 </template>
@@ -137,13 +131,7 @@
131 <div class="detail-section">
132 <h3 class="section-title">Sources</h3>
133 <div class="source-links">
140 - <n-button
141 - text
142 - tag="a"
143 - :href="item.source.msrc_cvrf_url"
144 - target="_blank"
145 - type="primary"
146 - >
134 + <n-button text tag="a" :href="item.source.msrc_cvrf_url" target="_blank" type="primary">
135 <template #icon>
136 <Icon :name="ExternalLinkIcon" />
137 </template>
@@ -174,186 +162,179 @@
162 </template>
163
164 <script setup lang="ts">
165 +// TODO: refactor
166 import type { PatchTuesdayItem } from "@/types/patchTuesday.d"
178 -import {
179 - NAlert,
180 - NButton,
181 - NDescriptions,
182 - NDescriptionsItem,
183 - NDivider,
184 - NEmpty,
185 - NTag
186 -} from "naive-ui"
167 +import { NAlert, NButton, NDescriptions, NDescriptionsItem, NDivider, NEmpty, NTag } from "naive-ui"
168 import Icon from "@/components/common/Icon.vue"
169 import { PriorityLevel } from "@/types/patchTuesday.d"
170 import PatchTuesdayPriorityBadge from "./PatchTuesdayPriorityBadge.vue"
171
172 defineProps<{
192 - item: PatchTuesdayItem
173 + item: PatchTuesdayItem
174 }>()
175 const AlertIcon = "carbon:warning"
176 const ExternalLinkIcon = "carbon:launch"
177
178 function getSeverityType(severity: string): "error" | "warning" | "info" | "default" {
198 - const s = severity.toLowerCase()
199 - if (s === "critical") return "error"
200 - if (s === "important") return "warning"
201 - if (s === "moderate") return "info"
202 - return "default"
179 + const s = severity.toLowerCase()
180 + if (s === "critical") return "error"
181 + if (s === "important") return "warning"
182 + if (s === "moderate") return "info"
183 + return "default"
184 }
185
186 function getCvssClass(score: number): string {
206 - if (score >= 9.0) return "critical"
207 - if (score >= 7.0) return "high"
208 - if (score >= 4.0) return "medium"
209 - return "low"
187 + if (score >= 9.0) return "critical"
188 + if (score >= 7.0) return "high"
189 + if (score >= 4.0) return "medium"
190 + return "low"
191 }
192
193 function getPriorityAlertType(priority: PriorityLevel | string): "error" | "warning" | "info" | "success" {
213 - switch (priority) {
214 - case PriorityLevel.P0:
215 - return "error"
216 - case PriorityLevel.P1:
217 - return "warning"
218 - case PriorityLevel.P2:
219 - return "info"
220 - case PriorityLevel.P3:
221 - return "success"
222 - default:
223 - return "info"
224 - }
194 + switch (priority) {
195 + case PriorityLevel.P0:
196 + return "error"
197 + case PriorityLevel.P1:
198 + return "warning"
199 + case PriorityLevel.P2:
200 + return "info"
201 + case PriorityLevel.P3:
202 + return "success"
203 + default:
204 + return "info"
205 + }
206 }
207
208 function formatDate(dateStr: string): string {
228 - if (!dateStr) return "-"
229 - return new Date(dateStr).toLocaleDateString("en-US", {
230 - year: "numeric",
231 - month: "long",
232 - day: "numeric"
233 - })
209 + if (!dateStr) return "-"
210 + return new Date(dateStr).toLocaleDateString("en-US", {
211 + year: "numeric",
212 + month: "long",
213 + day: "numeric"
214 + })
215 }
216
217 function formatDateTime(dateStr: string): string {
237 - if (!dateStr) return "-"
238 - return new Date(dateStr).toLocaleString()
218 + if (!dateStr) return "-"
219 + return new Date(dateStr).toLocaleString()
220 }
221
222 function openKB(kb: string) {
242 - window.open(`https://support.microsoft.com/help/${kb.replace("KB", "")}`, "_blank")
223 + window.open(`https://support.microsoft.com/help/${kb.replace("KB", "")}`, "_blank")
224 }
225 </script>
226
227 <style scoped lang="scss">
228 .patch-tuesday-detail {
248 - .detail-header {
249 - .header-row {
250 - display: flex;
251 - gap: 8px;
252 - margin-bottom: 12px;
253 - }
254 -
255 - .detail-title {
256 - font-size: 1.1rem;
257 - font-weight: 600;
258 - line-height: 1.4;
259 - }
260 - }
261 -
262 - .detail-section {
263 - .section-title {
264 - font-size: 0.9rem;
265 - font-weight: 600;
266 - margin-bottom: 12px;
267 - display: flex;
268 - align-items: center;
269 - }
270 - }
271 -
272 - .scores-grid {
273 - display: grid;
274 - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
275 - gap: 12px;
276 -
277 - .score-card {
278 - background: var(--bg-secondary-color);
279 - padding: 12px;
280 - border-radius: 8px;
281 - display: flex;
282 - flex-direction: column;
283 -
284 - .score-label {
285 - font-size: 0.75rem;
286 - opacity: 0.7;
287 - margin-bottom: 4px;
288 - }
289 -
290 - .score-value {
291 - font-size: 1.25rem;
292 - font-weight: 700;
293 -
294 - &.critical {
295 - color: #ef4444;
296 - }
297 - &.high {
298 - color: #f97316;
299 - }
300 - &.medium {
301 - color: #eab308;
302 - }
303 - &.low {
304 - color: #22c55e;
305 - }
306 - }
307 -
308 - .score-detail {
309 - font-size: 0.7rem;
310 - opacity: 0.6;
311 - margin-top: 4px;
312 - }
313 - }
314 - }
315 -
316 - .reasons-list {
317 - ul {
318 - margin: 8px 0 0 20px;
319 - padding: 0;
320 -
321 - li {
322 - margin-bottom: 4px;
323 - font-size: 0.875rem;
324 - }
325 - }
326 - }
327 -
328 - .kb-tags {
329 - display: flex;
330 - flex-wrap: wrap;
331 - gap: 8px;
332 - margin-top: 8px;
333 -
334 - .kb-tag {
335 - cursor: pointer;
336 -
337 - &:hover {
338 - opacity: 0.8;
339 - }
340 - }
341 - }
342 -
343 - .source-links {
344 - display: flex;
345 - flex-direction: column;
346 - gap: 8px;
347 - }
348 -
349 - .detail-footer {
350 - margin-top: 24px;
351 - padding-top: 12px;
352 - border-top: 1px solid var(--border-color);
353 - font-size: 0.75rem;
354 - opacity: 0.6;
355 - display: flex;
356 - gap: 8px;
357 - }
229 + .detail-header {
230 + .header-row {
231 + display: flex;
232 + gap: 8px;
233 + margin-bottom: 12px;
234 + }
235 +
236 + .detail-title {
237 + font-size: 1.1rem;
238 + font-weight: 600;
239 + line-height: 1.4;
240 + }
241 + }
242 +
243 + .detail-section {
244 + .section-title {
245 + font-size: 0.9rem;
246 + font-weight: 600;
247 + margin-bottom: 12px;
248 + display: flex;
249 + align-items: center;
250 + }
251 + }
252 +
253 + .scores-grid {
254 + display: grid;
255 + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
256 + gap: 12px;
257 +
258 + .score-card {
259 + background: var(--bg-secondary-color);
260 + padding: 12px;
261 + border-radius: 8px;
262 + display: flex;
263 + flex-direction: column;
264 +
265 + .score-label {
266 + font-size: 0.75rem;
267 + opacity: 0.7;
268 + margin-bottom: 4px;
269 + }
270 +
271 + .score-value {
272 + font-size: 1.25rem;
273 + font-weight: 700;
274 +
275 + &.critical {
276 + color: #ef4444;
277 + }
278 + &.high {
279 + color: #f97316;
280 + }
281 + &.medium {
282 + color: #eab308;
283 + }
284 + &.low {
285 + color: #22c55e;
286 + }
287 + }
288 +
289 + .score-detail {
290 + font-size: 0.7rem;
291 + opacity: 0.6;
292 + margin-top: 4px;
293 + }
294 + }
295 + }
296 +
297 + .reasons-list {
298 + ul {
299 + margin: 8px 0 0 20px;
300 + padding: 0;
301 +
302 + li {
303 + margin-bottom: 4px;
304 + font-size: 0.875rem;
305 + }
306 + }
307 + }
308 +
309 + .kb-tags {
310 + display: flex;
311 + flex-wrap: wrap;
312 + gap: 8px;
313 + margin-top: 8px;
314 +
315 + .kb-tag {
316 + cursor: pointer;
317 +
318 + &:hover {
319 + opacity: 0.8;
320 + }
321 + }
322 + }
323 +
324 + .source-links {
325 + display: flex;
326 + flex-direction: column;
327 + gap: 8px;
328 + }
329 +
330 + .detail-footer {
331 + margin-top: 24px;
332 + padding-top: 12px;
333 + border-top: 1px solid var(--border-color);
334 + font-size: 0.75rem;
335 + opacity: 0.6;
336 + display: flex;
337 + gap: 8px;
338 + }
339 }
340 </style>
frontend/src/components/patchTuesday/PatchTuesdayFilters.vue
+66 -68
@@ -73,10 +73,7 @@
73 <div class="filter-item toggle-item">
74 <n-tooltip trigger="hover">
75 <template #trigger>
76 - <n-switch
77 - :value="filters.kevOnly"
78 - @update:value="updateFilter('kevOnly', $event)"
79 - >
76 + <n-switch :value="filters.kevOnly" @update:value="updateFilter('kevOnly', $event)">
77 <template #checked>KEV</template>
78 <template #unchecked>KEV</template>
79 </n-switch>
@@ -99,6 +96,7 @@
96 </template>
97
98 <script setup lang="ts">
99 +// TODO: refactor
100 import type { PatchTuesdayFilters } from "./types"
101 import { NButton, NCard, NInput, NSelect, NSwitch, NTooltip } from "naive-ui"
102 import { computed } from "vue"
@@ -106,96 +104,96 @@ import Icon from "@/components/common/Icon.vue"
104 import { PriorityLevel } from "@/types/patchTuesday.d"
105
106 const props = defineProps<{
109 - filters: PatchTuesdayFilters
110 - cycles: string[]
111 - families: string[]
112 - loading?: boolean
107 + filters: PatchTuesdayFilters
108 + cycles: string[]
109 + families: string[]
110 + loading?: boolean
111 }>()
112 const emit = defineEmits<{
115 - (e: "update:filters", filters: PatchTuesdayFilters): void
113 + (e: "update:filters", filters: PatchTuesdayFilters): void
114 }>()
115 const SearchIcon = "carbon:search"
116 const ClearIcon = "carbon:close"
117
118 const cycleOptions = computed(() =>
121 - props.cycles.map(cycle => ({
122 - label: cycle,
123 - value: cycle
124 - }))
119 + props.cycles.map(cycle => ({
120 + label: cycle,
121 + value: cycle
122 + }))
123 )
124
125 const priorityOptions = [
128 - { label: "P0 - Emergency", value: PriorityLevel.P0 },
129 - { label: "P1 - High", value: PriorityLevel.P1 },
130 - { label: "P2 - Medium", value: PriorityLevel.P2 },
131 - { label: "P3 - Low", value: PriorityLevel.P3 }
126 + { label: "P0 - Emergency", value: PriorityLevel.P0 },
127 + { label: "P1 - High", value: PriorityLevel.P1 },
128 + { label: "P2 - Medium", value: PriorityLevel.P2 },
129 + { label: "P3 - Low", value: PriorityLevel.P3 }
130 ]
131
132 const familyOptions = computed(() =>
135 - props.families.map(family => ({
136 - label: family,
137 - value: family
138 - }))
133 + props.families.map(family => ({
134 + label: family,
135 + value: family
136 + }))
137 )
138
139 const severityOptions = [
142 - { label: "Critical", value: "critical" },
143 - { label: "Important", value: "important" },
144 - { label: "Moderate", value: "moderate" },
145 - { label: "Low", value: "low" }
140 + { label: "Critical", value: "critical" },
141 + { label: "Important", value: "important" },
142 + { label: "Moderate", value: "moderate" },
143 + { label: "Low", value: "low" }
144 ]
145
146 function updateFilter<K extends keyof PatchTuesdayFilters>(key: K, value: PatchTuesdayFilters[K]) {
149 - emit("update:filters", {
150 - ...props.filters,
151 - [key]: value
152 - })
147 + emit("update:filters", {
148 + ...props.filters,
149 + [key]: value
150 + })
151 }
152
153 function clearFilters() {
156 - emit("update:filters", {
157 - cycle: props.filters.cycle, // Keep cycle selected
158 - priority: null,
159 - family: null,
160 - severity: null,
161 - searchQuery: "",
162 - kevOnly: false
163 - })
154 + emit("update:filters", {
155 + cycle: props.filters.cycle, // Keep cycle selected
156 + priority: null,
157 + family: null,
158 + severity: null,
159 + searchQuery: "",
160 + kevOnly: false
161 + })
162 }
163 </script>
164
165 <style scoped lang="scss">
166 .filters-card {
169 - background: var(--bg-secondary-color);
170 - border-radius: 8px;
171 -
172 - .filters-container {
173 - display: flex;
174 - flex-wrap: wrap;
175 - align-items: flex-end;
176 - gap: 16px;
177 - }
178 -
179 - .filter-item {
180 - display: flex;
181 - flex-direction: column;
182 - gap: 4px;
183 -
184 - .filter-label {
185 - font-size: 0.75rem;
186 - text-transform: uppercase;
187 - letter-spacing: 0.5px;
188 - opacity: 0.7;
189 - }
190 -
191 - &.search-item {
192 - flex: 1;
193 - min-width: 200px;
194 - }
195 -
196 - &.toggle-item {
197 - padding-bottom: 4px;
198 - }
199 - }
167 + background: var(--bg-secondary-color);
168 + border-radius: 8px;
169 +
170 + .filters-container {
171 + display: flex;
172 + flex-wrap: wrap;
173 + align-items: flex-end;
174 + gap: 16px;
175 + }
176 +
177 + .filter-item {
178 + display: flex;
179 + flex-direction: column;
180 + gap: 4px;
181 +
182 + .filter-label {
183 + font-size: 0.75rem;
184 + text-transform: uppercase;
185 + letter-spacing: 0.5px;
186 + opacity: 0.7;
187 + }
188 +
189 + &.search-item {
190 + flex: 1;
191 + min-width: 200px;
192 + }
193 +
194 + &.toggle-item {
195 + padding-bottom: 4px;
196 + }
197 + }
198 }
199 </style>
frontend/src/components/patchTuesday/PatchTuesdayList.vue
+100 -110
@@ -1,19 +1,13 @@
1 <template>
2 <div class="patch-tuesday-list">
3 <!-- Header -->
4 - <div class="header flex items-center justify-between gap-4 mb-4">
4 + <div class="header mb-4 flex items-center justify-between gap-4">
5 <div class="flex items-center gap-3">
6 <Icon :name="CalendarIcon" :size="26" class="text-primary-color" />
7 <h1 class="text-2xl font-bold">Microsoft Patch Tuesday</h1>
8 </div>
9 <div class="flex items-center gap-2">
10 - <n-button
11 - :loading="loading"
12 - :disabled="loading"
13 - type="primary"
14 - secondary
15 - @click="fetchData"
16 - >
10 + <n-button :loading="loading" :disabled="loading" type="primary" secondary @click="fetchData">
11 <template #icon>
12 <Icon :name="RefreshIcon" />
13 </template>
@@ -54,13 +48,8 @@
48 </n-spin>
49
50 <!-- Pagination -->
57 - <div v-if="filteredItems.length > pageSize" class="flex justify-center mt-4">
58 - <n-pagination
59 - v-model:page="currentPage"
60 - :page-count="totalPages"
61 - :page-size="pageSize"
62 - show-quick-jumper
63 - />
51 + <div v-if="filteredItems.length > pageSize" class="mt-4 flex justify-center">
52 + <n-pagination v-model:page="currentPage" :page-count="totalPages" :page-size="pageSize" show-quick-jumper />
53 </div>
54
55 <!-- Detail Drawer -->
@@ -73,6 +62,7 @@
62 </template>
63
64 <script setup lang="ts">
65 +// TODO: refactor
66 import type { PatchTuesdayFilters as FiltersType } from "./types"
67 import type { PatchTuesdayItem, PatchTuesdaySummary } from "@/types/patchTuesday.d"
68 import { NButton, NDrawer, NDrawerContent, NEmpty, NPagination, NSpin, useMessage } from "naive-ui"
@@ -100,138 +90,138 @@ const showDetail = ref(false)
90 const selectedItem = ref<PatchTuesdayItem | null>(null)
91
92 const filters = ref<FiltersType>({
103 - cycle: "",
104 - priority: null,
105 - family: null,
106 - severity: null,
107 - searchQuery: "",
108 - kevOnly: false
93 + cycle: "",
94 + priority: null,
95 + family: null,
96 + severity: null,
97 + searchQuery: "",
98 + kevOnly: false
99 })
100
101 // Computed
102 const availableFamilies = computed(() => {
113 - if (!summary.value?.by_family) return []
114 - return Object.keys(summary.value.by_family).sort()
103 + if (!summary.value?.by_family) return []
104 + return Object.keys(summary.value.by_family).sort()
105 })
106
107 const filteredItems = computed(() => {
118 - let result = [...items.value]
119 -
120 - // Filter by priority
121 - if (filters.value.priority) {
122 - result = result.filter(item => item.prioritization.priority === filters.value.priority)
123 - }
124 -
125 - // Filter by family
126 - if (filters.value.family) {
127 - result = result.filter(item => item.affected.family === filters.value.family)
128 - }
129 -
130 - // Filter by severity
131 - if (filters.value.severity) {
132 - result = result.filter(item => item.severity?.toLowerCase() === filters.value.severity?.toLowerCase())
133 - }
134 -
135 - // Filter KEV only
136 - if (filters.value.kevOnly) {
137 - result = result.filter(item => item.kev.in_kev)
138 - }
139 -
140 - // Search filter
141 - if (filters.value.searchQuery) {
142 - const query = filters.value.searchQuery.toLowerCase()
143 - result = result.filter(
144 - item =>
145 - item.cve.toLowerCase().includes(query) ||
146 - item.title?.toLowerCase().includes(query) ||
147 - item.affected.product.toLowerCase().includes(query)
148 - )
149 - }
150 -
151 - return result
108 + let result = [...items.value]
109 +
110 + // Filter by priority
111 + if (filters.value.priority) {
112 + result = result.filter(item => item.prioritization.priority === filters.value.priority)
113 + }
114 +
115 + // Filter by family
116 + if (filters.value.family) {
117 + result = result.filter(item => item.affected.family === filters.value.family)
118 + }
119 +
120 + // Filter by severity
121 + if (filters.value.severity) {
122 + result = result.filter(item => item.severity?.toLowerCase() === filters.value.severity?.toLowerCase())
123 + }
124 +
125 + // Filter KEV only
126 + if (filters.value.kevOnly) {
127 + result = result.filter(item => item.kev.in_kev)
128 + }
129 +
130 + // Search filter
131 + if (filters.value.searchQuery) {
132 + const query = filters.value.searchQuery.toLowerCase()
133 + result = result.filter(
134 + item =>
135 + item.cve.toLowerCase().includes(query) ||
136 + item.title?.toLowerCase().includes(query) ||
137 + item.affected.product.toLowerCase().includes(query)
138 + )
139 + }
140 +
141 + return result
142 })
143
144 const totalPages = computed(() => Math.ceil(filteredItems.value.length / pageSize))
145
146 const paginatedItems = computed(() => {
157 - const start = (currentPage.value - 1) * pageSize
158 - return filteredItems.value.slice(start, start + pageSize)
147 + const start = (currentPage.value - 1) * pageSize
148 + return filteredItems.value.slice(start, start + pageSize)
149 })
150
151 // Methods
152 async function fetchCycles() {
163 - try {
164 - const response = await patchTuesdayApi.getCycles()
165 - if (response.data.success) {
166 - availableCycles.value = response.data.cycles
167 - if (!filters.value.cycle && response.data.current_cycle) {
168 - filters.value.cycle = response.data.current_cycle
169 - }
170 - }
171 - } catch (error) {
172 - console.error("Failed to fetch cycles:", error)
173 - }
153 + try {
154 + const response = await patchTuesdayApi.getCycles()
155 + if (response.data.success) {
156 + availableCycles.value = response.data.cycles
157 + if (!filters.value.cycle && response.data.current_cycle) {
158 + filters.value.cycle = response.data.current_cycle
159 + }
160 + }
161 + } catch (error) {
162 + console.error("Failed to fetch cycles:", error)
163 + }
164 }
165
166 async function fetchData() {
177 - if (!filters.value.cycle) return
178 -
179 - loading.value = true
180 - try {
181 - const response = await patchTuesdayApi.getPatchTuesday({
182 - cycle: filters.value.cycle,
183 - include_epss: true,
184 - include_kev: true
185 - })
186 -
187 - if (response.data.success) {
188 - items.value = response.data.items
189 - summary.value = response.data.summary
190 - } else {
191 - message.error(response.data.message || "Failed to fetch Patch Tuesday data")
192 - }
193 - } catch (error: unknown) {
194 - const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"
195 - message.error(`Error fetching data: ${errorMessage}`)
196 - } finally {
197 - loading.value = false
198 - }
167 + if (!filters.value.cycle) return
168 +
169 + loading.value = true
170 + try {
171 + const response = await patchTuesdayApi.getPatchTuesday({
172 + cycle: filters.value.cycle,
173 + include_epss: true,
174 + include_kev: true
175 + })
176 +
177 + if (response.data.success) {
178 + items.value = response.data.items
179 + summary.value = response.data.summary
180 + } else {
181 + message.error(response.data.message || "Failed to fetch Patch Tuesday data")
182 + }
183 + } catch (error: unknown) {
184 + const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"
185 + message.error(`Error fetching data: ${errorMessage}`)
186 + } finally {
187 + loading.value = false
188 + }
189 }
190
191 function handleFiltersChange() {
202 - currentPage.value = 1
203 - if (filters.value.cycle) {
204 - fetchData()
205 - }
192 + currentPage.value = 1
193 + if (filters.value.cycle) {
194 + fetchData()
195 + }
196 }
197
198 function openItemDetail(item: PatchTuesdayItem) {
209 - selectedItem.value = item
210 - showDetail.value = true
199 + selectedItem.value = item
200 + showDetail.value = true
201 }
202
203 // Watch for cycle changes
204 watch(
215 - () => filters.value.cycle,
216 - newCycle => {
217 - if (newCycle) {
218 - fetchData()
219 - }
220 - }
205 + () => filters.value.cycle,
206 + newCycle => {
207 + if (newCycle) {
208 + fetchData()
209 + }
210 + }
211 )
212
213 // Lifecycle
214 onMounted(async () => {
225 - await fetchCycles()
215 + await fetchCycles()
216 })
217 </script>
218
219 <style scoped lang="scss">
220 .patch-tuesday-list {
231 - .items-grid {
232 - display: grid;
233 - grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
234 - gap: 16px;
235 - }
221 + .items-grid {
222 + display: grid;
223 + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
224 + gap: 16px;
225 + }
226 }
227 </style>
frontend/src/components/patchTuesday/PatchTuesdayPriorityBadge.vue
+28 -27
@@ -5,48 +5,49 @@
5 </template>
6
7 <script setup lang="ts">
8 +// TODO: refactor
9 import { NTag } from "naive-ui"
10 import { computed } from "vue"
11 import { PriorityLevel } from "@/types/patchTuesday.d"
12
13 const props = defineProps<{
13 - priority: PriorityLevel | string
14 + priority: PriorityLevel | string
15 }>()
16
17 const tagType = computed(() => {
17 - switch (props.priority) {
18 - case PriorityLevel.P0:
19 - return "error"
20 - case PriorityLevel.P1:
21 - return "warning"
22 - case PriorityLevel.P2:
23 - return "info"
24 - case PriorityLevel.P3:
25 - return "success"
26 - default:
27 - return "default"
28 - }
18 + switch (props.priority) {
19 + case PriorityLevel.P0:
20 + return "error"
21 + case PriorityLevel.P1:
22 + return "warning"
23 + case PriorityLevel.P2:
24 + return "info"
25 + case PriorityLevel.P3:
26 + return "success"
27 + default:
28 + return "default"
29 + }
30 })
31
32 const label = computed(() => {
32 - switch (props.priority) {
33 - case PriorityLevel.P0:
34 - return "P0"
35 - case PriorityLevel.P1:
36 - return "P1"
37 - case PriorityLevel.P2:
38 - return "P2"
39 - case PriorityLevel.P3:
40 - return "P3"
41 - default:
42 - return props.priority
43 - }
33 + switch (props.priority) {
34 + case PriorityLevel.P0:
35 + return "P0"
36 + case PriorityLevel.P1:
37 + return "P1"
38 + case PriorityLevel.P2:
39 + return "P2"
40 + case PriorityLevel.P3:
41 + return "P3"
42 + default:
43 + return props.priority
44 + }
45 })
46 </script>
47
48 <style scoped lang="scss">
49 .priority-badge {
49 - font-weight: 600;
50 - font-size: 0.7rem;
50 + font-weight: 600;
51 + font-size: 0.7rem;
52 }
53 </style>
frontend/src/components/patchTuesday/PatchTuesdayStats.vue
+97 -93
@@ -82,15 +82,18 @@
82 <n-space :size="24">
83 <span class="info-item">
84 <Icon :name="CalendarIcon" class="mr-1" />
85 - Patch Tuesday: <strong>{{ formatDate(summary.patch_tuesday_date) }}</strong>
85 + Patch Tuesday:
86 + <strong>{{ formatDate(summary.patch_tuesday_date) }}</strong>
87 </span>
88 <span class="info-item">
89 <Icon :name="DatabaseIcon" class="mr-1" />
89 - Total Records: <strong>{{ summary.total_records }}</strong>
90 + Total Records:
91 + <strong>{{ summary.total_records }}</strong>
92 </span>
93 <span class="info-item">
94 <Icon :name="ClockIcon" class="mr-1" />
93 - Generated: <strong>{{ formatDateTime(summary.generated_utc) }}</strong>
95 + Generated:
96 + <strong>{{ formatDateTime(summary.generated_utc) }}</strong>
97 </span>
98 </n-space>
99 </div>
@@ -98,13 +101,14 @@
101 </template>
102
103 <script setup lang="ts">
104 +// TODO: refactor
105 import type { PatchTuesdaySummary } from "@/types/patchTuesday.d"
106 import { NCard, NGrid, NGridItem, NSpace } from "naive-ui"
107 import Icon from "@/components/common/Icon.vue"
108
109 defineProps<{
106 - summary: PatchTuesdaySummary | null
107 - loading?: boolean
110 + summary: PatchTuesdaySummary | null
111 + loading?: boolean
112 }>()
113 const AlertIcon = "carbon:warning"
114 const CalendarIcon = "carbon:calendar"
@@ -116,101 +120,101 @@ const ShieldIcon = "carbon:security"
120 const UrgentIcon = "carbon:warning-hex"
121
122 function formatDate(dateStr: string): string {
119 - if (!dateStr) return "-"
120 - return new Date(dateStr).toLocaleDateString("en-US", {
121 - year: "numeric",
122 - month: "long",
123 - day: "numeric"
124 - })
123 + if (!dateStr) return "-"
124 + return new Date(dateStr).toLocaleDateString("en-US", {
125 + year: "numeric",
126 + month: "long",
127 + day: "numeric"
128 + })
129 }
130
131 function formatDateTime(dateStr: string): string {
128 - if (!dateStr) return "-"
129 - return new Date(dateStr).toLocaleString("en-US", {
130 - month: "short",
131 - day: "numeric",
132 - hour: "2-digit",
133 - minute: "2-digit"
134 - })
132 + if (!dateStr) return "-"
133 + return new Date(dateStr).toLocaleString("en-US", {
134 + month: "short",
135 + day: "numeric",
136 + hour: "2-digit",
137 + minute: "2-digit"
138 + })
139 }
140 </script>
141
142 <style scoped lang="scss">
143 .patch-tuesday-stats {
140 - .stat-card {
141 - border-radius: 8px;
142 - background: var(--bg-secondary-color);
143 -
144 - .stat-content {
145 - display: flex;
146 - align-items: center;
147 - gap: 12px;
148 - }
149 -
150 - .stat-icon {
151 - width: 48px;
152 - height: 48px;
153 - border-radius: 8px;
154 - display: flex;
155 - align-items: center;
156 - justify-content: center;
157 -
158 - &.total {
159 - background: rgba(99, 102, 241, 0.15);
160 - color: #6366f1;
161 - }
162 -
163 - &.p0 {
164 - background: rgba(239, 68, 68, 0.15);
165 - color: #ef4444;
166 - }
167 -
168 - &.p1 {
169 - background: rgba(249, 115, 22, 0.15);
170 - color: #f97316;
171 - }
172 -
173 - &.p2 {
174 - background: rgba(234, 179, 8, 0.15);
175 - color: #eab308;
176 - }
177 -
178 - &.p3 {
179 - background: rgba(34, 197, 94, 0.15);
180 - color: #22c55e;
181 - }
182 - }
183 -
184 - .stat-info {
185 - display: flex;
186 - flex-direction: column;
187 -
188 - .stat-value {
189 - font-size: 1.5rem;
190 - font-weight: 700;
191 - line-height: 1.2;
192 - }
193 -
194 - .stat-label {
195 - font-size: 0.75rem;
196 - opacity: 0.7;
197 - text-transform: uppercase;
198 - letter-spacing: 0.5px;
199 - }
200 - }
201 - }
202 -
203 - .info-bar {
204 - padding: 12px 16px;
205 - background: var(--bg-secondary-color);
206 - border-radius: 8px;
207 -
208 - .info-item {
209 - display: inline-flex;
210 - align-items: center;
211 - font-size: 0.875rem;
212 - opacity: 0.8;
213 - }
214 - }
144 + .stat-card {
145 + border-radius: 8px;
146 + background: var(--bg-secondary-color);
147 +
148 + .stat-content {
149 + display: flex;
150 + align-items: center;
151 + gap: 12px;
152 + }
153 +
154 + .stat-icon {
155 + width: 48px;
156 + height: 48px;
157 + border-radius: 8px;
158 + display: flex;
159 + align-items: center;
160 + justify-content: center;
161 +
162 + &.total {
163 + background: rgba(99, 102, 241, 0.15);
164 + color: #6366f1;
165 + }
166 +
167 + &.p0 {
168 + background: rgba(239, 68, 68, 0.15);
169 + color: #ef4444;
170 + }
171 +
172 + &.p1 {
173 + background: rgba(249, 115, 22, 0.15);
174 + color: #f97316;
175 + }
176 +
177 + &.p2 {
178 + background: rgba(234, 179, 8, 0.15);
179 + color: #eab308;
180 + }
181 +
182 + &.p3 {
183 + background: rgba(34, 197, 94, 0.15);
184 + color: #22c55e;
185 + }
186 + }
187 +
188 + .stat-info {
189 + display: flex;
190 + flex-direction: column;
191 +
192 + .stat-value {
193 + font-size: 1.5rem;
194 + font-weight: 700;
195 + line-height: 1.2;
196 + }
197 +
198 + .stat-label {
199 + font-size: 0.75rem;
200 + opacity: 0.7;
201 + text-transform: uppercase;
202 + letter-spacing: 0.5px;
203 + }
204 + }
205 + }
206 +
207 + .info-bar {
208 + padding: 12px 16px;
209 + background: var(--bg-secondary-color);
210 + border-radius: 8px;
211 +
212 + .info-item {
213 + display: inline-flex;
214 + align-items: center;
215 + font-size: 0.875rem;
216 + opacity: 0.8;
217 + }
218 + }
219 }
220 </style>
frontend/src/components/patchTuesday/types.d.ts
+11 -10
@@ -1,23 +1,24 @@
1 import type { PatchTuesdayItem, PatchTuesdaySummary, PriorityLevel } from "@/types/patchTuesday.d"
2
3 +// TODO: refactor
4 export interface PatchTuesdayFilters {
4 - cycle: string
5 - priority: PriorityLevel | null
6 - family: string | null
7 - severity: string | null
8 - searchQuery: string
9 - kevOnly: boolean
5 + cycle: string
6 + priority: PriorityLevel | null
7 + family: string | null
8 + severity: string | null
9 + searchQuery: string
10 + kevOnly: boolean
11 }
12
13 export interface PatchTuesdayListEmits {
13 - (e: "item-click", item: PatchTuesdayItem): void
14 + (e: "item-click", item: PatchTuesdayItem): void
15 }
16
17 export interface PatchTuesdayStatsProps {
17 - summary: PatchTuesdaySummary | null
18 - loading?: boolean
18 + summary: PatchTuesdaySummary | null
19 + loading?: boolean
20 }
21
22 export interface PatchTuesdayItemProps {
22 - item: PatchTuesdayItem
23 + item: PatchTuesdayItem
24 }
frontend/src/components/reportCreation/Panels.vue
+1 -1
@@ -208,7 +208,7 @@ import draggable from "vuedraggable"
208 import Api from "@/api"
209 import Icon from "@/components/common/Icon.vue"
210 import { useSettingsStore } from "@/stores/settings"
211 -import { formatDate } from "@/utils"
211 +import { formatDate } from "@/utils/format"
212 import * as defaultSettings from "./defaultSettings"
213 import PrintSettings from "./PrintSettings.vue"
214
frontend/src/components/sca/GenerateReportForm.vue
+43 -50
@@ -21,19 +21,11 @@
21 <n-divider>Filters (Optional)</n-divider>
22
23 <n-form-item label="Agent Name" path="agent_name">
24 - <n-input
25 - v-model:value="formValue.agent_name"
26 - placeholder="Filter by specific agent"
27 - clearable
28 - />
24 + <n-input v-model:value="formValue.agent_name" placeholder="Filter by specific agent" clearable />
25 </n-form-item>
26
27 <n-form-item label="Policy ID" path="policy_id">
32 - <n-input
33 - v-model:value="formValue.policy_id"
34 - placeholder="Filter by specific policy ID"
35 - clearable
36 - />
28 + <n-input v-model:value="formValue.policy_id" placeholder="Filter by specific policy ID" clearable />
29 </n-form-item>
30
31 <n-form-item label="Minimum Score" path="min_score">
@@ -71,6 +63,7 @@
63 </template>
64
65 <script setup lang="ts">
66 +// TODO: refactor
67 import type { FormInst, FormRules } from "naive-ui"
68 import type { SCAReportGenerateRequest } from "@/types/sca.d"
69 import { NButton, NDivider, NForm, NFormItem, NInput, NInputNumber, NSelect, NSpace } from "naive-ui"
@@ -80,65 +73,65 @@ import Icon from "@/components/common/Icon.vue"
73 defineProps<Props>()
74
75 const emit = defineEmits<{
83 - generate: [request: SCAReportGenerateRequest]
84 - cancel: []
76 + generate: [request: SCAReportGenerateRequest]
77 + cancel: []
78 }>()
79
80 const GenerateIcon = "carbon:document-add"
81
82 interface Props {
90 - customers: Array<{ label: string; value: string }>
91 - loading?: boolean
83 + customers: Array<{ label: string; value: string }>
84 + loading?: boolean
85 }
86
87 const formRef = ref<FormInst | null>(null)
88 const formValue = ref<SCAReportGenerateRequest>({
96 - customer_code: "",
97 - report_name: undefined,
98 - agent_name: undefined,
99 - policy_id: undefined,
100 - min_score: undefined,
101 - max_score: undefined
89 + customer_code: "",
90 + report_name: undefined,
91 + agent_name: undefined,
92 + policy_id: undefined,
93 + min_score: undefined,
94 + max_score: undefined
95 })
96
97 const rules: FormRules = {
105 - customer_code: [
106 - {
107 - required: true,
108 - message: "Please select a customer",
109 - trigger: ["blur", "change"]
110 - }
111 - ]
98 + customer_code: [
99 + {
100 + required: true,
101 + message: "Please select a customer",
102 + trigger: ["blur", "change"]
103 + }
104 + ]
105 }
106
107 async function handleGenerate() {
115 - try {
116 - await formRef.value?.validate()
117 -
118 - // Clean up empty optional fields
119 - const request: SCAReportGenerateRequest = {
120 - customer_code: formValue.value.customer_code
121 - }
122 -
123 - if (formValue.value.report_name) request.report_name = formValue.value.report_name
124 - if (formValue.value.agent_name) request.agent_name = formValue.value.agent_name
125 - if (formValue.value.policy_id) request.policy_id = formValue.value.policy_id
126 - if (formValue.value.min_score !== undefined && formValue.value.min_score !== null) {
127 - request.min_score = formValue.value.min_score
128 - }
129 - if (formValue.value.max_score !== undefined && formValue.value.max_score !== null) {
130 - request.max_score = formValue.value.max_score
131 - }
132 -
133 - emit("generate", request)
134 - } catch (error) {
135 - console.error("Form validation failed:", error)
136 - }
108 + try {
109 + await formRef.value?.validate()
110 +
111 + // Clean up empty optional fields
112 + const request: SCAReportGenerateRequest = {
113 + customer_code: formValue.value.customer_code
114 + }
115 +
116 + if (formValue.value.report_name) request.report_name = formValue.value.report_name
117 + if (formValue.value.agent_name) request.agent_name = formValue.value.agent_name
118 + if (formValue.value.policy_id) request.policy_id = formValue.value.policy_id
119 + if (formValue.value.min_score !== undefined && formValue.value.min_score !== null) {
120 + request.min_score = formValue.value.min_score
121 + }
122 + if (formValue.value.max_score !== undefined && formValue.value.max_score !== null) {
123 + request.max_score = formValue.value.max_score
124 + }
125 +
126 + emit("generate", request)
127 + } catch (error) {
128 + console.error("Form validation failed:", error)
129 + }
130 }
131 </script>
132
133 <style scoped>
134 .w-full {
142 - width: 100%;
135 + width: 100%;
136 }
137 </style>
frontend/src/components/sca/List.vue
+4 -1
@@ -61,7 +61,8 @@
61 </template>
62
63 <script setup lang="ts">
64 -import type { ScaOverviewFilter, ScaOverviewFilterTypes } from "./types"
64 +// TODO: refactor
65 +import type { ScaOverviewFilter, ScaOverviewFilterTypes } from "./types.d"
66 import type { AgentScaOverviewItem, ScaOverviewQuery } from "@/types/sca.d"
67 import { useResizeObserver, useStorage, watchDebounced } from "@vueuse/core"
68 import axios from "axios"
@@ -190,6 +191,8 @@ watchDebounced(
191
192 useResizeObserver(header, entries => {
193 const entry = entries[0]
194 + if (!entry) return
195 +
196 const { width } = entry.contentRect
197
198 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/sca/ListFilters.vue
+2 -1
@@ -128,6 +128,7 @@
128 </template>
129
130 <script setup lang="ts">
131 +// TODO: refactor
132 import type { ScaOverviewFilter, ScaOverviewFilterTypes } from "./types.d"
133 import type { Agent } from "@/types/agents.d"
134 import type { Customer } from "@/types/customers.d"
@@ -206,7 +207,7 @@ function setFilter(newFilters: ScaOverviewFilter[]) {
207 const filterIndex = filters.value.findIndex(o => o.type === newFilter.type)
208
209 if (filterIndex !== -1) {
209 - if (newFilter.value) {
210 + if (newFilter.value && filters.value[filterIndex]) {
211 filters.value[filterIndex].value = newFilter.value
212 } else {
213 delFilter(newFilter.type)
frontend/src/components/sca/SCAReports.vue
+192 -202
@@ -2,7 +2,7 @@
2 <div class="sca-reports">
3 <div class="header mb-6 flex items-center justify-between">
4 <div>
5 - <h2 class="text-2xl font-bold mb-2">SCA Reports</h2>
5 + <h2 class="mb-2 text-2xl font-bold">SCA Reports</h2>
6 <p class="text-secondary">Generate, manage, and download Security Configuration Assessment reports</p>
7 </div>
8 <n-button type="primary" size="large" @click="showGenerateModal = true">
@@ -50,7 +50,7 @@
50 v-model:show="showGenerateModal"
51 preset="card"
52 title="Generate SCA Report"
53 - style="width: 600px; max-width: 90vw;"
53 + style="width: 600px; max-width: 90vw"
54 :closable="true"
55 >
56 <GenerateReportForm
@@ -78,12 +78,15 @@
78 </template>
79
80 <script setup lang="ts">
81 +// TODO: refactor
82 import type { DataTableColumns } from "naive-ui"
83 +import type { Customer } from "@/types/customers"
84 import type { SCAReport, SCAReportGenerateRequest } from "@/types/sca.d"
85 import { NButton, NCard, NDataTable, NModal, NSelect, NSpace, NTag, NTooltip, useMessage } from "naive-ui"
86 import { computed, h, onMounted, ref } from "vue"
87 import Api from "@/api"
88 import Icon from "@/components/common/Icon.vue"
89 +import { useSettingsStore } from "@/stores/settings"
90 import { formatBytes, formatDate } from "@/utils/format"
91 import GenerateReportForm from "./GenerateReportForm.vue"
92
@@ -96,6 +99,7 @@ const ErrorIcon = "carbon:warning-filled"
99 const LoadingIcon = "eos-icons:loading"
100
101 const message = useMessage()
102 +const dFormats = useSettingsStore().dateFormat
103
104 const loading = ref(false)
105 const generating = ref(false)
@@ -109,240 +113,226 @@ const customers = ref<Array<{ label: string; value: string }>>([])
113 const customerOptions = computed(() => [...customers.value])
114
115 const pagination = {
112 - pageSize: 20,
113 - showSizePicker: true,
114 - pageSizes: [10, 20, 50, 100]
116 + pageSize: 20,
117 + showSizePicker: true,
118 + pageSizes: [10, 20, 50, 100]
119 }
120
121 const columns: DataTableColumns<SCAReport> = [
118 - {
119 - title: "Report Name",
120 - key: "report_name",
121 - ellipsis: {
122 - tooltip: true
123 - }
124 - },
125 - {
126 - title: "Customer",
127 - key: "customer_code",
128 - width: 120
129 - },
130 - {
131 - title: "Status",
132 - key: "status",
133 - width: 120,
134 - render: (row: SCAReport) => {
135 - const statusMap = {
136 - completed: { type: "success", icon: CheckIcon, text: "Completed" },
137 - processing: { type: "warning", icon: LoadingIcon, text: "Processing" },
138 - failed: { type: "error", icon: ErrorIcon, text: "Failed" }
139 - }
140 - const status = statusMap[row.status]
141 - return h(
142 - NTag,
143 - { type: status.type as any, size: "small" },
144 - {
145 - default: () => status.text,
146 - icon: () => h(Icon, { name: status.icon, size: "14" })
147 - }
148 - )
149 - }
150 - },
151 - {
152 - title: "Policies",
153 - key: "total_policies",
154 - width: 100,
155 - render: (row: SCAReport) => row.total_policies.toLocaleString()
156 - },
157 - {
158 - title: "Checks",
159 - key: "total_checks",
160 - width: 140,
161 - render: (row: SCAReport) => {
162 - return h(
163 - NTooltip,
164 - {},
165 - {
166 - trigger: () => row.total_checks.toLocaleString(),
167 - default: () =>
168 - h("div", {}, [
169 - h("div", {}, `Passed: ${row.passed_count}`),
170 - h("div", {}, `Failed: ${row.failed_count}`),
171 - h("div", {}, `Invalid: ${row.invalid_count}`)
172 - ])
173 - }
174 - )
175 - }
176 - },
177 - {
178 - title: "File Size",
179 - key: "file_size",
180 - width: 120,
181 - render: (row: SCAReport) => formatBytes(row.file_size)
182 - },
183 - {
184 - title: "Generated",
185 - key: "generated_at",
186 - width: 180,
187 - render: (row: SCAReport) => formatDate(row.generated_at)
188 - },
189 - {
190 - title: "Actions",
191 - key: "actions",
192 - width: 140,
193 - render: (row: SCAReport) => {
194 - return h(
195 - NSpace,
196 - { size: "small" },
197 - {
198 - default: () => [
199 - row.status === "completed"
200 - ? h(
201 - NButton,
202 - {
203 - size: "small",
204 - type: "primary",
205 - onClick: () => handleDownload(row)
206 - },
207 - {
208 - icon: () => h(Icon, { name: DownloadIcon })
209 - }
210 - )
211 - : null,
212 - h(
213 - NButton,
214 - {
215 - size: "small",
216 - type: "error",
217 - onClick: () => handleDeleteClick(row)
218 - },
219 - {
220 - icon: () => h(Icon, { name: DeleteIcon })
221 - }
222 - )
223 - ]
224 - }
225 - )
226 - }
227 - }
122 + {
123 + title: "Report Name",
124 + key: "report_name",
125 + ellipsis: {
126 + tooltip: true
127 + }
128 + },
129 + {
130 + title: "Customer",
131 + key: "customer_code",
132 + width: 120
133 + },
134 + {
135 + title: "Status",
136 + key: "status",
137 + width: 120,
138 + render: (row: SCAReport) => {
139 + const statusMap = {
140 + completed: { type: "success", icon: CheckIcon, text: "Completed" },
141 + processing: { type: "warning", icon: LoadingIcon, text: "Processing" },
142 + failed: { type: "error", icon: ErrorIcon, text: "Failed" }
143 + }
144 + const status = statusMap[row.status]
145 + return h(
146 + NTag,
147 + { type: status.type as any, size: "small" },
148 + {
149 + default: () => status.text,
150 + icon: () => h(Icon, { name: status.icon, size: 14 })
151 + }
152 + )
153 + }
154 + },
155 + {
156 + title: "Policies",
157 + key: "total_policies",
158 + width: 100,
159 + render: (row: SCAReport) => row.total_policies.toLocaleString()
160 + },
161 + {
162 + title: "Checks",
163 + key: "total_checks",
164 + width: 140,
165 + render: (row: SCAReport) => {
166 + return h(
167 + NTooltip,
168 + {},
169 + {
170 + trigger: () => row.total_checks.toLocaleString(),
171 + default: () =>
172 + h("div", {}, [
173 + h("div", {}, `Passed: ${row.passed_count}`),
174 + h("div", {}, `Failed: ${row.failed_count}`),
175 + h("div", {}, `Invalid: ${row.invalid_count}`)
176 + ])
177 + }
178 + )
179 + }
180 + },
181 + {
182 + title: "File Size",
183 + key: "file_size",
184 + width: 120,
185 + render: (row: SCAReport) => formatBytes(row.file_size)
186 + },
187 + {
188 + title: "Generated",
189 + key: "generated_at",
190 + width: 180,
191 + render: (row: SCAReport) => `${formatDate(row.generated_at, dFormats.datetime)}`
192 + },
193 + {
194 + title: "Actions",
195 + key: "actions",
196 + width: 140,
197 + render: (row: SCAReport) => {
198 + return h(
199 + NSpace,
200 + { size: "small" },
201 + {
202 + default: () => [
203 + row.status === "completed"
204 + ? h(
205 + NButton,
206 + {
207 + size: "small",
208 + type: "primary",
209 + onClick: () => handleDownload(row)
210 + },
211 + {
212 + icon: () => h(Icon, { name: DownloadIcon })
213 + }
214 + )
215 + : null,
216 + h(
217 + NButton,
218 + {
219 + size: "small",
220 + type: "error",
221 + onClick: () => handleDeleteClick(row)
222 + },
223 + {
224 + icon: () => h(Icon, { name: DeleteIcon })
225 + }
226 + )
227 + ]
228 + }
229 + )
230 + }
231 + }
232 ]
233
234 async function loadReports() {
231 - loading.value = true
232 - try {
233 - const response = await Api.sca.listReports(filterCustomerCode.value || undefined)
234 - if (response.data.success) {
235 - reports.value = response.data.reports
236 - } else {
237 - message.error(response.data.message || "Failed to load reports")
238 - }
239 - } catch (error: any) {
240 - message.error(error?.response?.data?.detail || "Failed to load reports")
241 - } finally {
242 - loading.value = false
243 - }
235 + loading.value = true
236 + try {
237 + const response = await Api.sca.listReports(filterCustomerCode.value || undefined)
238 + if (response.data.success) {
239 + reports.value = response.data.reports
240 + } else {
241 + message.error(response.data.message || "Failed to load reports")
242 + }
243 + } catch (error: any) {
244 + message.error(error?.response?.data?.detail || "Failed to load reports")
245 + } finally {
246 + loading.value = false
247 + }
248 }
249
250 async function loadCustomers() {
247 - try {
248 - const response = await Api.customers.getCustomers()
249 - let customerData = response.data
251 + try {
252 + const response = await Api.customers.getCustomers()
253 + const customerData = response.data.customers || []
254
251 - if (customerData.data) {
252 - customerData = customerData.data
253 - }
254 -
255 - if (Array.isArray(customerData)) {
256 - customers.value = customerData.map((c: any) => ({
257 - label: `${c.customer_name || c.name || c.customer_code} (${c.customer_code})`,
258 - value: c.customer_code
259 - }))
260 - } else if (customerData.customers && Array.isArray(customerData.customers)) {
261 - customers.value = customerData.customers.map((c: any) => ({
262 - label: `${c.customer_name || c.name || c.customer_code} (${c.customer_code})`,
263 - value: c.customer_code
264 - }))
265 - } else {
266 - console.warn("Unexpected customer data structure:", customerData)
267 - message.warning("Customer data format unexpected")
268 - }
269 - } catch (error: any) {
270 - console.error("Failed to load customers:", error)
271 - message.error("Failed to load customers list")
272 - }
255 + customers.value = customerData.map((c: Customer) => ({
256 + label: `${c.customer_name} (${c.customer_code})`,
257 + value: c.customer_code
258 + }))
259 + } catch (error: any) {
260 + console.error("Failed to load customers:", error)
261 + message.error("Failed to load customers list")
262 + }
263 }
264
265 async function handleGenerateReport(request: SCAReportGenerateRequest) {
276 - generating.value = true
277 - try {
278 - const response = await Api.sca.generateReport(request)
266 + generating.value = true
267 + try {
268 + const response = await Api.sca.generateReport(request)
269
280 - if (response.data.success) {
281 - message.success(response.data.message)
282 - showGenerateModal.value = false
283 - await loadReports()
284 - } else {
285 - message.error(response.data.error || "Failed to generate report")
286 - }
287 - } catch (error: any) {
288 - message.error(error?.response?.data?.detail || "Failed to generate report")
289 - } finally {
290 - generating.value = false
291 - }
270 + if (response.data.success) {
271 + message.success(response.data.message)
272 + showGenerateModal.value = false
273 + await loadReports()
274 + } else {
275 + message.error(response.data.error || "Failed to generate report")
276 + }
277 + } catch (error: any) {
278 + message.error(error?.response?.data?.detail || "Failed to generate report")
279 + } finally {
280 + generating.value = false
281 + }
282 }
283
284 async function handleDownload(report: SCAReport) {
295 - try {
296 - const response = await Api.sca.downloadReport(report.id)
285 + try {
286 + const response = await Api.sca.downloadReport(report.id)
287
298 - const url = window.URL.createObjectURL(new Blob([response.data]))
299 - const link = document.createElement("a")
300 - link.href = url
301 - link.setAttribute("download", report.file_name)
302 - document.body.appendChild(link)
303 - link.click()
304 - link.remove()
305 - window.URL.revokeObjectURL(url)
288 + const url = window.URL.createObjectURL(new Blob([response.data]))
289 + const link = document.createElement("a")
290 + link.href = url
291 + link.setAttribute("download", report.file_name)
292 + document.body.appendChild(link)
293 + link.click()
294 + link.remove()
295 + window.URL.revokeObjectURL(url)
296
307 - message.success("Report downloaded successfully")
308 - } catch (error: any) {
309 - message.error(error?.response?.data?.detail || "Failed to download report")
310 - }
297 + message.success("Report downloaded successfully")
298 + } catch (error: any) {
299 + message.error(error?.response?.data?.detail || "Failed to download report")
300 + }
301 }
302
303 function handleDeleteClick(report: SCAReport) {
314 - reportToDelete.value = report
315 - showDeleteModal.value = true
304 + reportToDelete.value = report
305 + showDeleteModal.value = true
306 }
307
308 async function confirmDelete() {
319 - if (!reportToDelete.value) return
309 + if (!reportToDelete.value) return
310
321 - try {
322 - const response = await Api.sca.deleteReport(reportToDelete.value.id)
311 + try {
312 + const response = await Api.sca.deleteReport(reportToDelete.value.id)
313
324 - if (response.data.success) {
325 - message.success(response.data.message)
326 - await loadReports()
327 - } else {
328 - message.error("Failed to delete report")
329 - }
330 - } catch (error: any) {
331 - message.error(error?.response?.data?.detail || "Failed to delete report")
332 - } finally {
333 - showDeleteModal.value = false
334 - reportToDelete.value = null
335 - }
314 + if (response.data.success) {
315 + message.success(response.data.message)
316 + await loadReports()
317 + } else {
318 + message.error("Failed to delete report")
319 + }
320 + } catch (error: any) {
321 + message.error(error?.response?.data?.detail || "Failed to delete report")
322 + } finally {
323 + showDeleteModal.value = false
324 + reportToDelete.value = null
325 + }
326 }
327
328 onMounted(() => {
339 - loadReports()
340 - loadCustomers()
329 + loadReports()
330 + loadCustomers()
331 })
332 </script>
333
334 <style scoped>
335 .sca-reports {
346 - padding: 20px;
336 + padding: 20px;
337 }
338 </style>
frontend/src/components/sca/ScaCard.vue
+4 -4
@@ -57,7 +57,7 @@
57 <template #value>
58 <code
59 class="text-primary cursor-pointer"
60 - @click.stop="gotoCustomer({ code: sca.customer_code })"
60 + @click.stop="routeCustomer({ code: sca.customer_code }).navigate()"
61 >
62 customer #{{ sca.customer_code }}
63 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -95,16 +95,16 @@ import { ref } from "vue"
95 import Badge from "@/components/common/Badge.vue"
96 import CardEntity from "@/components/common/cards/CardEntity.vue"
97 import Icon from "@/components/common/Icon.vue"
98 -import { useGoto } from "@/composables/useGoto"
98 +import { useNavigation } from "@/composables/useNavigation"
99 import { useSettingsStore } from "@/stores/settings"
100 -import { formatDate } from "@/utils"
100 +import { formatDate } from "@/utils/format"
101 import ScaCardContent from "./ScaCardContent.vue"
102 import ScaLevelBadge from "./ScaLevelBadge.vue"
103 import { getComplianceLevel } from "./utils"
104
105 const { sca } = defineProps<{ sca: AgentScaOverviewItem; embedded?: boolean }>()
106
107 -const { gotoCustomer } = useGoto()
107 +const { routeCustomer } = useNavigation()
108 const dFormats = useSettingsStore().dateFormat
109
110 const showDetails = ref(false)
frontend/src/components/sca/ScaCardContent.vue
+4 -4
@@ -22,7 +22,7 @@
22 <template #value>
23 <code
24 class="text-primary cursor-pointer"
25 - @click.stop="gotoCustomer({ code: sca.customer_code })"
25 + @click.stop="routeCustomer({ code: sca.customer_code }).navigate()"
26 >
27 #{{ sca.customer_code }}
28 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -174,15 +174,15 @@ import _toNumber from "lodash/toNumber"
174 import { NCard, NProgress, NStatistic, NTabPane, NTabs } from "naive-ui"
175 import CardKV from "@/components/common/cards/CardKV.vue"
176 import Icon from "@/components/common/Icon.vue"
177 -import { useGoto } from "@/composables/useGoto"
177 +import { useNavigation } from "@/composables/useNavigation"
178 import { useSettingsStore } from "@/stores/settings"
179 -import { formatDate } from "@/utils"
179 +import { formatDate } from "@/utils/format"
180 import { getComplianceLevel } from "./utils"
181
182 const { sca } = defineProps<{ sca: AgentScaOverviewItem }>()
183
184 const dFormats = useSettingsStore().dateFormat
185 -const { gotoCustomer } = useGoto()
185 +const { routeCustomer } = useNavigation()
186
187 const InfoIcon = "carbon:information"
188 const LinkIcon = "carbon:launch"
frontend/src/components/sca/StreamingList.vue
+224 -237
@@ -23,20 +23,13 @@
23 </template>
24 Load SCA Data
25 </n-button>
26 - <n-button
27 - v-if="isStreaming"
28 - type="error"
29 - @click="stopStream"
30 - >
26 + <n-button v-if="isStreaming" type="error" @click="stopStream">
27 <template #icon>
28 <Icon :name="StopIcon" />
29 </template>
30 Stop
31 </n-button>
36 - <n-button
37 - v-if="streamComplete"
38 - @click="startStream"
39 - >
32 + <n-button v-if="streamComplete" @click="startStream">
33 <template #icon>
34 <Icon :name="RefreshIcon" />
35 </template>
@@ -60,7 +53,8 @@
53 </code>
54 </span>
55 <span>
63 - Results: <code>{{ results.length }}</code>
56 + Results:
57 + <code>{{ results.length }}</code>
58 </span>
59 </div>
60 </div>
@@ -74,9 +68,7 @@
68 <n-statistic label="Total Policies" :value="stats.total_policies" />
69 <n-statistic label="Average Score">
70 <template #default>
77 - <span :class="getScoreClass(stats.average_score)">
78 - {{ stats.average_score }}%
79 - </span>
71 + <span :class="getScoreClass(stats.average_score)">{{ stats.average_score }}%</span>
72 </template>
73 </n-statistic>
74 <n-statistic label="Checks" :value="stats.total_checks" />
@@ -141,9 +133,7 @@
133 class="py-12"
134 >
135 <template #extra>
144 - <n-button type="primary" @click="startStream">
145 - Load SCA Data
146 - </n-button>
136 + <n-button type="primary" @click="startStream">Load SCA Data</n-button>
137 </template>
138 </n-empty>
139
@@ -169,26 +159,22 @@
159 </template>
160
161 <script setup lang="ts">
162 +// TODO: refactor
163 import type { DataTableColumns } from "naive-ui"
173 -import type {
174 - AgentScaOverviewItem,
175 - ScaOverviewQuery,
176 - ScaStreamComplete,
177 - ScaStreamProgress
178 -} from "@/types/sca.d"
164 +import type { AgentScaOverviewItem, ScaOverviewQuery, ScaStreamComplete, ScaStreamProgress } from "@/types/sca.d"
165 import {
180 - NAlert,
181 - NButton,
182 - NCard,
183 - NDataTable,
184 - NEmpty,
185 - NInput,
186 - NInputNumber,
187 - NProgress,
188 - NSelect,
189 - NSpin,
190 - NStatistic,
191 - useMessage
166 + NAlert,
167 + NButton,
168 + NCard,
169 + NDataTable,
170 + NEmpty,
171 + NInput,
172 + NInputNumber,
173 + NProgress,
174 + NSelect,
175 + NSpin,
176 + NStatistic,
177 + useMessage
178 } from "naive-ui"
179 import { computed, h, onBeforeUnmount, reactive, ref } from "vue"
180 import Api from "@/api"
@@ -210,20 +196,20 @@ const stats = ref<ScaStreamComplete | null>(null)
196 const abortController = ref<AbortController | null>(null)
197
198 const progress = reactive<ScaStreamProgress>({
213 - processed: 0,
214 - total: 0,
215 - successful: 0,
216 - failed: 0,
217 - results_so_far: 0,
218 - percent_complete: 0
199 + processed: 0,
200 + total: 0,
201 + successful: 0,
202 + failed: 0,
203 + results_so_far: 0,
204 + percent_complete: 0
205 })
206
207 const filters = reactive<ScaOverviewQuery>({
222 - customer_code: undefined,
223 - agent_name: undefined,
224 - policy_name: undefined,
225 - min_score: undefined,
226 - max_score: undefined
208 + customer_code: undefined,
209 + agent_name: undefined,
210 + policy_name: undefined,
211 + min_score: undefined,
212 + max_score: undefined
213 })
214
215 // Customer options (you'd populate this from your API)
@@ -231,232 +217,233 @@ const customerOptions = ref<{ label: string; value: string }[]>([])
217
218 // Computed
219 const statusMessage = computed(() => {
234 - if (isConnecting.value) return "Connecting..."
235 - if (isStreaming.value) return `Collecting SCA data... ${progress.processed}/${progress.total} agents`
236 - if (streamComplete.value) return stats.value?.message || "Collection complete"
237 - return "Ready to load SCA data"
220 + if (isConnecting.value) return "Connecting..."
221 + if (isStreaming.value) return `Collecting SCA data... ${progress.processed}/${progress.total} agents`
222 + if (streamComplete.value) return stats.value?.message || "Collection complete"
223 + return "Ready to load SCA data"
224 })
225
226 const filteredResults = computed(() => {
241 - return results.value.filter(item => {
242 - if (filters.policy_name && !item.policy_name.toLowerCase().includes(filters.policy_name.toLowerCase())) {
243 - return false
244 - }
245 - return true
246 - })
227 + return results.value.filter(item => {
228 + if (filters.policy_name && !item.policy_name.toLowerCase().includes(filters.policy_name.toLowerCase())) {
229 + return false
230 + }
231 + return true
232 + })
233 })
234
235 const pagination = reactive({
250 - page: 1,
251 - pageSize: 25,
252 - showSizePicker: true,
253 - pageSizes: [10, 25, 50, 100],
254 - itemCount: computed(() => filteredResults.value.length),
255 - onChange: (page: number) => {
256 - pagination.page = page
257 - },
258 - onUpdatePageSize: (pageSize: number) => {
259 - pagination.pageSize = pageSize
260 - pagination.page = 1
261 - }
236 + page: 1,
237 + pageSize: 25,
238 + showSizePicker: true,
239 + pageSizes: [10, 25, 50, 100],
240 + itemCount: computed(() => filteredResults.value.length),
241 + onChange: (page: number) => {
242 + pagination.page = page
243 + },
244 + onUpdatePageSize: (pageSize: number) => {
245 + pagination.pageSize = pageSize
246 + pagination.page = 1
247 + }
248 })
249
250 const paginatedResults = computed(() => {
265 - const start = (pagination.page - 1) * pagination.pageSize
266 - const end = start + pagination.pageSize
267 - return filteredResults.value.slice(start, end)
251 + const start = (pagination.page - 1) * pagination.pageSize
252 + const end = start + pagination.pageSize
253 + return filteredResults.value.slice(start, end)
254 })
255
256 // Table columns
257 const columns: DataTableColumns<AgentScaOverviewItem> = [
272 - {
273 - title: "Agent",
274 - key: "agent_name",
275 - width: 150,
276 - ellipsis: { tooltip: true }
277 - },
278 - {
279 - title: "Customer",
280 - key: "customer_code",
281 - width: 120
282 - },
283 - {
284 - title: "Policy",
285 - key: "policy_name",
286 - ellipsis: { tooltip: true }
287 - },
288 - {
289 - title: "Checks",
290 - key: "total_checks",
291 - width: 80,
292 - align: "center"
293 - },
294 - {
295 - title: "Passed",
296 - key: "pass_count",
297 - width: 80,
298 - align: "center",
299 - render: (row) => h("span", { class: "text-success" }, row.pass_count)
300 - },
301 - {
302 - title: "Failed",
303 - key: "fail_count",
304 - width: 80,
305 - align: "center",
306 - render: (row) => h("span", { class: "text-error" }, row.fail_count)
307 - },
308 - {
309 - title: "Score",
310 - key: "score",
311 - width: 100,
312 - align: "center",
313 - sorter: (a, b) => a.score - b.score,
314 - render: (row) => h(
315 - Badge,
316 - {
317 - type: "splitted",
318 - color: row.score >= 80 ? "success" : row.score >= 60 ? "warning" : "danger"
319 - },
320 - { label: () => `${row.score}%` }
321 - )
322 - },
323 - {
324 - title: "Last Scan",
325 - key: "end_scan",
326 - width: 160,
327 - render: (row) => new Date(row.end_scan).toLocaleString()
328 - }
258 + {
259 + title: "Agent",
260 + key: "agent_name",
261 + width: 150,
262 + ellipsis: { tooltip: true }
263 + },
264 + {
265 + title: "Customer",
266 + key: "customer_code",
267 + width: 120
268 + },
269 + {
270 + title: "Policy",
271 + key: "policy_name",
272 + ellipsis: { tooltip: true }
273 + },
274 + {
275 + title: "Checks",
276 + key: "total_checks",
277 + width: 80,
278 + align: "center"
279 + },
280 + {
281 + title: "Passed",
282 + key: "pass_count",
283 + width: 80,
284 + align: "center",
285 + render: row => h("span", { class: "text-success" }, row.pass)
286 + },
287 + {
288 + title: "Failed",
289 + key: "fail_count",
290 + width: 80,
291 + align: "center",
292 + render: row => h("span", { class: "text-error" }, row.fail)
293 + },
294 + {
295 + title: "Score",
296 + key: "score",
297 + width: 100,
298 + align: "center",
299 + sorter: (a, b) => a.score - b.score,
300 + render: row =>
301 + h(
302 + Badge,
303 + {
304 + type: "splitted",
305 + color: row.score >= 80 ? "success" : row.score >= 60 ? "warning" : "danger"
306 + },
307 + { label: () => `${row.score}%` }
308 + )
309 + },
310 + {
311 + title: "Last Scan",
312 + key: "end_scan",
313 + width: 160,
314 + render: row => new Date(row.end_scan).toLocaleString()
315 + }
316 ]
317
318 // Methods
319 function getScoreClass(score: number): string {
333 - if (score >= 80) return "text-success"
334 - if (score >= 60) return "text-warning"
335 - return "text-error"
320 + if (score >= 80) return "text-success"
321 + if (score >= 60) return "text-warning"
322 + return "text-error"
323 }
324
325 async function startStream() {
339 - // Reset state
340 - results.value = []
341 - stats.value = null
342 - streamError.value = null
343 - streamComplete.value = false
344 - isConnecting.value = true
345 -
346 - Object.assign(progress, {
347 - processed: 0,
348 - total: 0,
349 - successful: 0,
350 - failed: 0,
351 - results_so_far: 0,
352 - percent_complete: 0
353 - })
354 -
355 - // Abort existing connection if any
356 - if (abortController.value) {
357 - abortController.value.abort()
358 - }
359 -
360 - // Create new abort controller
361 - abortController.value = new AbortController()
362 -
363 - // Build query params
364 - const query: ScaOverviewQuery = {}
365 - if (filters.customer_code) query.customer_code = filters.customer_code
366 - if (filters.agent_name) query.agent_name = filters.agent_name
367 - if (filters.policy_name) query.policy_name = filters.policy_name
368 - if (filters.min_score !== undefined) query.min_score = filters.min_score
369 - if (filters.max_score !== undefined) query.max_score = filters.max_score
370 -
371 - try {
372 - await Api.sca.streamScaOverview(
373 - query,
374 - {
375 - onStart(data) {
376 - isConnecting.value = false
377 - isStreaming.value = true
378 - progress.total = data.total_agents
379 - message.info(data.message)
380 - },
381 - onAgentResult(data) {
382 - // Add all policies from this agent
383 - for (const policy of data.policies) {
384 - results.value.push({
385 - agent_id: data.agent_id,
386 - agent_name: data.agent_name,
387 - customer_code: data.customer_code,
388 - ...policy
389 - })
390 - }
391 - },
392 - onAgentEmpty(data) {
393 - // Agent had no SCA data - could log or display if needed
394 - console.debug(`Agent ${data.agent_name} has no SCA data`)
395 - },
396 - onProgress(data) {
397 - Object.assign(progress, data)
398 - },
399 - onComplete(data) {
400 - stats.value = data
401 - isStreaming.value = false
402 - streamComplete.value = true
403 -
404 - // Sort results by score (lowest first)
405 - results.value.sort((a, b) => a.score - b.score)
406 -
407 - message.success(data.message)
408 - },
409 - onError(error) {
410 - const errorMessage = error?.message || error?.error || "Unknown error"
411 - console.warn("Stream error:", error)
412 -
413 - // Only set error if we haven't completed successfully
414 - if (!streamComplete.value) {
415 - streamError.value = errorMessage
416 - }
417 - progress.failed++
418 - }
419 - },
420 - abortController.value
421 - )
422 - } catch (error: any) {
423 - // Don't show error for intentional abort
424 - if (error.name !== "AbortError") {
425 - streamError.value = error.message || "Connection error"
426 - console.error("Stream connection error:", error)
427 - }
428 - } finally {
429 - isStreaming.value = false
430 - isConnecting.value = false
431 - }
326 + // Reset state
327 + results.value = []
328 + stats.value = null
329 + streamError.value = null
330 + streamComplete.value = false
331 + isConnecting.value = true
332 +
333 + Object.assign(progress, {
334 + processed: 0,
335 + total: 0,
336 + successful: 0,
337 + failed: 0,
338 + results_so_far: 0,
339 + percent_complete: 0
340 + })
341 +
342 + // Abort existing connection if any
343 + if (abortController.value) {
344 + abortController.value.abort()
345 + }
346 +
347 + // Create new abort controller
348 + abortController.value = new AbortController()
349 +
350 + // Build query params
351 + const query: ScaOverviewQuery = {}
352 + if (filters.customer_code) query.customer_code = filters.customer_code
353 + if (filters.agent_name) query.agent_name = filters.agent_name
354 + if (filters.policy_name) query.policy_name = filters.policy_name
355 + if (filters.min_score !== undefined) query.min_score = filters.min_score
356 + if (filters.max_score !== undefined) query.max_score = filters.max_score
357 +
358 + try {
359 + await Api.sca.streamScaOverview(
360 + query,
361 + {
362 + onStart(data) {
363 + isConnecting.value = false
364 + isStreaming.value = true
365 + progress.total = data.total_agents
366 + message.info(data.message)
367 + },
368 + onAgentResult(data) {
369 + // Add all policies from this agent
370 + for (const policy of data.policies) {
371 + results.value.push({
372 + agent_id: data.agent_id,
373 + agent_name: data.agent_name,
374 + customer_code: data.customer_code,
375 + ...policy
376 + })
377 + }
378 + },
379 + onAgentEmpty(data) {
380 + // Agent had no SCA data - could log or display if needed
381 + console.warn(`Agent ${data.agent_name} has no SCA data`)
382 + },
383 + onProgress(data) {
384 + Object.assign(progress, data)
385 + },
386 + onComplete(data) {
387 + stats.value = data
388 + isStreaming.value = false
389 + streamComplete.value = true
390 +
391 + // Sort results by score (lowest first)
392 + results.value.sort((a, b) => a.score - b.score)
393 +
394 + message.success(data.message)
395 + },
396 + onError(error) {
397 + const errorMessage = error?.message || error?.error || "Unknown error"
398 + console.warn("Stream error:", error)
399 +
400 + // Only set error if we haven't completed successfully
401 + if (!streamComplete.value) {
402 + streamError.value = errorMessage
403 + }
404 + progress.failed++
405 + }
406 + },
407 + abortController.value
408 + )
409 + } catch (error: any) {
410 + // Don't show error for intentional abort
411 + if (error.name !== "AbortError") {
412 + streamError.value = error.message || "Connection error"
413 + console.error("Stream connection error:", error)
414 + }
415 + } finally {
416 + isStreaming.value = false
417 + isConnecting.value = false
418 + }
419 }
420
421 function stopStream() {
435 - if (abortController.value) {
436 - abortController.value.abort()
437 - abortController.value = null
438 - }
439 - isStreaming.value = false
440 - isConnecting.value = false
441 - message.warning("Stream stopped by user")
422 + if (abortController.value) {
423 + abortController.value.abort()
424 + abortController.value = null
425 + }
426 + isStreaming.value = false
427 + isConnecting.value = false
428 + message.warning("Stream stopped by user")
429 }
430
431 function onFilterChange() {
445 - // Debounce and restart stream with new filters if already streaming
446 - // Or just filter client-side if data is already loaded
447 - pagination.page = 1
432 + // Debounce and restart stream with new filters if already streaming
433 + // Or just filter client-side if data is already loaded
434 + pagination.page = 1
435 }
436
437 // Cleanup on unmount
438 onBeforeUnmount(() => {
452 - if (abortController.value) {
453 - abortController.value.abort()
454 - }
439 + if (abortController.value) {
440 + abortController.value.abort()
441 + }
442 })
443 </script>
444
445 <style scoped>
446 .results-container {
460 - min-height: 400px;
447 + min-height: 400px;
448 }
449 </style>
frontend/src/components/scheduler/Item.vue
+1 -1
@@ -50,7 +50,7 @@ import Badge from "@/components/common/Badge.vue"
50 import CardEntity from "@/components/common/cards/CardEntity.vue"
51 import Icon from "@/components/common/Icon.vue"
52 import { useSettingsStore } from "@/stores/settings"
53 -import { formatDate } from "@/utils"
53 +import { formatDate } from "@/utils/format"
54 import JobActions from "./JobActions.vue"
55 import NextJobTimeTooltip from "./NextJobTimeTooltip.vue"
56
frontend/src/components/scheduler/NextJobTimeTooltip.vue
+1 -1
@@ -19,7 +19,7 @@ import { ref, toRefs } from "vue"
19 import Api from "@/api"
20 import Icon from "@/components/common/Icon.vue"
21 import { useSettingsStore } from "@/stores/settings"
22 -import { formatDate } from "@/utils"
22 +import { formatDate } from "@/utils/format"
23
24 const props = defineProps<{ jobId: string }>()
25 const { jobId } = toRefs(props)
frontend/src/components/sigma/QueriesList.vue
+4
@@ -163,6 +163,8 @@ const header = ref()
163 const pageSlot = ref(8)
164
165 const itemsPaginated = computed(() => {
166 + if (!pageSize.value) return []
167 +
168 const from = (currentPage.value - 1) * pageSize.value
169 const to = currentPage.value * pageSize.value
170
@@ -245,6 +247,8 @@ function getData() {
247
248 useResizeObserver(header, entries => {
249 const entry = entries[0]
250 + if (!entry) return
251 +
252 const { width } = entry.contentRect
253
254 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/sigma/QueryItem.vue
+1 -1
@@ -98,7 +98,7 @@ import Badge from "@/components/common/Badge.vue"
98 import CardEntity from "@/components/common/cards/CardEntity.vue"
99 import Icon from "@/components/common/Icon.vue"
100 import { useSettingsStore } from "@/stores/settings"
101 -import { formatDate } from "@/utils"
101 +import { formatDate } from "@/utils/format"
102 import QueryDeleteOne from "./actionsProviders/QueryDeleteOne.vue"
103 import QueryTimeIntervalForm from "./actionsProviders/QueryTimeIntervalForm.vue"
104 import QueryDetails from "./QueryDetails.vue"
frontend/src/components/sigma/QueryOverview.vue
+1 -1
@@ -133,7 +133,7 @@ import { ref, toRefs } from "vue"
133 import CardKV from "@/components/common/cards/CardKV.vue"
134 import Icon from "@/components/common/Icon.vue"
135 import { useSettingsStore } from "@/stores/settings"
136 -import { formatDate } from "@/utils"
136 +import { formatDate } from "@/utils/format"
137 import QueryActiveForm from "./actionsProviders/QueryActiveForm.vue"
138 import QueryDeleteOne from "./actionsProviders/QueryDeleteOne.vue"
139 import QueryTimeIntervalForm from "./actionsProviders/QueryTimeIntervalForm.vue"
frontend/src/components/sigma/actionsProviders/QueryActiveForm.vue
+4 -1
@@ -21,6 +21,7 @@
21 </template>
22
23 <script setup lang="ts">
24 +// TODO: refactor
25 import type { SigmaQuery } from "@/types/sigma.d"
26 import { NButton, NPopover, NSwitch, useMessage } from "naive-ui"
27 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
@@ -75,7 +76,9 @@ function updateActive() {
76 .setQueryActive(query.value.rule_name, model.value.active)
77 .then(res => {
78 if (res.data.success) {
78 - emit("updated", res.data.sigma_queries[0])
79 + if (res.data.sigma_queries[0]) {
80 + emit("updated", res.data.sigma_queries[0])
81 + }
82 message.success(res.data?.message || "Sigma query updated successfully")
83 } else {
84 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/sigma/actionsProviders/QueryTimeIntervalForm.vue
+4 -1
@@ -42,6 +42,7 @@
42 </template>
43
44 <script setup lang="ts">
45 +// TODO: refactor
46 import type { SigmaQuery, SigmaTimeInterval, SigmaTimeIntervalUnit } from "@/types/sigma.d"
47 import { NButton, NInputGroup, NInputNumber, NPopover, NSelect, useMessage } from "naive-ui"
48 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
@@ -114,7 +115,9 @@ function updateTimeInterval() {
115 .setQueryTimeInterval(query.value.rule_name, modelTimeInterval.value)
116 .then(res => {
117 if (res.data.success) {
117 - emit("updated", res.data.sigma_queries[0])
118 + if (res.data.sigma_queries[0]) {
119 + emit("updated", res.data.sigma_queries[0])
120 + }
121 message.success(res.data?.message || "Sigma query updated successfully")
122 } else {
123 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/snapshots/CreateSnapshotForm.vue
+56 -55
@@ -31,7 +31,7 @@
31 <span class="ml-2 text-sm text-gray-500">Wait for snapshot to complete before returning</span>
32 </n-form-item>
33
34 - <div class="flex justify-end gap-2 mt-4">
34 + <div class="mt-4 flex justify-end gap-2">
35 <n-button @click="$emit('cancel')">Cancel</n-button>
36 <n-button type="primary" :loading="loading" @click="handleSubmit">Create Snapshot</n-button>
37 </div>
@@ -39,6 +39,7 @@
39 </template>
40
41 <script setup lang="ts">
42 +// TODO: refactor
43 import type { FormInst, FormRules } from "naive-ui"
44 import type { CreateSnapshotRequest } from "@/types/snapshots.d"
45 import { NButton, NForm, NFormItem, NInput, NSwitch, useMessage } from "naive-ui"
@@ -46,12 +47,12 @@ import { ref } from "vue"
47 import Api from "@/api"
48
49 const props = defineProps<{
49 - repository: string | null
50 + repository: string | null
51 }>()
52
53 const emit = defineEmits<{
53 - (e: "success"): void
54 - (e: "cancel"): void
54 + (e: "success"): void
55 + (e: "cancel"): void
56 }>()
57
58 const message = useMessage()
@@ -60,62 +61,62 @@ const loading = ref(false)
61 const indicesInput = ref("")
62
63 const formData = ref<Omit<CreateSnapshotRequest, "repository" | "indices">>({
63 - snapshot: "",
64 - ignore_unavailable: true,
65 - include_global_state: false,
66 - partial: false,
67 - wait_for_completion: false,
68 - skip_write_indices: true
64 + snapshot: "",
65 + ignore_unavailable: true,
66 + include_global_state: false,
67 + partial: false,
68 + wait_for_completion: false,
69 + skip_write_indices: true
70 })
71
72 const rules: FormRules = {
72 - snapshot: {
73 - required: true,
74 - message: "Snapshot name is required",
75 - trigger: "blur"
76 - }
73 + snapshot: {
74 + required: true,
75 + message: "Snapshot name is required",
76 + trigger: "blur"
77 + }
78 }
79
80 async function handleSubmit() {
80 - if (!props.repository) {
81 - message.error("Repository is required")
82 - return
83 - }
84 -
85 - try {
86 - await formRef.value?.validate()
87 - } catch {
88 - return
89 - }
90 -
91 - loading.value = true
92 - try {
93 - const indices = indicesInput.value
94 - .split(",")
95 - .map(s => s.trim())
96 - .filter(s => s.length > 0)
97 -
98 - const request: CreateSnapshotRequest = {
99 - repository: props.repository,
100 - snapshot: formData.value.snapshot,
101 - indices: indices.length > 0 ? indices : undefined,
102 - ignore_unavailable: formData.value.ignore_unavailable,
103 - include_global_state: formData.value.include_global_state,
104 - partial: formData.value.partial,
105 - wait_for_completion: formData.value.wait_for_completion,
106 - skip_write_indices: formData.value.skip_write_indices
107 - }
108 -
109 - const response = await Api.snapshots.createSnapshot(request)
110 - if (response.data.success) {
111 - emit("success")
112 - } else {
113 - message.error(response.data.message)
114 - }
115 - } catch (error: any) {
116 - message.error(error.message || "Failed to create snapshot")
117 - } finally {
118 - loading.value = false
119 - }
81 + if (!props.repository) {
82 + message.error("Repository is required")
83 + return
84 + }
85 +
86 + try {
87 + await formRef.value?.validate()
88 + } catch {
89 + return
90 + }
91 +
92 + loading.value = true
93 + try {
94 + const indices = indicesInput.value
95 + .split(",")
96 + .map(s => s.trim())
97 + .filter(s => s.length > 0)
98 +
99 + const request: CreateSnapshotRequest = {
100 + repository: props.repository,
101 + snapshot: formData.value.snapshot,
102 + indices: indices.length > 0 ? indices : undefined,
103 + ignore_unavailable: formData.value.ignore_unavailable,
104 + include_global_state: formData.value.include_global_state,
105 + partial: formData.value.partial,
106 + wait_for_completion: formData.value.wait_for_completion,
107 + skip_write_indices: formData.value.skip_write_indices
108 + }
109 +
110 + const response = await Api.snapshots.createSnapshot(request)
111 + if (response.data.success) {
112 + emit("success")
113 + } else {
114 + message.error(response.data.message)
115 + }
116 + } catch (error: any) {
117 + message.error(error.message || "Failed to create snapshot")
118 + } finally {
119 + loading.value = false
120 + }
121 }
122 </script>
frontend/src/components/snapshots/RestoreSnapshotForm.vue
+55 -51
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="formData" :rules="rules" label-placement="left" label-width="160px">
2 + <n-form :model="formData" :rules label-placement="left" label-width="160px">
3 <n-form-item label="Snapshot">
4 <n-input :value="snapshot?.snapshot" disabled />
5 </n-form-item>
@@ -43,11 +43,15 @@
43 </n-form-item>
44
45 <n-alert type="warning" class="mb-4">
46 - <strong>Note:</strong> If an index with the same name already exists, use the rename pattern to restore under
47 - a different name. Example: Pattern <code>(.+)</code> with replacement <code>restored_$1</code>
46 + <strong>Note:</strong>
47 + If an index with the same name already exists, use the rename pattern to restore under a different name.
48 + Example: Pattern
49 + <code>(.+)</code>
50 + with replacement
51 + <code>restored_$1</code>
52 </n-alert>
53
50 - <div class="flex justify-end gap-2 mt-4">
54 + <div class="mt-4 flex justify-end gap-2">
55 <n-button @click="$emit('cancel')">Cancel</n-button>
56 <n-button type="primary" :loading="loading" @click="handleSubmit">Restore Snapshot</n-button>
57 </div>
@@ -55,73 +59,73 @@
59 </template>
60
61 <script setup lang="ts">
58 -import type { FormInst, FormRules } from "naive-ui"
62 +// TODO: refactor
63 +import type { FormRules } from "naive-ui"
64 import type { RestoreSnapshotRequest, SnapshotInfo } from "@/types/snapshots.d"
65 import { NAlert, NButton, NForm, NFormItem, NInput, NSwitch, NTag, useMessage } from "naive-ui"
66 import { ref } from "vue"
67 import Api from "@/api"
68
69 const props = defineProps<{
65 - repository: string | null
66 - snapshot: SnapshotInfo | null
70 + repository: string | null
71 + snapshot: SnapshotInfo | null
72 }>()
73
74 const emit = defineEmits<{
70 - (e: "success"): void
71 - (e: "cancel"): void
75 + (e: "success"): void
76 + (e: "cancel"): void
77 }>()
78
79 const message = useMessage()
75 -const formRef = ref<FormInst | null>(null)
80 const loading = ref(false)
81 const indicesInput = ref("")
82
83 const formData = ref({
80 - rename_pattern: "(.+)",
81 - rename_replacement: "restored_$1",
82 - include_global_state: false,
83 - include_aliases: true,
84 - ignore_unavailable: true,
85 - partial: false
84 + rename_pattern: "(.+)",
85 + rename_replacement: "restored_$1",
86 + include_global_state: false,
87 + include_aliases: true,
88 + ignore_unavailable: true,
89 + partial: false
90 })
91
92 const rules: FormRules = {}
93
94 async function handleSubmit() {
91 - if (!props.repository || !props.snapshot) {
92 - message.error("Repository and snapshot are required")
93 - return
94 - }
95 -
96 - loading.value = true
97 - try {
98 - const indices = indicesInput.value
99 - .split(",")
100 - .map(s => s.trim())
101 - .filter(s => s.length > 0)
102 -
103 - const request: RestoreSnapshotRequest = {
104 - repository: props.repository,
105 - snapshot: props.snapshot.snapshot,
106 - indices: indices.length > 0 ? indices : undefined,
107 - rename_pattern: formData.value.rename_pattern || undefined,
108 - rename_replacement: formData.value.rename_replacement || undefined,
109 - include_global_state: formData.value.include_global_state,
110 - include_aliases: formData.value.include_aliases,
111 - ignore_unavailable: formData.value.ignore_unavailable,
112 - partial: formData.value.partial
113 - }
114 -
115 - const response = await Api.snapshots.restoreSnapshot(request)
116 - if (response.data.success) {
117 - emit("success")
118 - } else {
119 - message.error(response.data.message)
120 - }
121 - } catch (error: any) {
122 - message.error(error.message || "Failed to restore snapshot")
123 - } finally {
124 - loading.value = false
125 - }
95 + if (!props.repository || !props.snapshot) {
96 + message.error("Repository and snapshot are required")
97 + return
98 + }
99 +
100 + loading.value = true
101 + try {
102 + const indices = indicesInput.value
103 + .split(",")
104 + .map(s => s.trim())
105 + .filter(s => s.length > 0)
106 +
107 + const request: RestoreSnapshotRequest = {
108 + repository: props.repository,
109 + snapshot: props.snapshot.snapshot,
110 + indices: indices.length > 0 ? indices : undefined,
111 + rename_pattern: formData.value.rename_pattern || undefined,
112 + rename_replacement: formData.value.rename_replacement || undefined,
113 + include_global_state: formData.value.include_global_state,
114 + include_aliases: formData.value.include_aliases,
115 + ignore_unavailable: formData.value.ignore_unavailable,
116 + partial: formData.value.partial
117 + }
118 +
119 + const response = await Api.snapshots.restoreSnapshot(request)
120 + if (response.data.success) {
121 + emit("success")
122 + } else {
123 + message.error(response.data.message)
124 + }
125 + } catch (error: any) {
126 + message.error(error.message || "Failed to restore snapshot")
127 + } finally {
128 + loading.value = false
129 + }
130 }
131 </script>
frontend/src/components/snapshots/SnapshotList.vue
+102 -101
@@ -57,12 +57,13 @@
57 </template>
58
59 <script setup lang="ts">
60 +// TODO: refactor
61 import type { DataTableColumns, SelectOption } from "naive-ui"
62 import type { SnapshotInfo, SnapshotRepository } from "@/types/snapshots.d"
62 -import { Icon } from "@iconify/vue"
63 import { NButton, NCard, NDataTable, NEmpty, NModal, NSelect, NSpin, NTag, useMessage } from "naive-ui"
64 import { computed, h, onMounted, ref } from "vue"
65 import Api from "@/api"
66 +import Icon from "@/components/common/Icon.vue"
67 import CreateSnapshotForm from "./CreateSnapshotForm.vue"
68 import RestoreSnapshotForm from "./RestoreSnapshotForm.vue"
69
@@ -78,126 +79,126 @@ const showRestoreModal = ref(false)
79 const selectedSnapshot = ref<SnapshotInfo | null>(null)
80
81 const repositoryOptions = computed<SelectOption[]>(() =>
81 - repositories.value.map(repo => ({
82 - label: repo.name,
83 - value: repo.name
84 - }))
82 + repositories.value.map(repo => ({
83 + label: repo.name,
84 + value: repo.name
85 + }))
86 )
87
88 const columns: DataTableColumns<SnapshotInfo> = [
88 - {
89 - title: "Snapshot",
90 - key: "snapshot",
91 - sorter: "default"
92 - },
93 - {
94 - title: "State",
95 - key: "state",
96 - render(row) {
97 - const typeMap: Record<string, "success" | "warning" | "error" | "info"> = {
98 - SUCCESS: "success",
99 - IN_PROGRESS: "warning",
100 - PARTIAL: "warning",
101 - FAILED: "error"
102 - }
103 - return h(NTag, { type: typeMap[row.state] || "info", size: "small" }, () => row.state)
104 - }
105 - },
106 - {
107 - title: "Indices",
108 - key: "indices",
109 - render(row) {
110 - return h("span", {}, `${row.indices.length} indices`)
111 - }
112 - },
113 - {
114 - title: "Start Time",
115 - key: "start_time",
116 - render(row) {
117 - return row.start_time ? new Date(row.start_time).toLocaleString() : "-"
118 - }
119 - },
120 - {
121 - title: "End Time",
122 - key: "end_time",
123 - render(row) {
124 - return row.end_time ? new Date(row.end_time).toLocaleString() : "-"
125 - }
126 - },
127 - {
128 - title: "Duration",
129 - key: "duration_in_millis",
130 - render(row) {
131 - if (!row.duration_in_millis) return "-"
132 - const seconds = Math.floor(row.duration_in_millis / 1000)
133 - if (seconds < 60) return `${seconds}s`
134 - const minutes = Math.floor(seconds / 60)
135 - return `${minutes}m ${seconds % 60}s`
136 - }
137 - },
138 - {
139 - title: "Actions",
140 - key: "actions",
141 - render(row) {
142 - return h(
143 - NButton,
144 - {
145 - size: "small",
146 - type: "primary",
147 - onClick: () => openRestoreModal(row)
148 - },
149 - () => "Restore"
150 - )
151 - }
152 - }
89 + {
90 + title: "Snapshot",
91 + key: "snapshot",
92 + sorter: "default"
93 + },
94 + {
95 + title: "State",
96 + key: "state",
97 + render(row) {
98 + const typeMap: Record<string, "success" | "warning" | "error" | "info"> = {
99 + SUCCESS: "success",
100 + IN_PROGRESS: "warning",
101 + PARTIAL: "warning",
102 + FAILED: "error"
103 + }
104 + return h(NTag, { type: typeMap[row.state] || "info", size: "small" }, () => row.state)
105 + }
106 + },
107 + {
108 + title: "Indices",
109 + key: "indices",
110 + render(row) {
111 + return h("span", {}, `${row.indices.length} indices`)
112 + }
113 + },
114 + {
115 + title: "Start Time",
116 + key: "start_time",
117 + render(row) {
118 + return row.start_time ? new Date(row.start_time).toLocaleString() : "-"
119 + }
120 + },
121 + {
122 + title: "End Time",
123 + key: "end_time",
124 + render(row) {
125 + return row.end_time ? new Date(row.end_time).toLocaleString() : "-"
126 + }
127 + },
128 + {
129 + title: "Duration",
130 + key: "duration_in_millis",
131 + render(row) {
132 + if (!row.duration_in_millis) return "-"
133 + const seconds = Math.floor(row.duration_in_millis / 1000)
134 + if (seconds < 60) return `${seconds}s`
135 + const minutes = Math.floor(seconds / 60)
136 + return `${minutes}m ${seconds % 60}s`
137 + }
138 + },
139 + {
140 + title: "Actions",
141 + key: "actions",
142 + render(row) {
143 + return h(
144 + NButton,
145 + {
146 + size: "small",
147 + type: "primary",
148 + onClick: () => openRestoreModal(row)
149 + },
150 + () => "Restore"
151 + )
152 + }
153 + }
154 ]
155
156 function openRestoreModal(snapshot: SnapshotInfo) {
156 - selectedSnapshot.value = snapshot
157 - showRestoreModal.value = true
157 + selectedSnapshot.value = snapshot
158 + showRestoreModal.value = true
159 }
160
161 async function fetchRepositories() {
161 - try {
162 - const response = await Api.snapshots.getRepositories()
163 - if (response.data.success) {
164 - repositories.value = response.data.repositories
165 - }
166 - } catch (error: any) {
167 - message.error(error.message || "Failed to fetch repositories")
168 - }
162 + try {
163 + const response = await Api.snapshots.getRepositories()
164 + if (response.data.success) {
165 + repositories.value = response.data.repositories
166 + }
167 + } catch (error: any) {
168 + message.error(error.message || "Failed to fetch repositories")
169 + }
170 }
171
172 async function fetchSnapshots() {
172 - if (!selectedRepository.value) return
173 -
174 - loading.value = true
175 - try {
176 - const response = await Api.snapshots.listSnapshots(selectedRepository.value)
177 - if (response.data.success) {
178 - snapshots.value = response.data.snapshots
179 - } else {
180 - message.error(response.data.message)
181 - }
182 - } catch (error: any) {
183 - message.error(error.message || "Failed to fetch snapshots")
184 - } finally {
185 - loading.value = false
186 - }
173 + if (!selectedRepository.value) return
174 +
175 + loading.value = true
176 + try {
177 + const response = await Api.snapshots.listSnapshots(selectedRepository.value)
178 + if (response.data.success) {
179 + snapshots.value = response.data.snapshots
180 + } else {
181 + message.error(response.data.message)
182 + }
183 + } catch (error: any) {
184 + message.error(error.message || "Failed to fetch snapshots")
185 + } finally {
186 + loading.value = false
187 + }
188 }
189
190 function onSnapshotCreated() {
190 - showCreateModal.value = false
191 - fetchSnapshots()
192 - message.success("Snapshot creation initiated")
191 + showCreateModal.value = false
192 + fetchSnapshots()
193 + message.success("Snapshot creation initiated")
194 }
195
196 function onSnapshotRestored() {
196 - showRestoreModal.value = false
197 - message.success("Snapshot restoration initiated")
197 + showRestoreModal.value = false
198 + message.success("Snapshot restoration initiated")
199 }
200
201 onMounted(() => {
201 - fetchRepositories()
202 + fetchRepositories()
203 })
204 </script>
frontend/src/components/snapshots/SnapshotRepositories.vue
+38 -38
@@ -3,7 +3,10 @@
3 <n-alert type="info" :show-icon="true">
4 <template #header>Repository Registration Required</template>
5 Snapshot repositories must be manually registered in your Wazuh Indexer cluster.
6 - <n-a href="https://docs.opensearch.org/2.19/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/" target="_blank">
6 + <n-a
7 + href="https://docs.opensearch.org/2.19/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/"
8 + target="_blank"
9 + >
10 View the documentation
11 </n-a>
12 for instructions on how to register a snapshot repository.
@@ -36,60 +39,57 @@
39 </template>
40
41 <script setup lang="ts">
42 +// TODO: refactor
43 import type { DataTableColumns } from "naive-ui"
44 import type { SnapshotRepository } from "@/types/snapshots.d"
41 -import { Icon } from "@iconify/vue"
45 import { NA, NAlert, NButton, NCard, NDataTable, NEmpty, NSpin, useMessage } from "naive-ui"
46 import { h, onMounted, ref } from "vue"
47 import Api from "@/api"
48 +import Icon from "@/components/common/Icon.vue"
49
46 -const RefreshIcon = "carbon:refresh"
50 +const RefreshIcon = "carbon:renew"
51
52 const message = useMessage()
53 const loading = ref(false)
54 const repositories = ref<SnapshotRepository[]>([])
55
56 const columns: DataTableColumns<SnapshotRepository> = [
53 - {
54 - title: "Name",
55 - key: "name",
56 - sorter: "default"
57 - },
58 - {
59 - title: "Type",
60 - key: "type",
61 - sorter: "default"
62 - },
63 - {
64 - title: "Settings",
65 - key: "settings",
66 - render(row) {
67 - return h(
68 - "code",
69 - { class: "text-xs" },
70 - JSON.stringify(row.settings, null, 2)
71 - )
72 - }
73 - }
57 + {
58 + title: "Name",
59 + key: "name",
60 + sorter: "default"
61 + },
62 + {
63 + title: "Type",
64 + key: "type",
65 + sorter: "default"
66 + },
67 + {
68 + title: "Settings",
69 + key: "settings",
70 + render(row) {
71 + return h("code", { class: "text-xs" }, JSON.stringify(row.settings, null, 2))
72 + }
73 + }
74 ]
75
76 async function fetchRepositories() {
77 - loading.value = true
78 - try {
79 - const response = await Api.snapshots.getRepositories()
80 - if (response.data.success) {
81 - repositories.value = response.data.repositories
82 - } else {
83 - message.error(response.data.message)
84 - }
85 - } catch (error: any) {
86 - message.error(error.message || "Failed to fetch repositories")
87 - } finally {
88 - loading.value = false
89 - }
77 + loading.value = true
78 + try {
79 + const response = await Api.snapshots.getRepositories()
80 + if (response.data.success) {
81 + repositories.value = response.data.repositories
82 + } else {
83 + message.error(response.data.message)
84 + }
85 + } catch (error: any) {
86 + message.error(error.message || "Failed to fetch repositories")
87 + } finally {
88 + loading.value = false
89 + }
90 }
91
92 onMounted(() => {
93 - fetchRepositories()
93 + fetchRepositories()
94 })
95 </script>
frontend/src/components/snapshots/SnapshotScheduleForm.vue
+86 -87
@@ -6,9 +6,7 @@
6
7 <n-form-item label="Index Pattern" path="index_pattern">
8 <n-input v-model:value="formData.index_pattern" placeholder="e.g., wazuh_customer_*" />
9 - <template #feedback>
10 - Use wildcards (*) to match multiple indices. Example: wazuh_customer_*
11 - </template>
9 + <template #feedback>Use wildcards (*) to match multiple indices. Example: wazuh_customer_*</template>
10 </n-form-item>
11
12 <n-form-item label="Repository" path="repository">
@@ -54,7 +52,7 @@
52 </template>
53 </n-form-item>
54
57 - <div class="flex justify-end gap-2 mt-4">
55 + <div class="mt-4 flex justify-end gap-2">
56 <n-button @click="$emit('cancel')">Cancel</n-button>
57 <n-button type="primary" :loading="loading" @click="handleSubmit">
58 {{ isEditing ? "Update Schedule" : "Create Schedule" }}
@@ -64,6 +62,7 @@
62 </template>
63
64 <script setup lang="ts">
65 +// TODO: refactor
66 import type { FormInst, FormRules, SelectOption } from "naive-ui"
67 import type { SnapshotRepository, SnapshotScheduleCreate, SnapshotScheduleResponse } from "@/types/snapshots.d"
68 import { NButton, NForm, NFormItem, NInput, NInputNumber, NSelect, NSwitch, useMessage } from "naive-ui"
@@ -71,12 +70,12 @@ import { computed, onMounted, ref, watch } from "vue"
70 import Api from "@/api"
71
72 const props = defineProps<{
74 - schedule?: SnapshotScheduleResponse | null
73 + schedule?: SnapshotScheduleResponse | null
74 }>()
75
76 const emit = defineEmits<{
78 - (e: "success"): void
79 - (e: "cancel"): void
77 + (e: "success"): void
78 + (e: "cancel"): void
79 }>()
80
81 const message = useMessage()
@@ -88,103 +87,103 @@ const repositories = ref<SnapshotRepository[]>([])
87 const isEditing = computed(() => !!props.schedule)
88
89 const formData = ref<SnapshotScheduleCreate>({
91 - name: "",
92 - index_pattern: "",
93 - repository: "",
94 - enabled: true,
95 - snapshot_prefix: "scheduled",
96 - include_global_state: false,
97 - skip_write_indices: true,
98 - retention_days: null
90 + name: "",
91 + index_pattern: "",
92 + repository: "",
93 + enabled: true,
94 + snapshot_prefix: "scheduled",
95 + include_global_state: false,
96 + skip_write_indices: true,
97 + retention_days: null
98 })
99
100 const repositoryOptions = computed<SelectOption[]>(() =>
102 - repositories.value.map(repo => ({
103 - label: repo.name,
104 - value: repo.name
105 - }))
101 + repositories.value.map(repo => ({
102 + label: repo.name,
103 + value: repo.name
104 + }))
105 )
106
107 const rules: FormRules = {
109 - name: {
110 - required: true,
111 - message: "Name is required",
112 - trigger: "blur"
113 - },
114 - index_pattern: {
115 - required: true,
116 - message: "Index pattern is required",
117 - trigger: "blur"
118 - },
119 - repository: {
120 - required: true,
121 - message: "Repository is required",
122 - trigger: "change"
123 - }
108 + name: {
109 + required: true,
110 + message: "Name is required",
111 + trigger: "blur"
112 + },
113 + index_pattern: {
114 + required: true,
115 + message: "Index pattern is required",
116 + trigger: "blur"
117 + },
118 + repository: {
119 + required: true,
120 + message: "Repository is required",
121 + trigger: "change"
122 + }
123 }
124
125 watch(
127 - () => props.schedule,
128 - newSchedule => {
129 - if (newSchedule) {
130 - formData.value = {
131 - name: newSchedule.name,
132 - index_pattern: newSchedule.index_pattern,
133 - repository: newSchedule.repository,
134 - enabled: newSchedule.enabled,
135 - snapshot_prefix: newSchedule.snapshot_prefix,
136 - include_global_state: newSchedule.include_global_state,
137 - skip_write_indices: newSchedule.skip_write_indices,
138 - retention_days: newSchedule.retention_days
139 - }
140 - }
141 - },
142 - { immediate: true }
126 + () => props.schedule,
127 + newSchedule => {
128 + if (newSchedule) {
129 + formData.value = {
130 + name: newSchedule.name,
131 + index_pattern: newSchedule.index_pattern,
132 + repository: newSchedule.repository,
133 + enabled: newSchedule.enabled,
134 + snapshot_prefix: newSchedule.snapshot_prefix,
135 + include_global_state: newSchedule.include_global_state,
136 + skip_write_indices: newSchedule.skip_write_indices,
137 + retention_days: newSchedule.retention_days
138 + }
139 + }
140 + },
141 + { immediate: true }
142 )
143
144 async function fetchRepositories() {
146 - loadingRepositories.value = true
147 - try {
148 - const response = await Api.snapshots.getRepositories()
149 - if (response.data.success) {
150 - repositories.value = response.data.repositories
151 - }
152 - } catch (error: any) {
153 - message.error(error.message || "Failed to fetch repositories")
154 - } finally {
155 - loadingRepositories.value = false
156 - }
145 + loadingRepositories.value = true
146 + try {
147 + const response = await Api.snapshots.getRepositories()
148 + if (response.data.success) {
149 + repositories.value = response.data.repositories
150 + }
151 + } catch (error: any) {
152 + message.error(error.message || "Failed to fetch repositories")
153 + } finally {
154 + loadingRepositories.value = false
155 + }
156 }
157
158 async function handleSubmit() {
160 - try {
161 - await formRef.value?.validate()
162 - } catch {
163 - return
164 - }
165 -
166 - loading.value = true
167 - try {
168 - let response
169 - if (isEditing.value && props.schedule) {
170 - response = await Api.snapshots.updateSchedule(props.schedule.id, formData.value)
171 - } else {
172 - response = await Api.snapshots.createSchedule(formData.value)
173 - }
174 -
175 - if (response.data.success) {
176 - emit("success")
177 - } else {
178 - message.error(response.data.message)
179 - }
180 - } catch (error: any) {
181 - message.error(error.message || "Failed to save schedule")
182 - } finally {
183 - loading.value = false
184 - }
159 + try {
160 + await formRef.value?.validate()
161 + } catch {
162 + return
163 + }
164 +
165 + loading.value = true
166 + try {
167 + let response
168 + if (isEditing.value && props.schedule) {
169 + response = await Api.snapshots.updateSchedule(props.schedule.id, formData.value)
170 + } else {
171 + response = await Api.snapshots.createSchedule(formData.value)
172 + }
173 +
174 + if (response.data.success) {
175 + emit("success")
176 + } else {
177 + message.error(response.data.message)
178 + }
179 + } catch (error: any) {
180 + message.error(error.message || "Failed to save schedule")
181 + } finally {
182 + loading.value = false
183 + }
184 }
185
186 onMounted(() => {
188 - fetchRepositories()
187 + fetchRepositories()
188 })
189 </script>
frontend/src/components/snapshots/SnapshotSchedules.vue
+139 -150
@@ -35,10 +35,7 @@
35
36 <!-- Create Schedule Modal -->
37 <n-modal v-model:show="showCreateModal" preset="dialog" title="Create Snapshot Schedule" style="width: 600px">
38 - <SnapshotScheduleForm
39 - @success="onScheduleCreated"
40 - @cancel="showCreateModal = false"
41 - />
38 + <SnapshotScheduleForm @success="onScheduleCreated" @cancel="showCreateModal = false" />
39 </n-modal>
40
41 <!-- Edit Schedule Modal -->
@@ -53,27 +50,17 @@
50 </template>
51
52 <script setup lang="ts">
53 +// TODO: refactor
54 import type { DataTableColumns } from "naive-ui"
55 import type { SnapshotScheduleResponse } from "@/types/snapshots.d"
58 -import { Icon } from "@iconify/vue"
59 -import {
60 - NButton,
61 - NCard,
62 - NDataTable,
63 - NEmpty,
64 - NModal,
65 - NPopconfirm,
66 - NSpin,
67 - NSwitch,
68 - NTag,
69 - useMessage
70 -} from "naive-ui"
56 +import { NButton, NCard, NDataTable, NEmpty, NModal, NPopconfirm, NSpin, NSwitch, NTag, useMessage } from "naive-ui"
57 import { h, onMounted, ref } from "vue"
58 import Api from "@/api"
59 +import Icon from "@/components/common/Icon.vue"
60 import SnapshotScheduleForm from "./SnapshotScheduleForm.vue"
61
62 const AddIcon = "carbon:add"
76 -const RefreshIcon = "carbon:refresh"
63 +const RefreshIcon = "carbon:renew"
64
65 const message = useMessage()
66 const loading = ref(false)
@@ -83,158 +70,160 @@ const showEditModal = ref(false)
70 const selectedSchedule = ref<SnapshotScheduleResponse | null>(null)
71
72 const columns: DataTableColumns<SnapshotScheduleResponse> = [
86 - {
87 - title: "Name",
88 - key: "name",
89 - sorter: "default"
90 - },
91 - {
92 - title: "Index Pattern",
93 - key: "index_pattern",
94 - render(row) {
95 - return h("code", { class: "text-sm" }, row.index_pattern)
96 - }
97 - },
98 - {
99 - title: "Repository",
100 - key: "repository"
101 - },
102 - {
103 - title: "Enabled",
104 - key: "enabled",
105 - render(row) {
106 - return h(NSwitch, {
107 - value: row.enabled,
108 - onUpdateValue: (value: boolean) => toggleEnabled(row, value)
109 - })
110 - }
111 - },
112 - {
113 - title: "Retention",
114 - key: "retention_days",
115 - render(row) {
116 - return row.retention_days ? `${row.retention_days} days` : "Forever"
117 - }
118 - },
119 - {
120 - title: "Last Execution",
121 - key: "last_execution_time",
122 - render(row) {
123 - if (!row.last_execution_time) return "-"
124 - return h("div", { class: "flex flex-col" }, [
125 - h("span", {}, new Date(row.last_execution_time).toLocaleString()),
126 - h(
127 - NTag,
128 - {
129 - type: row.last_execution_status?.startsWith("SUCCESS")
130 -? "success" :
131 - row.last_execution_status?.startsWith("SKIPPED") ? "warning" : "error",
132 - size: "small",
133 - class: "mt-1"
134 - },
135 - () => row.last_execution_status?.split(":")[0] || "Unknown"
136 - )
137 - ])
138 - }
139 - },
140 - {
141 - title: "Last Snapshot",
142 - key: "last_snapshot_name",
143 - render(row) {
144 - return row.last_snapshot_name || "-"
145 - }
146 - },
147 - {
148 - title: "Actions",
149 - key: "actions",
150 - width: 150,
151 - render(row) {
152 - return h("div", { class: "flex gap-2" }, [
153 - h(
154 - NButton,
155 - {
156 - size: "small",
157 - onClick: () => openEditModal(row)
158 - },
159 - () => "Edit"
160 - ),
161 - h(
162 - NPopconfirm,
163 - {
164 - onPositiveClick: () => deleteSchedule(row)
165 - },
166 - {
167 - trigger: () => h(NButton, { size: "small", type: "error" }, () => "Delete"),
168 - default: () => "Are you sure you want to delete this schedule?"
169 - }
170 - )
171 - ])
172 - }
173 - }
73 + {
74 + title: "Name",
75 + key: "name",
76 + sorter: "default"
77 + },
78 + {
79 + title: "Index Pattern",
80 + key: "index_pattern",
81 + render(row) {
82 + return h("code", { class: "text-sm" }, row.index_pattern)
83 + }
84 + },
85 + {
86 + title: "Repository",
87 + key: "repository"
88 + },
89 + {
90 + title: "Enabled",
91 + key: "enabled",
92 + render(row) {
93 + return h(NSwitch, {
94 + value: row.enabled,
95 + onUpdateValue: (value: boolean) => toggleEnabled(row, value)
96 + })
97 + }
98 + },
99 + {
100 + title: "Retention",
101 + key: "retention_days",
102 + render(row) {
103 + return row.retention_days ? `${row.retention_days} days` : "Forever"
104 + }
105 + },
106 + {
107 + title: "Last Execution",
108 + key: "last_execution_time",
109 + render(row) {
110 + if (!row.last_execution_time) return "-"
111 + return h("div", { class: "flex flex-col" }, [
112 + h("span", {}, new Date(row.last_execution_time).toLocaleString()),
113 + h(
114 + NTag,
115 + {
116 + type: row.last_execution_status?.startsWith("SUCCESS")
117 + ? "success"
118 + : row.last_execution_status?.startsWith("SKIPPED")
119 + ? "warning"
120 + : "error",
121 + size: "small",
122 + class: "mt-1"
123 + },
124 + () => row.last_execution_status?.split(":")[0] || "Unknown"
125 + )
126 + ])
127 + }
128 + },
129 + {
130 + title: "Last Snapshot",
131 + key: "last_snapshot_name",
132 + render(row) {
133 + return row.last_snapshot_name || "-"
134 + }
135 + },
136 + {
137 + title: "Actions",
138 + key: "actions",
139 + width: 150,
140 + render(row) {
141 + return h("div", { class: "flex gap-2" }, [
142 + h(
143 + NButton,
144 + {
145 + size: "small",
146 + onClick: () => openEditModal(row)
147 + },
148 + () => "Edit"
149 + ),
150 + h(
151 + NPopconfirm,
152 + {
153 + onPositiveClick: () => deleteSchedule(row)
154 + },
155 + {
156 + trigger: () => h(NButton, { size: "small", type: "error" }, () => "Delete"),
157 + default: () => "Are you sure you want to delete this schedule?"
158 + }
159 + )
160 + ])
161 + }
162 + }
163 ]
164
165 function openEditModal(schedule: SnapshotScheduleResponse) {
177 - selectedSchedule.value = schedule
178 - showEditModal.value = true
166 + selectedSchedule.value = schedule
167 + showEditModal.value = true
168 }
169
170 async function toggleEnabled(schedule: SnapshotScheduleResponse, enabled: boolean) {
182 - try {
183 - const response = await Api.snapshots.updateSchedule(schedule.id, { enabled })
184 - if (response.data.success) {
185 - message.success(`Schedule ${enabled ? "enabled" : "disabled"}`)
186 - fetchSchedules()
187 - } else {
188 - message.error(response.data.message)
189 - }
190 - } catch (error: any) {
191 - message.error(error.message || "Failed to update schedule")
192 - }
171 + try {
172 + const response = await Api.snapshots.updateSchedule(schedule.id, { enabled })
173 + if (response.data.success) {
174 + message.success(`Schedule ${enabled ? "enabled" : "disabled"}`)
175 + fetchSchedules()
176 + } else {
177 + message.error(response.data.message)
178 + }
179 + } catch (error: any) {
180 + message.error(error.message || "Failed to update schedule")
181 + }
182 }
183
184 async function deleteSchedule(schedule: SnapshotScheduleResponse) {
196 - try {
197 - const response = await Api.snapshots.deleteSchedule(schedule.id)
198 - if (response.data.success) {
199 - message.success("Schedule deleted")
200 - fetchSchedules()
201 - } else {
202 - message.error(response.data.message)
203 - }
204 - } catch (error: any) {
205 - message.error(error.message || "Failed to delete schedule")
206 - }
185 + try {
186 + const response = await Api.snapshots.deleteSchedule(schedule.id)
187 + if (response.data.success) {
188 + message.success("Schedule deleted")
189 + fetchSchedules()
190 + } else {
191 + message.error(response.data.message)
192 + }
193 + } catch (error: any) {
194 + message.error(error.message || "Failed to delete schedule")
195 + }
196 }
197
198 async function fetchSchedules() {
210 - loading.value = true
211 - try {
212 - const response = await Api.snapshots.getSchedules()
213 - if (response.data.success) {
214 - schedules.value = response.data.schedules
215 - } else {
216 - message.error(response.data.message)
217 - }
218 - } catch (error: any) {
219 - message.error(error.message || "Failed to fetch schedules")
220 - } finally {
221 - loading.value = false
222 - }
199 + loading.value = true
200 + try {
201 + const response = await Api.snapshots.getSchedules()
202 + if (response.data.success) {
203 + schedules.value = response.data.schedules
204 + } else {
205 + message.error(response.data.message)
206 + }
207 + } catch (error: any) {
208 + message.error(error.message || "Failed to fetch schedules")
209 + } finally {
210 + loading.value = false
211 + }
212 }
213
214 function onScheduleCreated() {
226 - showCreateModal.value = false
227 - fetchSchedules()
228 - message.success("Schedule created successfully")
215 + showCreateModal.value = false
216 + fetchSchedules()
217 + message.success("Schedule created successfully")
218 }
219
220 function onScheduleUpdated() {
232 - showEditModal.value = false
233 - fetchSchedules()
234 - message.success("Schedule updated successfully")
221 + showEditModal.value = false
222 + fetchSchedules()
223 + message.success("Schedule updated successfully")
224 }
225
226 onMounted(() => {
238 - fetchSchedules()
227 + fetchSchedules()
228 })
229 </script>
frontend/src/components/soc/SocAlerts/SocAlertAssets/SocAlertAssetsItem.vue
+4 -4
@@ -49,7 +49,7 @@
49 {{ formatDate(asset.date_update) }}
50 </template>
51 </Badge>
52 - <Badge type="active" class="cursor-pointer" @click.stop="gotoAgent(asset.asset_tags)">
52 + <Badge type="active" class="cursor-pointer" @click.stop="routeAgent(asset.asset_tags).navigate()">
53 <template #iconRight>
54 <Icon :name="LinkIcon" :size="14" />
55 </template>
@@ -80,7 +80,7 @@
80 <code
81 v-if="value && value !== '-'"
82 class="text-primary cursor-pointer"
83 - @click.stop="gotoAgent(value)"
83 + @click.stop="routeAgent(value).navigate()"
84 >
85 {{ value }}
86 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -149,7 +149,7 @@ import Badge from "@/components/common/Badge.vue"
149 import CardEntity from "@/components/common/cards/CardEntity.vue"
150 import CardKV from "@/components/common/cards/CardKV.vue"
151 import Icon from "@/components/common/Icon.vue"
152 -import { useGoto } from "@/composables/useGoto"
152 +import { useNavigation } from "@/composables/useNavigation"
153 import { useSettingsStore } from "@/stores/settings"
154 import { isUrlLike } from "@/utils"
155 import dayjs from "@/utils/dayjs"
@@ -160,7 +160,7 @@ const ArtifactsCollect = defineAsyncComponent(() => import("@/components/artifac
160
161 const ClockIcon = "carbon:time"
162 const LinkIcon = "carbon:launch"
163 -const { gotoAgent } = useGoto()
163 +const { routeAgent } = useNavigation()
164 const showDetails = ref(false)
165
166 const dFormats = useSettingsStore().dateFormat
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemBadges.vue
+5 -5
@@ -23,7 +23,7 @@
23 {{ alert.severity?.severity_name || "-" }}
24 </template>
25 </Badge>
26 - <Badge type="splitted" color="primary" class="hidden! @2xl:!flex">
26 + <Badge type="splitted" color="primary" class="hidden! @2xl:flex!">
27 <template #iconLeft>
28 <Icon :name="SourceIcon" :size="13" />
29 </template>
@@ -32,7 +32,7 @@
32 {{ alert.alert_source || "-" }}
33 </template>
34 </Badge>
35 - <Badge type="splitted" color="primary" class="hidden! @2xl:!flex">
35 + <Badge type="splitted" color="primary" class="hidden! @2xl:flex!">
36 <template #iconLeft>
37 <Icon :name="CustomerIcon" :size="13" />
38 </template>
@@ -41,7 +41,7 @@
41 <template v-if="alert.customer?.customer_code && alert.customer.customer_code !== 'Customer Not Found'">
42 <code
43 class="text-primary cursor-pointer"
44 - @click="gotoCustomer({ code: alert.customer.customer_code })"
44 + @click="routeCustomer({ code: alert.customer.customer_code }).navigate()"
45 >
46 {{ alert.customer?.customer_name || alert.customer.customer_code || "-" }}
47 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -90,7 +90,7 @@ import { NSpin, NTooltip } from "naive-ui"
90 import { computed, toRefs } from "vue"
91 import Badge from "@/components/common/Badge.vue"
92 import Icon from "@/components/common/Icon.vue"
93 -import { useGoto } from "@/composables/useGoto"
93 +import { useNavigation } from "@/composables/useNavigation"
94 import SocAssignUser from "./SocAssignUser.vue"
95
96 const props = defineProps<{
@@ -111,7 +111,7 @@ const SourceIcon = "lucide:arrow-down-right-from-circle"
111 const CustomerIcon = "carbon:user"
112 const OwnerIcon = "carbon:user-military"
113
114 -const { gotoCustomer } = useGoto()
114 +const { routeCustomer } = useNavigation()
115
116 const ownerName = computed(() => alert.value?.owner?.user_login)
117 </script>
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemContext.vue
+1 -1
@@ -25,7 +25,7 @@
25 <template v-else>-</template>
26 </template>
27 <template v-else>
28 - <ExpandableText :text="value.toString() ?? '-'" :max-length="100" />
28 + <ExpandableText :text="value?.toString() ?? '-'" :max-length="100" />
29 </template>
30 </template>
31 </CardKV>
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemDetails.vue
+12 -4
@@ -16,7 +16,10 @@
16 </template>
17 <template #value>
18 <template v-if="key === 'customer_code' && value && value !== 'Customer Not Found'">
19 - <code class="text-primary cursor-pointer" @click="gotoCustomer({ code: value.toString() })">
19 + <code
20 + class="text-primary cursor-pointer"
21 + @click="routeCustomer({ code: value.toString() }).navigate()"
22 + >
23 #{{ value }}
24 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
25 </code>
@@ -30,7 +33,12 @@
33 </n-tab-pane>
34 <n-tab-pane name="Owner" tab="Owner" display-directive="show:lazy">
35 <div class="grid gap-2 px-7 pt-4">
33 - <Badge type="active" style="max-width: 145px" class="cursor-pointer" @click="gotoSocUsers(ownerId)">
36 + <Badge
37 + type="active"
38 + style="max-width: 145px"
39 + class="cursor-pointer"
40 + @click="routeSocUsers(ownerId).navigate()"
41 + >
42 <template #iconRight>
43 <Icon :name="LinkIcon" :size="14" />
44 </template>
@@ -96,7 +104,7 @@ import { SimpleJsonViewer } from "vue-sjv"
104 import Badge from "@/components/common/Badge.vue"
105 import CardKV from "@/components/common/cards/CardKV.vue"
106 import Icon from "@/components/common/Icon.vue"
99 -import { useGoto } from "@/composables/useGoto"
107 +import { useNavigation } from "@/composables/useNavigation"
108 import "@/assets/scss/overrides/vuesjv-override.scss"
109
110 const { alert } = defineProps<{
@@ -114,7 +122,7 @@ const SocAlertAssetsList = defineAsyncComponent(() => import("../SocAlertAssets/
122 const LinkIcon = "carbon:launch"
123 const EditIcon = "uil:edit-alt"
124
117 -const { gotoCustomer, gotoSocUsers } = useGoto()
125 +const { routeCustomer, routeSocUsers } = useNavigation()
126
127 const ownerName = computed(() => alert?.owner?.user_login)
128 const ownerId = computed(() => alert?.owner?.id)
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemTimeline.vue
+1 -1
@@ -45,7 +45,7 @@ onBeforeMount(() => {
45 const item = alert.modification_history[key]
46 history.value.push({
47 timeString: formatDate(_toNumber(key) * 1000, false),
48 - label: `${item.action} [${item.user}]`
48 + label: `${item?.action ?? ""} [${item?.user ?? ""}]`
49 })
50 }
51 }
frontend/src/components/soc/SocAlerts/SocAlertsFullList.vue
+2
@@ -139,6 +139,8 @@ function getUsers() {
139
140 useResizeObserver(list, entries => {
141 const entry = entries[0]
142 + if (!entry) return
143 +
144 const { width } = entry.contentRect
145
146 compactMode.value = width < 680
frontend/src/components/soc/SocAlerts/SocAlertsList.vue
+4 -2
@@ -35,7 +35,7 @@
35 </template>
36 <div class="checked-list flex flex-col gap-2">
37 <div v-for="alertId of checkedList" :key="alertId" class="w-full">
38 - <n-button size="small" class="w-full! !justify-start" @click="clearChecked(alertId)">
38 + <n-button size="small" class="w-full! justify-start!" @click="clearChecked(alertId)">
39 <template #icon>
40 <Icon :name="CloseIcon" :size="18" />
41 </template>
@@ -362,11 +362,13 @@ watchDebounced(
362
363 useResizeObserver(header, entries => {
364 const entry = entries[0]
365 + if (!entry) return
366 +
367 const { width } = entry.contentRect
368
369 if (width < 600) {
370 compactMode.value = true
369 - pageSize.value = pageSizes[0]
371 + pageSize.value = pageSizes[0] ?? 50
372 } else {
373 compactMode.value = false
374 }
frontend/src/components/soc/SocCases/SocCaseItem.vue
+4 -4
@@ -162,7 +162,7 @@
162 <template v-if="key === 'customer_code' && value && value !== 'Customer Not Found'">
163 <code
164 class="text-primary cursor-pointer"
165 - @click.stop="gotoCustomer({ code: value })"
165 + @click.stop="routeCustomer({ code: value }).navigate()"
166 >
167 #{{ value }}
168 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -223,7 +223,7 @@
223 </n-collapse-item>
224 </n-collapse>
225 </div>
226 - <n-divider class="!my-2" />
226 + <n-divider class="my-2!" />
227 <SocCaseNotesList v-if="baseInfo" v-model:requested="updateNotes" :case-id="baseInfo.case_id" />
228 </n-tab-pane>
229 </n-tabs>
@@ -255,7 +255,7 @@ import Badge from "@/components/common/Badge.vue"
255 import CardEntity from "@/components/common/cards/CardEntity.vue"
256 import CardKV from "@/components/common/cards/CardKV.vue"
257 import Icon from "@/components/common/Icon.vue"
258 -import { useGoto } from "@/composables/useGoto"
258 +import { useNavigation } from "@/composables/useNavigation"
259 import { useSettingsStore } from "@/stores/settings"
260 import { StateName } from "@/types/soc/case.d"
261 import dayjs from "@/utils/dayjs"
@@ -284,7 +284,7 @@ const OwnerIcon = "carbon:user-military"
284 const StatusIcon = "fluent:status-20-regular"
285 const AddIcon = "carbon:add-alt"
286
287 -const { gotoCustomer } = useGoto()
287 +const { routeCustomer } = useNavigation()
288 const showSocAlertDetails = ref(false)
289 const showDetails = ref(false)
290 const loadingDetails = ref(false)
frontend/src/components/soc/SocCases/SocCaseTimeline.vue
+1 -1
@@ -40,7 +40,7 @@ onBeforeMount(() => {
40 const item = caseData.modification_history[key]
41 history.value.push({
42 timeString: formatDate(_toNumber(key) * 1000, false),
43 - label: `${item.action} [${item.user}]`
43 + label: `${item?.action ?? ""} [${item?.user ?? ""}]`
44 })
45 }
46 }
frontend/src/components/soc/SocCases/SocCasesList.vue
+2
@@ -257,6 +257,8 @@ function purge() {
257
258 useResizeObserver(header, entries => {
259 const entry = entries[0]
260 + if (!entry) return
261 +
262 const { width } = entry.contentRect
263
264 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/threatIntel/ThreatIntelEpssScore.vue
+1 -1
@@ -52,7 +52,7 @@ import { onBeforeMount, ref } from "vue"
52 import Api from "@/api"
53 import CardKV from "@/components/common/cards/CardKV.vue"
54 import { useSettingsStore } from "@/stores/settings"
55 -import { formatDate } from "@/utils"
55 +import { formatDate } from "@/utils/format"
56
57 interface EpssScoreExt extends EpssScore {
58 ___id?: string
frontend/src/components/threatIntel/ThreatIntelForm.vue
+1 -1
@@ -80,7 +80,7 @@ import { NButton, NInput, NSpin, useMessage } from "naive-ui"
80 import { computed, onMounted, ref } from "vue"
81 import Api from "@/api"
82 import { useSettingsStore } from "@/stores/settings"
83 -import { formatDate } from "@/utils"
83 +import { formatDate } from "@/utils/format"
84
85 const emit = defineEmits<{
86 (
frontend/src/components/threatIntel/VirusTotalEnrichmentButton.vue
+1 -1
@@ -541,7 +541,7 @@ import { computed, defineAsyncComponent, ref } from "vue"
541 import Api from "@/api"
542 import Icon from "@/components/common/Icon.vue"
543 import { useSettingsStore } from "@/stores/settings"
544 -import { formatDate } from "@/utils"
544 +import { formatDate } from "@/utils/format"
545
546 const { iocValue, size } = defineProps<{
547 iocValue: string
frontend/src/components/users/AssignCustomer.vue
+2 -3
@@ -22,7 +22,7 @@
22 {{ user?.username }}
23 </div>
24
25 - <n-form ref="formRef" :model="formModel">
25 + <n-form :model="formModel">
26 <n-form-item label="Select Customers">
27 <n-select
28 v-model:value="formModel.customerCodes"
@@ -52,7 +52,7 @@
52 </template>
53
54 <script setup lang="ts">
55 -import type { FormInst } from "naive-ui"
55 +// TODO: refactor
56 import type { Customer } from "@/types/customers.d"
57 import type { User } from "@/types/user.d"
58 import { NButton, NForm, NFormItem, NModal, NSelect, NTag, useMessage } from "naive-ui"
@@ -73,7 +73,6 @@ const message = useMessage()
73 const showModal = ref(false)
74 const loading = ref(false)
75 const loadingCustomers = ref(false)
76 -const formRef = ref<FormInst>()
76 const customers = ref<Customer[]>([])
77 const currentAccess = ref<string[]>([])
78
frontend/src/components/users/AssignTags.vue
+103 -110
@@ -6,9 +6,7 @@
6 </div>
7
8 <n-spin :show="loading" size="small">
9 - <div v-if="!tagRbacEnabled" class="text-xs opacity-70">
10 - Tag RBAC is disabled
11 - </div>
9 + <div v-if="!tagRbacEnabled" class="text-xs opacity-70">Tag RBAC is disabled</div>
10
11 <div v-else class="flex flex-col gap-2">
12 <n-select
@@ -24,13 +22,7 @@
22 />
23
24 <div class="flex gap-2">
27 - <n-button
28 - size="small"
29 - type="primary"
30 - :loading="saving"
31 - :disabled="!hasChanges"
32 - @click="saveTags"
33 - >
25 + <n-button size="small" type="primary" :loading="saving" :disabled="!hasChanges" @click="saveTags">
26 Save
27 </n-button>
28 <n-button
@@ -49,7 +41,8 @@
41 </template>
42
43 <script setup lang="ts">
52 -import type { AlertTag } from "@/types/incidentManagement/tags.d"
44 +// TODO: refactor
45 +import type { AlertTag } from "@/types/tags"
46 import type { User } from "@/types/user.d"
47 import { NButton, NSelect, NSpin, useMessage } from "naive-ui"
48 import { computed, onMounted, ref, watch } from "vue"
@@ -57,11 +50,11 @@ import Api from "@/api"
50 import Icon from "@/components/common/Icon.vue"
51
52 const props = defineProps<{
60 - user: User | undefined
53 + user: User | undefined
54 }>()
55
56 const emit = defineEmits<{
64 - (e: "success"): void
57 + (e: "success"): void
58 }>()
59
60 const TagIcon = "carbon:tag"
@@ -77,132 +70,132 @@ const selectedTagIds = ref<number[]>([])
70 const originalTagIds = ref<number[]>([])
71
72 const tagOptions = computed(() =>
80 - availableTags.value.map(tag => ({
81 - label: tag.tag,
82 - value: tag.id
83 - }))
73 + availableTags.value.map((tag: AlertTag) => ({
74 + label: tag.tag,
75 + value: tag.id
76 + }))
77 )
78
79 const hasChanges = computed(() => {
87 - if (selectedTagIds.value.length !== originalTagIds.value.length) return true
88 - const sorted1 = [...selectedTagIds.value].sort()
89 - const sorted2 = [...originalTagIds.value].sort()
90 - return sorted1.some((val, idx) => val !== sorted2[idx])
80 + if (selectedTagIds.value.length !== originalTagIds.value.length) return true
81 + const sorted1 = [...selectedTagIds.value].sort()
82 + const sorted2 = [...originalTagIds.value].sort()
83 + return sorted1.some((val, idx) => val !== sorted2[idx])
84 })
85
86 async function loadSettings() {
94 - try {
95 - const res = await Api.tagRbac.getSettings()
96 - if (res.data.success && res.data.settings) {
97 - tagRbacEnabled.value = res.data.settings.enabled
98 - }
99 - } catch (error) {
100 - console.error("Failed to load tag RBAC settings:", error)
101 - }
87 + try {
88 + const res = await Api.tagRbac.getSettings()
89 + if (res.data.success && res.data.settings) {
90 + tagRbacEnabled.value = res.data.settings.enabled
91 + }
92 + } catch (error) {
93 + console.error("Failed to load tag RBAC settings:", error)
94 + }
95 }
96
97 async function loadAvailableTags() {
105 - loadingTags.value = true
106 - try {
107 - const res = await Api.tagRbac.getAvailableTags()
108 - if (res.data.success) {
109 - availableTags.value = res.data.tags
110 - }
111 - } catch (error) {
112 - console.error("Failed to load available tags:", error)
113 - } finally {
114 - loadingTags.value = false
115 - }
98 + loadingTags.value = true
99 + try {
100 + const res = await Api.tagRbac.getAvailableTags()
101 + if (res.data.success) {
102 + availableTags.value = res.data.tags
103 + }
104 + } catch (error) {
105 + console.error("Failed to load available tags:", error)
106 + } finally {
107 + loadingTags.value = false
108 + }
109 }
110
111 async function loadUserTags() {
119 - if (!props.user?.id) return
120 -
121 - loading.value = true
122 - try {
123 - const res = await Api.tagRbac.getUserTags(props.user.id)
124 - if (res.data.success) {
125 - // Backend returns accessible_tags, not tag_ids
126 - const tagIds = res.data.accessible_tags.map(tag => tag.id)
127 - selectedTagIds.value = tagIds
128 - originalTagIds.value = [...tagIds]
129 - }
130 - } catch (error) {
131 - console.error("Failed to load user tags:", error)
132 - } finally {
133 - loading.value = false
134 - }
112 + if (!props.user?.id) return
113 +
114 + loading.value = true
115 + try {
116 + const res = await Api.tagRbac.getUserTags(props.user.id)
117 + if (res.data.success) {
118 + // Backend returns accessible_tags, not tag_ids
119 + const tagIds = res.data.accessible_tags.map(tag => tag.id)
120 + selectedTagIds.value = tagIds
121 + originalTagIds.value = [...tagIds]
122 + }
123 + } catch (error) {
124 + console.error("Failed to load user tags:", error)
125 + } finally {
126 + loading.value = false
127 + }
128 }
129
130 async function saveTags() {
138 - if (!props.user?.id) return
139 -
140 - saving.value = true
141 - try {
142 - const res = await Api.tagRbac.assignUserTags(props.user.id, selectedTagIds.value)
143 - if (res.data.success) {
144 - message.success("Tag access updated")
145 - // Update original from response
146 - const tagIds = res.data.accessible_tags.map(tag => tag.id)
147 - originalTagIds.value = [...tagIds]
148 - emit("success")
149 - } else {
150 - message.error(res.data.message || "Failed to update tag access")
151 - }
152 - } catch (error: any) {
153 - message.error(error.response?.data?.message || "Failed to update tag access")
154 - } finally {
155 - saving.value = false
156 - }
131 + if (!props.user?.id) return
132 +
133 + saving.value = true
134 + try {
135 + const res = await Api.tagRbac.assignUserTags(props.user.id, selectedTagIds.value)
136 + if (res.data.success) {
137 + message.success("Tag access updated")
138 + // Update original from response
139 + const tagIds = res.data.accessible_tags.map(tag => tag.id)
140 + originalTagIds.value = [...tagIds]
141 + emit("success")
142 + } else {
143 + message.error(res.data.message || "Failed to update tag access")
144 + }
145 + } catch (error: any) {
146 + message.error(error.response?.data?.message || "Failed to update tag access")
147 + } finally {
148 + saving.value = false
149 + }
150 }
151
152 async function clearAllTags() {
160 - if (!props.user?.id) return
161 -
162 - saving.value = true
163 - try {
164 - // Use assignUserTags with empty array to clear all
165 - const res = await Api.tagRbac.assignUserTags(props.user.id, [])
166 - if (res.data.success) {
167 - message.success("Tag restrictions cleared")
168 - selectedTagIds.value = []
169 - originalTagIds.value = []
170 - emit("success")
171 - }
172 - } catch (error: any) {
173 - message.error(error.response?.data?.message || "Failed to clear tags")
174 - } finally {
175 - saving.value = false
176 - }
153 + if (!props.user?.id) return
154 +
155 + saving.value = true
156 + try {
157 + // Use assignUserTags with empty array to clear all
158 + const res = await Api.tagRbac.assignUserTags(props.user.id, [])
159 + if (res.data.success) {
160 + message.success("Tag restrictions cleared")
161 + selectedTagIds.value = []
162 + originalTagIds.value = []
163 + emit("success")
164 + }
165 + } catch (error: any) {
166 + message.error(error.response?.data?.message || "Failed to clear tags")
167 + } finally {
168 + saving.value = false
169 + }
170 }
171
172 watch(
180 - () => props.user?.id,
181 - () => {
182 - if (props.user?.id) {
183 - loadUserTags()
184 - }
185 - }
173 + () => props.user?.id,
174 + () => {
175 + if (props.user?.id) {
176 + loadUserTags()
177 + }
178 + }
179 )
180
181 onMounted(async () => {
189 - await loadSettings()
190 - if (tagRbacEnabled.value) {
191 - await loadAvailableTags()
192 - if (props.user?.id) {
193 - await loadUserTags()
194 - }
195 - }
182 + await loadSettings()
183 + if (tagRbacEnabled.value) {
184 + await loadAvailableTags()
185 + if (props.user?.id) {
186 + await loadUserTags()
187 + }
188 + }
189 })
190 </script>
191
192 <style scoped lang="scss">
193 .assign-tags-box {
201 - min-width: 250px;
194 + min-width: 250px;
195
203 - .title {
204 - font-size: 14px;
205 - font-weight: 500;
206 - }
196 + .title {
197 + font-size: 14px;
198 + font-weight: 500;
199 + }
200 }
201 </style>
frontend/src/components/users/ChangePassword.vue
+1 -1
@@ -172,7 +172,7 @@ function submit(e: Event) {
172 })
173 } else {
174 for (const err of errors) {
175 - message.error(err[0].message || "Invalid fields")
175 + message.error(err?.[0]?.message ?? "Invalid fields")
176 }
177 }
178 })
frontend/src/components/users/TagRbacSettings.vue
+183 -178
@@ -47,9 +47,7 @@
47 :loading="loadingTags"
48 />
49 <template #help>
50 - <span class="text-xs opacity-70">
51 - Users with access to this tag will also see untagged alerts
52 - </span>
50 + <span class="text-xs opacity-70">Users with access to this tag will also see untagged alerts</span>
51 </template>
52 </n-form-item>
53
@@ -57,23 +55,26 @@
55
56 <n-alert v-if="settings.enabled" type="info" title="How Tag RBAC Works">
57 <div class="info-content">
60 - <p class="intro">
61 - Tag RBAC controls which alerts users can see based on assigned tags.
62 - </p>
58 + <p class="intro">Tag RBAC controls which alerts users can see based on assigned tags.</p>
59
60 <div class="section">
61 <strong>User Access Rules:</strong>
62 <ul class="info-list">
63 <li>
68 - <strong>No tags assigned</strong> → User can see
69 - <em>all alerts</em> (no restrictions)
64 + <strong>No tags assigned</strong>
65 + → User can see
66 + <em>all alerts</em>
67 + (no restrictions)
68 </li>
69 <li>
72 - <strong>Tags assigned</strong> → User can
73 - <em>only</em> see alerts with matching tags
70 + <strong>Tags assigned</strong>
71 + → User can
72 + <em>only</em>
73 + see alerts with matching tags
74 </li>
75 <li>
76 - <strong>Admins &amp; Schedulers</strong> → Always have full access
76 + <strong>Admins &amp; Schedulers</strong>
77 + → Always have full access
78 </li>
79 </ul>
80 </div>
@@ -82,13 +83,16 @@
83 <strong>Untagged Alert Behavior:</strong>
84 <ul class="info-list">
85 <li>
85 - <strong>Visible to All:</strong> Everyone sees untagged alerts
86 + <strong>Visible to All:</strong>
87 + Everyone sees untagged alerts
88 </li>
89 <li>
88 - <strong>Admin Only:</strong> Only admins see untagged alerts
90 + <strong>Admin Only:</strong>
91 + Only admins see untagged alerts
92 </li>
93 <li>
91 - <strong>Default Tag:</strong> Users with the selected tag can see untagged alerts
94 + <strong>Default Tag:</strong>
95 + Users with the selected tag can see untagged alerts
96 </li>
97 </ul>
98 </div>
@@ -98,9 +102,9 @@
102 <div class="example">
103 <strong>Example:</strong>
104 <p>
101 - If analyst "John" is assigned the tag "Network", John will only see alerts tagged
102 - "Network". If untagged behavior is set to "Admin Only", John won't see any untagged
103 - alerts. If set to "Default Tag: Network", John will also see untagged alerts.
105 + If analyst "John" is assigned the tag "Network", John will only see alerts tagged "Network".
106 + If untagged behavior is set to "Admin Only", John won't see any untagged alerts. If set to
107 + "Default Tag: Network", John will also see untagged alerts.
108 </p>
109 </div>
110 </div>
@@ -117,27 +121,28 @@
121 </template>
122
123 <script setup lang="ts">
120 -import type { AlertTag } from "@/types/incidentManagement/tags.d"
124 +// TODO: refactor
125 +import type { AlertTag } from "@/types/tags"
126 import {
122 - NAlert,
123 - NButton,
124 - NDivider,
125 - NFormItem,
126 - NRadio,
127 - NRadioGroup,
128 - NSelect,
129 - NSpace,
130 - NSpin,
131 - NSwitch,
132 - useMessage
127 + NAlert,
128 + NButton,
129 + NDivider,
130 + NFormItem,
131 + NRadio,
132 + NRadioGroup,
133 + NSelect,
134 + NSpace,
135 + NSpin,
136 + NSwitch,
137 + useMessage
138 } from "naive-ui"
139 import { computed, onMounted, reactive, ref, watch } from "vue"
140 import Api from "@/api"
141
142 interface TagAccessSettings {
138 - enabled: boolean
139 - untagged_alert_behavior: "visible_to_all" | "admin_only" | "default_tag"
140 - default_tag_id: number | null
143 + enabled: boolean
144 + untagged_alert_behavior: "visible_to_all" | "admin_only" | "default_tag"
145 + default_tag_id: number | null
146 }
147
148 const message = useMessage()
@@ -148,184 +153,184 @@ const saving = ref(false)
153 const availableTags = ref<AlertTag[]>([])
154
155 const settings = reactive<TagAccessSettings>({
151 - enabled: false,
152 - untagged_alert_behavior: "visible_to_all",
153 - default_tag_id: null
156 + enabled: false,
157 + untagged_alert_behavior: "visible_to_all",
158 + default_tag_id: null
159 })
160
161 const originalSettings = ref<TagAccessSettings>({
157 - enabled: false,
158 - untagged_alert_behavior: "visible_to_all",
159 - default_tag_id: null
162 + enabled: false,
163 + untagged_alert_behavior: "visible_to_all",
164 + default_tag_id: null
165 })
166
167 const tagOptions = computed(() =>
163 - availableTags.value.map(tag => ({
164 - label: tag.tag,
165 - value: tag.id
166 - }))
168 + availableTags.value.map((tag: AlertTag) => ({
169 + label: tag.tag,
170 + value: tag.id
171 + }))
172 )
173
174 const hasChanges = computed(() => {
170 - return (
171 - settings.enabled !== originalSettings.value.enabled ||
172 - settings.untagged_alert_behavior !== originalSettings.value.untagged_alert_behavior ||
173 - settings.default_tag_id !== originalSettings.value.default_tag_id
174 - )
175 + return (
176 + settings.enabled !== originalSettings.value.enabled ||
177 + settings.untagged_alert_behavior !== originalSettings.value.untagged_alert_behavior ||
178 + settings.default_tag_id !== originalSettings.value.default_tag_id
179 + )
180 })
181
182 async function loadAvailableTags() {
178 - loadingTags.value = true
179 - try {
180 - const res = await Api.tagRbac.getAvailableTags()
181 - if (res.data.success) {
182 - availableTags.value = res.data.tags
183 - }
184 - } catch (error) {
185 - console.error("Failed to load available tags:", error)
186 - } finally {
187 - loadingTags.value = false
188 - }
183 + loadingTags.value = true
184 + try {
185 + const res = await Api.tagRbac.getAvailableTags()
186 + if (res.data.success) {
187 + availableTags.value = res.data.tags
188 + }
189 + } catch (error) {
190 + console.error("Failed to load available tags:", error)
191 + } finally {
192 + loadingTags.value = false
193 + }
194 }
195
196 async function loadSettings() {
192 - loading.value = true
193 - try {
194 - const res = await Api.tagRbac.getSettings()
195 -
196 - if (res.data.success && res.data.settings) {
197 - settings.enabled = res.data.settings.enabled
198 - settings.untagged_alert_behavior = res.data.settings.untagged_alert_behavior
199 - settings.default_tag_id = res.data.settings.default_tag_id
200 - originalSettings.value = {
201 - enabled: res.data.settings.enabled,
202 - untagged_alert_behavior: res.data.settings.untagged_alert_behavior,
203 - default_tag_id: res.data.settings.default_tag_id
204 - }
205 - }
206 - } catch (error) {
207 - console.error("Failed to load tag RBAC settings:", error)
208 - message.error("Failed to load Tag RBAC settings")
209 - } finally {
210 - loading.value = false
211 - }
197 + loading.value = true
198 + try {
199 + const res = await Api.tagRbac.getSettings()
200 +
201 + if (res.data.success && res.data.settings) {
202 + settings.enabled = res.data.settings.enabled
203 + settings.untagged_alert_behavior = res.data.settings.untagged_alert_behavior
204 + settings.default_tag_id = res.data.settings.default_tag_id
205 + originalSettings.value = {
206 + enabled: res.data.settings.enabled,
207 + untagged_alert_behavior: res.data.settings.untagged_alert_behavior,
208 + default_tag_id: res.data.settings.default_tag_id
209 + }
210 + }
211 + } catch (error) {
212 + console.error("Failed to load tag RBAC settings:", error)
213 + message.error("Failed to load Tag RBAC settings")
214 + } finally {
215 + loading.value = false
216 + }
217 }
218
219 async function saveSettings() {
215 - // Validate default_tag_id is set when using default_tag behavior
216 - if (settings.untagged_alert_behavior === "default_tag" && !settings.default_tag_id) {
217 - message.warning("Please select a default tag")
218 - return
219 - }
220 -
221 - saving.value = true
222 - try {
223 - const res = await Api.tagRbac.updateSettings({
224 - enabled: settings.enabled,
225 - untagged_alert_behavior: settings.untagged_alert_behavior,
226 - default_tag_id: settings.default_tag_id
227 - })
228 -
229 - if (res.data.success) {
230 - message.success("Tag RBAC settings saved")
231 - originalSettings.value = { ...settings }
232 - } else {
233 - message.error(res.data.message || "Failed to save settings")
234 - }
235 - } catch (error: any) {
236 - console.error("Failed to save settings:", error)
237 - message.error(error.response?.data?.message || "Failed to save settings")
238 - } finally {
239 - saving.value = false
240 - }
220 + // Validate default_tag_id is set when using default_tag behavior
221 + if (settings.untagged_alert_behavior === "default_tag" && !settings.default_tag_id) {
222 + message.warning("Please select a default tag")
223 + return
224 + }
225 +
226 + saving.value = true
227 + try {
228 + const res = await Api.tagRbac.updateSettings({
229 + enabled: settings.enabled,
230 + untagged_alert_behavior: settings.untagged_alert_behavior,
231 + default_tag_id: settings.default_tag_id
232 + })
233 +
234 + if (res.data.success) {
235 + message.success("Tag RBAC settings saved")
236 + originalSettings.value = { ...settings }
237 + } else {
238 + message.error(res.data.message || "Failed to save settings")
239 + }
240 + } catch (error: any) {
241 + console.error("Failed to save settings:", error)
242 + message.error(error.response?.data?.message || "Failed to save settings")
243 + } finally {
244 + saving.value = false
245 + }
246 }
247
248 function resetSettings() {
244 - settings.enabled = originalSettings.value.enabled
245 - settings.untagged_alert_behavior = originalSettings.value.untagged_alert_behavior
246 - settings.default_tag_id = originalSettings.value.default_tag_id
249 + settings.enabled = originalSettings.value.enabled
250 + settings.untagged_alert_behavior = originalSettings.value.untagged_alert_behavior
251 + settings.default_tag_id = originalSettings.value.default_tag_id
252 }
253
254 // Load tags when default_tag behavior is selected
255 watch(
251 - () => settings.untagged_alert_behavior,
252 - newValue => {
253 - if (newValue === "default_tag" && availableTags.value.length === 0) {
254 - loadAvailableTags()
255 - }
256 - }
256 + () => settings.untagged_alert_behavior,
257 + newValue => {
258 + if (newValue === "default_tag" && availableTags.value.length === 0) {
259 + loadAvailableTags()
260 + }
261 + }
262 )
263
264 onMounted(async () => {
260 - await loadSettings()
261 - // Pre-load tags if default_tag is already selected
262 - if (settings.untagged_alert_behavior === "default_tag") {
263 - await loadAvailableTags()
264 - }
265 + await loadSettings()
266 + // Pre-load tags if default_tag is already selected
267 + if (settings.untagged_alert_behavior === "default_tag") {
268 + await loadAvailableTags()
269 + }
270 })
271 </script>
272
273 <style scoped lang="scss">
274 .settings-form {
270 - .radio-label {
271 - display: flex;
272 - flex-direction: column;
273 -
274 - .label {
275 - font-weight: 500;
276 - }
277 -
278 - .description {
279 - font-size: 12px;
280 - opacity: 0.7;
281 - }
282 - }
283 -
284 - .info-content {
285 - .intro {
286 - margin: 0 0 12px 0;
287 - font-weight: 500;
288 - }
289 -
290 - .section {
291 - margin-bottom: 12px;
292 -
293 - > strong {
294 - display: block;
295 - margin-bottom: 4px;
296 - }
297 - }
298 -
299 - .info-list {
300 - margin: 0;
301 - padding-left: 20px;
302 -
303 - li {
304 - margin-bottom: 4px;
305 -
306 - em {
307 - font-style: normal;
308 - text-decoration: underline;
309 - }
310 - }
311 - }
312 -
313 - .example {
314 - background: rgba(0, 0, 0, 0.05);
315 - border-radius: 4px;
316 - padding: 10px;
317 -
318 - > strong {
319 - display: block;
320 - margin-bottom: 4px;
321 - }
322 -
323 - p {
324 - margin: 0;
325 - font-size: 13px;
326 - line-height: 1.5;
327 - }
328 - }
329 - }
275 + .radio-label {
276 + display: flex;
277 + flex-direction: column;
278 +
279 + .label {
280 + font-weight: 500;
281 + }
282 +
283 + .description {
284 + font-size: 12px;
285 + opacity: 0.7;
286 + }
287 + }
288 +
289 + .info-content {
290 + .intro {
291 + margin: 0 0 12px 0;
292 + font-weight: 500;
293 + }
294 +
295 + .section {
296 + margin-bottom: 12px;
297 +
298 + > strong {
299 + display: block;
300 + margin-bottom: 4px;
301 + }
302 + }
303 +
304 + .info-list {
305 + margin: 0;
306 + padding-left: 20px;
307 +
308 + li {
309 + margin-bottom: 4px;
310 +
311 + em {
312 + font-style: normal;
313 + text-decoration: underline;
314 + }
315 + }
316 + }
317 +
318 + .example {
319 + background: rgba(0, 0, 0, 0.05);
320 + border-radius: 4px;
321 + padding: 10px;
322 +
323 + > strong {
324 + display: block;
325 + margin-bottom: 4px;
326 + }
327 +
328 + p {
329 + margin: 0;
330 + font-size: 13px;
331 + line-height: 1.5;
332 + }
333 + }
334 + }
335 }
336 </style>
frontend/src/components/users/UsersList.vue
+94 -93
@@ -106,6 +106,7 @@
106 </template>
107
108 <script setup lang="ts">
109 +// TODO: refactor
110 import type { User } from "@/types/user.d"
111 import { NButton, NDropdown, NModal, NScrollbar, NSpin, NTable, NTag, useMessage } from "naive-ui"
112 import { computed, defineAsyncComponent, h, onBeforeMount, ref } from "vue"
@@ -138,118 +139,118 @@ const usernameList = computed(() => usersList.value.map(user => user.username))
139 const emailList = computed(() => usersList.value.map(user => user.email))
140
141 function getRoleTagType(roleName: string | null | undefined) {
141 - switch (roleName?.toLowerCase()) {
142 - case "admin":
143 - return "error"
144 - case "analyst":
145 - return "warning"
146 - case "scheduler":
147 - return "info"
148 - case "customer_user":
149 - return "success"
150 - default:
151 - return "default"
152 - }
142 + switch (roleName?.toLowerCase()) {
143 + case "admin":
144 + return "error"
145 + case "analyst":
146 + return "warning"
147 + case "scheduler":
148 + return "info"
149 + case "customer_user":
150 + return "success"
151 + default:
152 + return "default"
153 + }
154 }
155
156 const options = [
156 - {
157 - key: "AssignRole",
158 - type: "render",
159 - render: () =>
160 - h(AssignRole, {
161 - user: selectedUser.value || undefined,
162 - onSuccess: getUsers
163 - })
164 - },
165 - {
166 - key: "AssignCustomer",
167 - type: "render",
168 - render: () =>
169 - h(AssignCustomer, {
170 - user: selectedUser.value || undefined,
171 - onSuccess: getUsers
172 - })
173 - },
174 - {
175 - key: "AssignTags",
176 - type: "render",
177 - render: () =>
178 - h(AssignTags, {
179 - user: selectedUser.value || undefined,
180 - onSuccess: getUsers
181 - })
182 - },
183 - {
184 - key: "ChangePassword",
185 - type: "render",
186 - render: () => h(ChangePassword, { user: selectedUser.value || undefined })
187 - },
188 - {
189 - key: "DeleteUser",
190 - type: "render",
191 - render: () =>
192 - h(DeleteUser, {
193 - user: selectedUser.value || undefined,
194 - onSuccess: getUsers,
195 - onLoading: updateLoadingDelete
196 - })
197 - }
157 + {
158 + key: "AssignRole",
159 + type: "render",
160 + render: () =>
161 + h(AssignRole, {
162 + user: selectedUser.value || undefined,
163 + onSuccess: getUsers
164 + })
165 + },
166 + {
167 + key: "AssignCustomer",
168 + type: "render",
169 + render: () =>
170 + h(AssignCustomer, {
171 + user: selectedUser.value || undefined,
172 + onSuccess: getUsers
173 + })
174 + },
175 + {
176 + key: "AssignTags",
177 + type: "render",
178 + render: () =>
179 + h(AssignTags, {
180 + user: selectedUser.value || undefined,
181 + onSuccess: getUsers
182 + })
183 + },
184 + {
185 + key: "ChangePassword",
186 + type: "render",
187 + render: () => h(ChangePassword, { user: selectedUser.value || undefined })
188 + },
189 + {
190 + key: "DeleteUser",
191 + type: "render",
192 + render: () =>
193 + h(DeleteUser, {
194 + user: selectedUser.value || undefined,
195 + onSuccess: getUsers,
196 + onLoading: updateLoadingDelete
197 + })
198 + }
199 ]
200
201 function updateLoadingDelete(value: boolean) {
201 - loadingDelete.value = value
202 + loadingDelete.value = value
203 }
204
205 function addUserSuccess() {
205 - getUsers()
206 - showForm.value = false
206 + getUsers()
207 + showForm.value = false
208 }
209
210 function getUsers() {
210 - loadingUsers.value = true
211 -
212 - Api.users
213 - .getUsers()
214 - .then(res => {
215 - if (res.data.success) {
216 - usersList.value = res.data?.users || []
217 - } else {
218 - message.warning(res.data?.message || "An error occurred. Please try again later.")
219 - }
220 - })
221 - .catch(err => {
222 - usersList.value = []
223 -
224 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
225 - })
226 - .finally(() => {
227 - loadingUsers.value = false
228 - })
211 + loadingUsers.value = true
212 +
213 + Api.users
214 + .getUsers()
215 + .then(res => {
216 + if (res.data.success) {
217 + usersList.value = res.data?.users || []
218 + } else {
219 + message.warning(res.data?.message || "An error occurred. Please try again later.")
220 + }
221 + })
222 + .catch(err => {
223 + usersList.value = []
224 +
225 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
226 + })
227 + .finally(() => {
228 + loadingUsers.value = false
229 + })
230 }
231
232 onBeforeMount(() => {
232 - getUsers()
233 + getUsers()
234 })
235 </script>
236
237 <style lang="scss" scoped>
238 .users-list {
238 - border-radius: var(--border-radius);
239 - overflow: hidden;
240 -
241 - tr:hover {
242 - td {
243 - background-color: rgba(var(--primary-color-rgb) / 0.05);
244 - }
245 - }
246 -
247 - .highlight {
248 - td {
249 - border-top: 1px solid rgba(var(--primary-color-rgb) / 0.3);
250 - border-bottom: 1px solid rgba(var(--primary-color-rgb) / 0.3);
251 - background-color: rgba(var(--primary-color-rgb) / 0.05);
252 - }
253 - }
239 + border-radius: var(--border-radius);
240 + overflow: hidden;
241 +
242 + tr:hover {
243 + td {
244 + background-color: rgba(var(--primary-color-rgb) / 0.05);
245 + }
246 + }
247 +
248 + .highlight {
249 + td {
250 + border-top: 1px solid rgba(var(--primary-color-rgb) / 0.3);
251 + border-bottom: 1px solid rgba(var(--primary-color-rgb) / 0.3);
252 + background-color: rgba(var(--primary-color-rgb) / 0.05);
253 + }
254 + }
255 }
256 </style>
frontend/src/components/vulnerabilities/GenerateReportForm.vue
+56 -59
@@ -1,11 +1,7 @@
1 <template>
2 <n-form ref="formRef" :model="formData" :rules="rules" label-placement="top">
3 <n-form-item label="Report Name" path="report_name">
4 - <n-input
5 - v-model:value="formData.report_name"
6 - placeholder="Leave empty for auto-generated name"
7 - clearable
8 - />
4 + <n-input v-model:value="formData.report_name" placeholder="Leave empty for auto-generated name" clearable />
5 </n-form-item>
6
7 <n-form-item label="Customer" path="customer_code" required>
@@ -47,7 +43,7 @@
43 </n-switch>
44 </n-form-item>
45
50 - <div class="flex justify-end gap-3 mt-6">
46 + <div class="mt-6 flex justify-end gap-3">
47 <n-button @click="$emit('cancel')">Cancel</n-button>
48 <n-button type="primary" :loading="loading" @click="handleSubmit">Generate Report</n-button>
49 </div>
@@ -55,6 +51,7 @@
51 </template>
52
53 <script setup lang="ts">
54 +// TODO: refactor
55 import type { FormInst, FormRules } from "naive-ui"
56 import type { VulnerabilityReportGenerateRequest } from "@/types/vulnerabilities.d"
57 import { NButton, NDivider, NForm, NFormItem, NInput, NSelect, NSwitch } from "naive-ui"
@@ -62,76 +59,76 @@ import { ref } from "vue"
59 import { VulnerabilitySeverity } from "@/types/vulnerabilities.d"
60
61 interface Props {
65 - customers: Array<{ label: string; value: string }>
66 - loading?: boolean
62 + customers: Array<{ label: string; value: string }>
63 + loading?: boolean
64 }
65
66 defineProps<Props>()
67
68 const emit = defineEmits<{
72 - (e: "generate", value: VulnerabilityReportGenerateRequest): void
73 - (e: "cancel"): void
69 + (e: "generate", value: VulnerabilityReportGenerateRequest): void
70 + (e: "cancel"): void
71 }>()
72
73 const formRef = ref<FormInst | null>(null)
74 const formData = ref<VulnerabilityReportGenerateRequest>({
78 - customer_code: "",
79 - report_name: "",
80 - agent_name: "",
81 - severity: undefined,
82 - cve_id: "",
83 - package_name: "",
84 - include_epss: false
75 + customer_code: "",
76 + report_name: "",
77 + agent_name: "",
78 + severity: undefined,
79 + cve_id: "",
80 + package_name: "",
81 + include_epss: false
82 })
83
84 const severityOptions = [
88 - { label: "Critical", value: VulnerabilitySeverity.Critical },
89 - { label: "High", value: VulnerabilitySeverity.High },
90 - { label: "Medium", value: VulnerabilitySeverity.Medium },
91 - { label: "Low", value: VulnerabilitySeverity.Low }
85 + { label: "Critical", value: VulnerabilitySeverity.Critical },
86 + { label: "High", value: VulnerabilitySeverity.High },
87 + { label: "Medium", value: VulnerabilitySeverity.Medium },
88 + { label: "Low", value: VulnerabilitySeverity.Low }
89 ]
90
91 const rules: FormRules = {
95 - customer_code: [
96 - {
97 - required: true,
98 - message: "Please select a customer",
99 - trigger: ["blur", "change"]
100 - }
101 - ]
92 + customer_code: [
93 + {
94 + required: true,
95 + message: "Please select a customer",
96 + trigger: ["blur", "change"]
97 + }
98 + ]
99 }
100
101 async function handleSubmit() {
105 - if (!formRef.value) return
106 -
107 - try {
108 - await formRef.value.validate()
109 -
110 - // Clean up empty strings
111 - const request: VulnerabilityReportGenerateRequest = {
112 - customer_code: formData.value.customer_code,
113 - include_epss: formData.value.include_epss
114 - }
115 -
116 - if (formData.value.report_name?.trim()) {
117 - request.report_name = formData.value.report_name.trim()
118 - }
119 - if (formData.value.agent_name?.trim()) {
120 - request.agent_name = formData.value.agent_name.trim()
121 - }
122 - if (formData.value.severity) {
123 - request.severity = formData.value.severity
124 - }
125 - if (formData.value.cve_id?.trim()) {
126 - request.cve_id = formData.value.cve_id.trim()
127 - }
128 - if (formData.value.package_name?.trim()) {
129 - request.package_name = formData.value.package_name.trim()
130 - }
131 -
132 - emit("generate", request)
133 - } catch (error) {
134 - console.error("Form validation failed:", error)
135 - }
102 + if (!formRef.value) return
103 +
104 + try {
105 + await formRef.value.validate()
106 +
107 + // Clean up empty strings
108 + const request: VulnerabilityReportGenerateRequest = {
109 + customer_code: formData.value.customer_code,
110 + include_epss: formData.value.include_epss
111 + }
112 +
113 + if (formData.value.report_name?.trim()) {
114 + request.report_name = formData.value.report_name.trim()
115 + }
116 + if (formData.value.agent_name?.trim()) {
117 + request.agent_name = formData.value.agent_name.trim()
118 + }
119 + if (formData.value.severity) {
120 + request.severity = formData.value.severity
121 + }
122 + if (formData.value.cve_id?.trim()) {
123 + request.cve_id = formData.value.cve_id.trim()
124 + }
125 + if (formData.value.package_name?.trim()) {
126 + request.package_name = formData.value.package_name.trim()
127 + }
128 +
129 + emit("generate", request)
130 + } catch (error) {
131 + console.error("Form validation failed:", error)
132 + }
133 }
134 </script>
frontend/src/components/vulnerabilities/List.vue
+2
@@ -176,6 +176,8 @@ watchDebounced(
176
177 useResizeObserver(header, entries => {
178 const entry = entries[0]
179 + if (!entry) return
180 +
181 const { width } = entry.contentRect
182
183 pageSlot.value = width < 700 ? 5 : 8
frontend/src/components/vulnerabilities/ListFilters.vue
+1 -1
@@ -203,7 +203,7 @@ function setFilter(newFilters: VulnerabilitiesListFilter[]) {
203 const filterIndex = filters.value.findIndex(o => o.type === newFilter.type)
204
205 if (filterIndex !== -1) {
206 - if (newFilter.value) {
206 + if (newFilter.value && filters.value[filterIndex]) {
207 filters.value[filterIndex].value = newFilter.value
208 } else {
209 delFilter(newFilter.type)
frontend/src/components/vulnerabilities/VulnerabilityCard.vue
+5 -4
@@ -46,7 +46,7 @@
46 <template #value>
47 <code
48 class="text-primary cursor-pointer"
49 - @click.stop="gotoCustomer({ code: vulnerability.customer_code })"
49 + @click.stop="routeCustomer({ code: vulnerability.customer_code }).navigate()"
50 >
51 customer #{{ vulnerability.customer_code }}
52 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -93,22 +93,23 @@
93 </template>
94
95 <script setup lang="ts">
96 +// TODO: review VulnerabilityCard (this component)
97 import type { VulnerabilitySearchItem } from "@/types/vulnerabilities.d"
98 import { NModal } from "naive-ui"
99 import { ref } from "vue"
100 import Badge from "@/components/common/Badge.vue"
101 import CardEntity from "@/components/common/cards/CardEntity.vue"
102 import Icon from "@/components/common/Icon.vue"
102 -import { useGoto } from "@/composables/useGoto"
103 +import { useNavigation } from "@/composables/useNavigation"
104 import { useSettingsStore } from "@/stores/settings"
105 import { VulnerabilitySeverity } from "@/types/vulnerabilities.d"
105 -import { formatDate } from "@/utils"
106 +import { formatDate } from "@/utils/format"
107 import VulnerabilityCardContent from "./VulnerabilityCardContent.vue"
108 import VulnerabilitySeverityBadge from "./VulnerabilitySeverityBadge.vue"
109
110 const { vulnerability } = defineProps<{ vulnerability: VulnerabilitySearchItem; embedded?: boolean }>()
111
111 -const { gotoCustomer } = useGoto()
112 +const { routeCustomer } = useNavigation()
113 const dFormats = useSettingsStore().dateFormat
114
115 const showDetails = ref(false)
frontend/src/components/vulnerabilities/VulnerabilityCardContent.vue
+6 -5
@@ -32,7 +32,7 @@
32 <template #value>
33 <code
34 class="text-primary cursor-pointer"
35 - @click.stop="gotoCustomer({ code: vulnerability.customer_code })"
35 + @click.stop="routeCustomer({ code: vulnerability.customer_code }).navigate()"
36 >
37 #{{ vulnerability.customer_code }}
38 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -106,20 +106,21 @@
106 </template>
107
108 <script setup lang="ts">
109 +// TODO: review VulnerabilityCardContent (this component)
110 import type { VulnerabilitySearchItem } from "@/types/vulnerabilities.d"
111 import { NCard, NEmpty, NStatistic, NTabPane, NTabs, NTimeline, NTimelineItem } from "naive-ui"
112 import CardKV from "@/components/common/cards/CardKV.vue"
113 import Icon from "@/components/common/Icon.vue"
113 -import { useGoto } from "@/composables/useGoto"
114 +import { useNavigation } from "@/composables/useNavigation"
115 import { useSettingsStore } from "@/stores/settings"
115 -import { formatDate } from "@/utils"
116 +import { formatDate } from "@/utils/format"
117 import VulnerabilitySeverityBadge from "./VulnerabilitySeverityBadge.vue"
118
119 const { vulnerability } = defineProps<{ vulnerability: VulnerabilitySearchItem }>()
120
121 const dFormats = useSettingsStore().dateFormat
122
122 -const { gotoCustomer } = useGoto()
123 +const { routeCustomer } = useNavigation()
124
125 const LinkIcon = "carbon:launch"
126
@@ -144,7 +145,7 @@ function parseReferences(references: string): string[] {
145 .filter(ref => ref.length > 0)
146
147 // 3. Space separated URLs (if they start with http)
147 - if (refs.length === 1 && refs[0].includes("http")) {
148 + if (refs.length === 1 && refs?.[0]?.includes("http")) {
149 const spaceRefs = refs[0].split(/\s+/).filter(ref => ref.startsWith("http"))
150 if (spaceRefs.length > 1) {
151 refs = spaceRefs
frontend/src/components/vulnerabilities/VulnerabilityReports.vue
+244 -241
@@ -2,7 +2,7 @@
2 <div class="vulnerability-reports">
3 <div class="header mb-6 flex items-center justify-between">
4 <div>
5 - <h2 class="text-2xl font-bold mb-2">Vulnerability Reports</h2>
5 + <h2 class="mb-2 text-2xl font-bold">Vulnerability Reports</h2>
6 <p class="text-secondary">Generate, manage, and download vulnerability reports</p>
7 </div>
8 <n-button type="primary" size="large" @click="showGenerateModal = true">
@@ -50,7 +50,7 @@
50 v-model:show="showGenerateModal"
51 preset="card"
52 title="Generate Vulnerability Report"
53 - style="width: 600px; max-width: 90vw;"
53 + style="width: 600px; max-width: 90vw"
54 :closable="true"
55 >
56 <GenerateReportForm
@@ -78,12 +78,14 @@
78 </template>
79
80 <script setup lang="ts">
81 +// TODO: review VulnerabilityReports (this component)
82 import type { DataTableColumns } from "naive-ui"
83 import type { VulnerabilityReport, VulnerabilityReportGenerateRequest } from "@/types/vulnerabilities.d"
84 import { NButton, NCard, NDataTable, NModal, NSelect, NSpace, NTag, NTooltip, useMessage } from "naive-ui"
85 import { computed, h, onMounted, ref } from "vue"
86 import Api from "@/api"
87 import Icon from "@/components/common/Icon.vue"
88 +import { useSettingsStore } from "@/stores/settings"
89 import { formatBytes, formatDate } from "@/utils/format"
90 import GenerateReportForm from "./GenerateReportForm.vue"
91
@@ -96,6 +98,7 @@ const ErrorIcon = "carbon:warning-filled"
98 const LoadingIcon = "eos-icons:loading"
99
100 const message = useMessage()
101 +const dFormats = useSettingsStore().dateFormat
102
103 const loading = ref(false)
104 const generating = ref(false)
@@ -106,278 +109,278 @@ const reportToDelete = ref<VulnerabilityReport | null>(null)
109 const filterCustomerCode = ref<string | null>(null)
110 const customers = ref<Array<{ label: string; value: string }>>([])
111
109 -const customerOptions = computed(() => [
110 - ...customers.value
111 -])
112 +const customerOptions = computed(() => [...customers.value])
113
114 const pagination = {
114 - pageSize: 20,
115 - showSizePicker: true,
116 - pageSizes: [10, 20, 50, 100]
115 + pageSize: 20,
116 + showSizePicker: true,
117 + pageSizes: [10, 20, 50, 100]
118 }
119
120 const columns: DataTableColumns<VulnerabilityReport> = [
120 - {
121 - title: "Report Name",
122 - key: "report_name",
123 - ellipsis: {
124 - tooltip: true
125 - }
126 - },
127 - {
128 - title: "Customer",
129 - key: "customer_code",
130 - width: 120
131 - },
132 - {
133 - title: "Status",
134 - key: "status",
135 - width: 120,
136 - render: (row: VulnerabilityReport) => {
137 - const statusMap = {
138 - completed: { type: "success", icon: CheckIcon, text: "Completed" },
139 - processing: { type: "warning", icon: LoadingIcon, text: "Processing" },
140 - failed: { type: "error", icon: ErrorIcon, text: "Failed" }
141 - }
142 - const status = statusMap[row.status]
143 - return h(
144 - NTag,
145 - { type: status.type as any, size: "small" },
146 - {
147 - default: () => status.text,
148 - icon: () => h(Icon, { name: status.icon, size: "14" })
149 - }
150 - )
151 - }
152 - },
153 - {
154 - title: "Vulnerabilities",
155 - key: "total_vulnerabilities",
156 - width: 140,
157 - render: (row: VulnerabilityReport) => {
158 - return h(
159 - NTooltip,
160 - {},
161 - {
162 - trigger: () => row.total_vulnerabilities.toLocaleString(),
163 - default: () => h("div", {}, [
164 - h("div", {}, `Critical: ${row.critical_count}`),
165 - h("div", {}, `High: ${row.high_count}`),
166 - h("div", {}, `Medium: ${row.medium_count}`),
167 - h("div", {}, `Low: ${row.low_count}`)
168 - ])
169 - }
170 - )
171 - }
172 - },
173 - {
174 - title: "File Size",
175 - key: "file_size",
176 - width: 120,
177 - render: (row: VulnerabilityReport) => formatBytes(row.file_size)
178 - },
179 - {
180 - title: "Generated",
181 - key: "generated_at",
182 - width: 180,
183 - render: (row: VulnerabilityReport) => formatDate(row.generated_at)
184 - },
185 - {
186 - title: "Actions",
187 - key: "actions",
188 - width: 140,
189 - render: (row: VulnerabilityReport) => {
190 - return h(
191 - NSpace,
192 - { size: "small" },
193 - {
194 - default: () => [
195 - row.status === "completed"
196 - ? h(
197 - NButton,
198 - {
199 - size: "small",
200 - type: "primary",
201 - onClick: () => handleDownload(row)
202 - },
203 - {
204 - icon: () => h(Icon, { name: DownloadIcon })
205 - }
206 - )
207 - : null,
208 - h(
209 - NButton,
210 - {
211 - size: "small",
212 - type: "error",
213 - onClick: () => handleDeleteClick(row)
214 - },
215 - {
216 - icon: () => h(Icon, { name: DeleteIcon })
217 - }
218 - )
219 - ]
220 - }
221 - )
222 - }
223 - }
121 + {
122 + title: "Report Name",
123 + key: "report_name",
124 + ellipsis: {
125 + tooltip: true
126 + }
127 + },
128 + {
129 + title: "Customer",
130 + key: "customer_code",
131 + width: 120
132 + },
133 + {
134 + title: "Status",
135 + key: "status",
136 + width: 120,
137 + render: (row: VulnerabilityReport) => {
138 + const statusMap = {
139 + completed: { type: "success", icon: CheckIcon, text: "Completed" },
140 + processing: { type: "warning", icon: LoadingIcon, text: "Processing" },
141 + failed: { type: "error", icon: ErrorIcon, text: "Failed" }
142 + }
143 + const status = statusMap[row.status]
144 + return h(
145 + NTag,
146 + { type: status.type as any, size: "small" },
147 + {
148 + default: () => status.text,
149 + icon: () => h(Icon, { name: status.icon, size: 14 })
150 + }
151 + )
152 + }
153 + },
154 + {
155 + title: "Vulnerabilities",
156 + key: "total_vulnerabilities",
157 + width: 140,
158 + render: (row: VulnerabilityReport) => {
159 + return h(
160 + NTooltip,
161 + {},
162 + {
163 + trigger: () => row.total_vulnerabilities.toLocaleString(),
164 + default: () =>
165 + h("div", {}, [
166 + h("div", {}, `Critical: ${row.critical_count}`),
167 + h("div", {}, `High: ${row.high_count}`),
168 + h("div", {}, `Medium: ${row.medium_count}`),
169 + h("div", {}, `Low: ${row.low_count}`)
170 + ])
171 + }
172 + )
173 + }
174 + },
175 + {
176 + title: "File Size",
177 + key: "file_size",
178 + width: 120,
179 + render: (row: VulnerabilityReport) => formatBytes(row.file_size)
180 + },
181 + {
182 + title: "Generated",
183 + key: "generated_at",
184 + width: 180,
185 + render: (row: VulnerabilityReport) => `${formatDate(row.generated_at, dFormats.datetime)}`
186 + },
187 + {
188 + title: "Actions",
189 + key: "actions",
190 + width: 140,
191 + render: (row: VulnerabilityReport) => {
192 + return h(
193 + NSpace,
194 + { size: "small" },
195 + {
196 + default: () => [
197 + row.status === "completed"
198 + ? h(
199 + NButton,
200 + {
201 + size: "small",
202 + type: "primary",
203 + onClick: () => handleDownload(row)
204 + },
205 + {
206 + icon: () => h(Icon, { name: DownloadIcon })
207 + }
208 + )
209 + : null,
210 + h(
211 + NButton,
212 + {
213 + size: "small",
214 + type: "error",
215 + onClick: () => handleDeleteClick(row)
216 + },
217 + {
218 + icon: () => h(Icon, { name: DeleteIcon })
219 + }
220 + )
221 + ]
222 + }
223 + )
224 + }
225 + }
226 ]
227
228 async function loadReports() {
227 - loading.value = true
228 - try {
229 - const response = await Api.vulnerabilities.listReports(filterCustomerCode.value || undefined)
230 - if (response.data.success) {
231 - reports.value = response.data.reports
232 - } else {
233 - message.error(response.data.message || "Failed to load reports")
234 - }
235 - } catch (error: any) {
236 - message.error(error?.response?.data?.detail || "Failed to load reports")
237 - } finally {
238 - loading.value = false
239 - }
229 + loading.value = true
230 + try {
231 + const response = await Api.vulnerabilities.listReports(filterCustomerCode.value || undefined)
232 + if (response.data.success) {
233 + reports.value = response.data.reports
234 + } else {
235 + message.error(response.data.message || "Failed to load reports")
236 + }
237 + } catch (error: any) {
238 + message.error(error?.response?.data?.detail || "Failed to load reports")
239 + } finally {
240 + loading.value = false
241 + }
242 }
243
244 async function loadCustomers() {
243 - try {
244 - // Try the customers API endpoint
245 - const response = await Api.customers.getCustomers()
246 -
247 - // Handle different possible response structures
248 - let customerData = response.data
249 -
250 - // If response.data is wrapped in a data property
251 - if (customerData.data) {
252 - customerData = customerData.data
253 - }
254 -
255 - // If it's an array, map it
256 - if (Array.isArray(customerData)) {
257 - customers.value = customerData.map((c: any) => ({
258 - label: `${c.customer_name || c.name || c.customer_code} (${c.customer_code})`,
259 - value: c.customer_code
260 - }))
261 - } else if (customerData.customers && Array.isArray(customerData.customers)) {
262 - // If customers are in a customers property
263 - customers.value = customerData.customers.map((c: any) => ({
264 - label: `${c.customer_name || c.name || c.customer_code} (${c.customer_code})`,
265 - value: c.customer_code
266 - }))
267 - } else {
268 - console.warn("Unexpected customer data structure:", customerData)
269 - message.warning("Customer data format unexpected")
270 - }
271 -
272 - console.log("Loaded customers:", customers.value)
273 - } catch (error: any) {
274 - console.error("Failed to load customers:", error)
275 - message.error("Failed to load customers list")
276 - }
245 + try {
246 + // Try the customers API endpoint
247 + const response = await Api.customers.getCustomers()
248 +
249 + // Handle different possible response structures
250 + let customerData = response.data
251 +
252 + // If response.data is wrapped in a data property
253 + // TODO: review this (customerData.data) - it's not in the type definition
254 + // @ts-expect-error - customerData.data is not in the type definition
255 + if (customerData.data) {
256 + // @ts-expect-error - customerData.data is not in the type definition
257 + customerData = customerData.data
258 + }
259 +
260 + // If it's an array, map it
261 + if (Array.isArray(customerData)) {
262 + customers.value = customerData.map((c: any) => ({
263 + label: `${c.customer_name || c.name || c.customer_code} (${c.customer_code})`,
264 + value: c.customer_code
265 + }))
266 + } else if (customerData.customers && Array.isArray(customerData.customers)) {
267 + // If customers are in a customers property
268 + customers.value = customerData.customers.map((c: any) => ({
269 + label: `${c.customer_name || c.name || c.customer_code} (${c.customer_code})`,
270 + value: c.customer_code
271 + }))
272 + } else {
273 + console.warn("Unexpected customer data structure:", customerData)
274 + message.warning("Customer data format unexpected")
275 + }
276 + } catch (error: any) {
277 + console.error("Failed to load customers:", error)
278 + message.error("Failed to load customers list")
279 + }
280 }
281
282 async function handleGenerateReport(request: VulnerabilityReportGenerateRequest) {
280 - generating.value = true
281 - try {
282 - // Use background generation for better UX
283 - const response = await Api.vulnerabilities.generateReportBackground(request)
284 -
285 - if (response.data.success) {
286 - message.success(response.data.message)
287 - showGenerateModal.value = false
288 -
289 - // Add the new processing report to the list
290 - await loadReports()
291 -
292 - // Start polling for status updates
293 - startStatusPolling(response.data.report_id)
294 - } else {
295 - message.error("Failed to queue report generation")
296 - }
297 - } catch (error: any) {
298 - message.error(error?.response?.data?.detail || "Failed to generate report")
299 - } finally {
300 - generating.value = false
301 - }
283 + generating.value = true
284 + try {
285 + // Use background generation for better UX
286 + const response = await Api.vulnerabilities.generateReportBackground(request)
287 +
288 + if (response.data.success) {
289 + message.success(response.data.message)
290 + showGenerateModal.value = false
291 +
292 + // Add the new processing report to the list
293 + await loadReports()
294 +
295 + // Start polling for status updates
296 + startStatusPolling(response.data.report_id)
297 + } else {
298 + message.error("Failed to queue report generation")
299 + }
300 + } catch (error: any) {
301 + message.error(error?.response?.data?.detail || "Failed to generate report")
302 + } finally {
303 + generating.value = false
304 + }
305 }
306
307 function startStatusPolling(reportId: number) {
305 - const pollInterval = setInterval(async () => {
306 - try {
307 - await loadReports()
308 -
309 - const report = reports.value.find(r => r.id === reportId)
310 - if (report && report.status !== "processing") {
311 - clearInterval(pollInterval)
312 -
313 - if (report.status === "completed") {
314 - message.success(`Report "${report.report_name}" completed successfully`)
315 - } else if (report.status === "failed") {
316 - message.error(`Report "${report.report_name}" failed: ${report.error_message}`)
317 - }
318 - }
319 - } catch (error) {
320 - clearInterval(pollInterval)
321 - }
322 - }, 3000) // Poll every 3 seconds
323 -
324 - // Stop polling after 5 minutes
325 - setTimeout(() => clearInterval(pollInterval), 300000)
308 + const pollInterval = setInterval(async () => {
309 + try {
310 + await loadReports()
311 +
312 + const report = reports.value.find(r => r.id === reportId)
313 + if (report && report.status !== "processing") {
314 + clearInterval(pollInterval)
315 +
316 + if (report.status === "completed") {
317 + message.success(`Report "${report.report_name}" completed successfully`)
318 + } else if (report.status === "failed") {
319 + message.error(`Report "${report.report_name}" failed: ${report.error_message}`)
320 + }
321 + }
322 + } catch {
323 + clearInterval(pollInterval)
324 + }
325 + }, 3000) // Poll every 3 seconds
326 +
327 + // Stop polling after 5 minutes
328 + setTimeout(() => clearInterval(pollInterval), 300000)
329 }
330
331 async function handleDownload(report: VulnerabilityReport) {
329 - try {
330 - const response = await Api.vulnerabilities.downloadReport(report.id)
331 -
332 - // Create download link
333 - const url = window.URL.createObjectURL(new Blob([response.data]))
334 - const link = document.createElement("a")
335 - link.href = url
336 - link.setAttribute("download", report.file_name)
337 - document.body.appendChild(link)
338 - link.click()
339 - link.remove()
340 - window.URL.revokeObjectURL(url)
341 -
342 - message.success("Report downloaded successfully")
343 - } catch (error: any) {
344 - message.error(error?.response?.data?.detail || "Failed to download report")
345 - }
332 + try {
333 + const response = await Api.vulnerabilities.downloadReport(report.id)
334 +
335 + // Create download link
336 + const url = window.URL.createObjectURL(new Blob([response.data]))
337 + const link = document.createElement("a")
338 + link.href = url
339 + link.setAttribute("download", report.file_name)
340 + document.body.appendChild(link)
341 + link.click()
342 + link.remove()
343 + window.URL.revokeObjectURL(url)
344 +
345 + message.success("Report downloaded successfully")
346 + } catch (error: any) {
347 + message.error(error?.response?.data?.detail || "Failed to download report")
348 + }
349 }
350
351 function handleDeleteClick(report: VulnerabilityReport) {
349 - reportToDelete.value = report
350 - showDeleteModal.value = true
352 + reportToDelete.value = report
353 + showDeleteModal.value = true
354 }
355
356 async function confirmDelete() {
354 - if (!reportToDelete.value) return
355 -
356 - try {
357 - const response = await Api.vulnerabilities.deleteReport(reportToDelete.value.id)
358 -
359 - if (response.data.success) {
360 - message.success(response.data.message)
361 - await loadReports()
362 - } else {
363 - message.error("Failed to delete report")
364 - }
365 - } catch (error: any) {
366 - message.error(error?.response?.data?.detail || "Failed to delete report")
367 - } finally {
368 - showDeleteModal.value = false
369 - reportToDelete.value = null
370 - }
357 + if (!reportToDelete.value) return
358 +
359 + try {
360 + const response = await Api.vulnerabilities.deleteReport(reportToDelete.value.id)
361 +
362 + if (response.data.success) {
363 + message.success(response.data.message)
364 + await loadReports()
365 + } else {
366 + message.error("Failed to delete report")
367 + }
368 + } catch (error: any) {
369 + message.error(error?.response?.data?.detail || "Failed to delete report")
370 + } finally {
371 + showDeleteModal.value = false
372 + reportToDelete.value = null
373 + }
374 }
375
376 onMounted(() => {
374 - loadReports()
375 - loadCustomers()
377 + loadReports()
378 + loadCustomers()
379 })
380 </script>
381
382 <style scoped>
383 .vulnerability-reports {
381 - padding: 20px;
384 + padding: 20px;
385 }
386 </style>
frontend/src/composables/useBreadcrumb.ts new
+89
@@ -0,0 +1,89 @@
1 +import { computed, ref } from "vue"
2 +
3 +export interface BreadcrumbItem {
4 + name: string
5 + path: string
6 + key: string
7 +}
8 +
9 +const breadcrumbItems = ref<BreadcrumbItem[]>([])
10 +
11 +export function useBreadcrumb() {
12 + const items = computed(() => breadcrumbItems.value)
13 +
14 + /**
15 + * Adds a new item to the end of the breadcrumb
16 + */
17 + function push(item: BreadcrumbItem) {
18 + breadcrumbItems.value.push(item)
19 + }
20 +
21 + /**
22 + * Replaces the last breadcrumb item
23 + * If there are no items, adds the new item
24 + */
25 + function replace(item: BreadcrumbItem) {
26 + if (breadcrumbItems.value.length === 0) {
27 + breadcrumbItems.value.push(item)
28 + } else {
29 + breadcrumbItems.value[breadcrumbItems.value.length - 1] = item
30 + }
31 + }
32 +
33 + /**
34 + * Updates only the name of the last breadcrumb item
35 + * If there are no items, does nothing
36 + */
37 + function updateLastCrumbName(name: string) {
38 + if (breadcrumbItems.value.length > 0) {
39 + const lastItem = breadcrumbItems.value[breadcrumbItems.value.length - 1]
40 + if (!lastItem) return
41 +
42 + breadcrumbItems.value[breadcrumbItems.value.length - 1] = {
43 + ...lastItem,
44 + name
45 + }
46 + }
47 + }
48 +
49 + /**
50 + * Removes the last breadcrumb item
51 + */
52 + function pop() {
53 + return breadcrumbItems.value.pop()
54 + }
55 +
56 + /**
57 + * Clears all breadcrumb items
58 + */
59 + function clear() {
60 + breadcrumbItems.value = []
61 + }
62 +
63 + /**
64 + * Sets all breadcrumb items
65 + */
66 + function setItems(items: BreadcrumbItem[]) {
67 + breadcrumbItems.value = items
68 + }
69 +
70 + /**
71 + * Removes items until reaching the specified index
72 + */
73 + function truncate(index: number) {
74 + if (index >= 0 && index < breadcrumbItems.value.length) {
75 + breadcrumbItems.value = breadcrumbItems.value.slice(0, index + 1)
76 + }
77 + }
78 +
79 + return {
80 + items,
81 + push,
82 + replace,
83 + updateLastCrumbName,
84 + pop,
85 + clear,
86 + setItems,
87 + truncate
88 + }
89 +}
frontend/src/composables/useGoto.ts deleted
-88
@@ -1,88 +0,0 @@
1 -import { useRouter } from "vue-router"
2 -
3 -export function useGoto() {
4 - const router = useRouter()
5 -
6 - function gotoCustomer(params?: { code?: string | number; action?: "add-customer" }) {
7 - if (params?.code) {
8 - router.push({ name: "Customers", query: { code: params.code.toString() } })
9 - } else if (params?.action) {
10 - router.push({ name: "Customers", query: { action: params.action.toString() } })
11 - } else {
12 - router.push({ name: "Customers" })
13 - }
14 - }
15 -
16 - function gotoAgent(agentId?: string | number) {
17 - if (agentId) {
18 - router.push({ name: "Agent", params: { id: agentId.toString() } })
19 - } else {
20 - router.push({ name: "Agents" })
21 - }
22 - }
23 -
24 - function gotoIndex(indexName?: string) {
25 - router.push({ name: "Indices", query: indexName ? { index_name: indexName } : {} })
26 - }
27 -
28 - function gotoLicense() {
29 - router.push({ name: "License" })
30 - }
31 -
32 - function gotoHealthcheck() {
33 - router.push({ name: "Healthcheck" })
34 - }
35 -
36 - function gotoGraylogMetrics() {
37 - router.push({ name: "Graylog-Metrics" })
38 - }
39 -
40 - function gotoGraylogManagement(tabName?: "messages" | "alerts" | "events" | "streams" | "provisioning" | "inputs") {
41 - router.push({ name: "Graylog-Management", hash: tabName ? `#${tabName}` : undefined })
42 - }
43 -
44 - function gotoSocAlerts() {
45 - router.push({ name: "Soc-Alerts" })
46 - }
47 -
48 - function gotoAlerts() {
49 - router.push({ name: "Alerts" })
50 - }
51 -
52 - function gotoConnectors() {
53 - router.push({ name: "Connectors" })
54 - }
55 -
56 - function gotoGraylogPipelines(rule?: string) {
57 - router.push({ name: "Graylog-Pipelines", query: rule ? { rule } : {} })
58 - }
59 -
60 - function gotoSocUsers(userId?: string | number) {
61 - router.push({ name: "Soc-Users", query: userId ? { user_id: userId } : {} })
62 - }
63 -
64 - function gotoIncidentManagementAlerts(alertId?: number) {
65 - router.push({ name: "IncidentManagement-Alerts", query: alertId ? { alert_id: alertId } : {} })
66 - }
67 -
68 - function gotoIncidentManagementCases(caseId?: number) {
69 - router.push({ name: "IncidentManagement-Cases", query: caseId ? { case_id: caseId } : {} })
70 - }
71 -
72 - return {
73 - gotoCustomer,
74 - gotoAgent,
75 - gotoIndex,
76 - gotoLicense,
77 - gotoHealthcheck,
78 - gotoGraylogMetrics,
79 - gotoGraylogManagement,
80 - gotoSocAlerts,
81 - gotoGraylogPipelines,
82 - gotoSocUsers,
83 - gotoAlerts,
84 - gotoConnectors,
85 - gotoIncidentManagementAlerts,
86 - gotoIncidentManagementCases
87 - }
88 -}
frontend/src/composables/useHealthchecksNotify.ts
+5 -5
@@ -3,13 +3,13 @@ import _capitalize from "lodash/capitalize"
3 import { computed, watch } from "vue"
4 import { useHealthcheckStore } from "@/stores/healthcheck"
5 import { IndexHealth } from "@/types/indices.d"
6 -import { useGoto } from "./useGoto"
6 +import { useNavigation } from "./useNavigation"
7 import { useNotifications } from "./useNotifications"
8
9 export function useHealthchecksNotify() {
10 return {
11 init: () => {
12 - const { gotoHealthcheck, gotoIndex, gotoGraylogMetrics } = useGoto()
12 + const { routeHealthcheck, routeIndex, routeGraylogMetrics } = useNavigation()
13
14 const uncommittedJournalEntriesThreshold = useHealthcheckStore().uncommittedJournalEntriesThreshold
15 const uncommittedJournalEntries = computed(() => useHealthcheckStore().uncommittedJournalEntries)
@@ -30,7 +30,7 @@ export function useHealthchecksNotify() {
30 read: false,
31 date: new Date(),
32 action() {
33 - gotoGraylogMetrics()
33 + routeGraylogMetrics().navigate()
34 },
35 actionTitle: "See Graylog Metrics"
36 }
@@ -56,7 +56,7 @@ export function useHealthchecksNotify() {
56 read: false,
57 date: new Date(),
58 action() {
59 - gotoIndex()
59 + routeIndex().navigate()
60 },
61 actionTitle: "See Cluster"
62 }
@@ -85,7 +85,7 @@ export function useHealthchecksNotify() {
85 read: false,
86 date: new Date(),
87 action() {
88 - gotoHealthcheck()
88 + routeHealthcheck().navigate()
89 },
90 actionTitle: "See Healthcheck"
91 }
frontend/src/composables/useLoadingBarSetup.ts
+5 -1
@@ -9,7 +9,11 @@ export function useLoadingBarSetup() {
9
10 onMounted(() => {
11 const loadingBar = useLoadingBar()
12 - router.beforeEach(() => loadingBar?.start())
12 + router.beforeEach((to, current) => {
13 + if (to.path !== current.path) {
14 + loadingBar?.start()
15 + }
16 + })
17 router.afterEach(() => loadingBar?.finish())
18 mainStore.setLoadingBar(loadingBar)
19 })
frontend/src/composables/useNavigation.ts new
+104
@@ -0,0 +1,104 @@
1 +import type { RouteLocationRaw } from "vue-router"
2 +import { useRouter } from "vue-router"
3 +
4 +export function useNavigation() {
5 + const router = useRouter()
6 +
7 + function routerConstructor(route: RouteLocationRaw) {
8 + return {
9 + navigate: () => router.push(route),
10 + replace: () => router.replace(route),
11 + valueOf: () => router.resolve(route).href,
12 + toString: () => router.resolve(route).href,
13 + fullUrl: () => {
14 + const resolved = router.resolve(route)
15 + return `${window.location.protocol}//${window.location.host}${resolved.href}`
16 + }
17 + }
18 + }
19 +
20 + function routeCustomer(params?: { code?: string | number; action?: "add-customer" }) {
21 + if (params?.code) {
22 + return routerConstructor({ name: "Customers", query: { code: params.code.toString() } })
23 + } else if (params?.action) {
24 + return routerConstructor({ name: "Customers", query: { action: params.action.toString() } })
25 + } else {
26 + return routerConstructor({ name: "Customers" })
27 + }
28 + }
29 +
30 + function routeAgent(agentId?: string | number) {
31 + if (agentId) {
32 + return routerConstructor({ name: "Agent", params: { id: agentId.toString() } })
33 + } else {
34 + return routerConstructor({ name: "Agents" })
35 + }
36 + }
37 +
38 + function routeIndex(indexName?: string) {
39 + return routerConstructor({ name: "Indices", query: indexName ? { index_name: indexName } : {} })
40 + }
41 +
42 + function routeLicense() {
43 + return routerConstructor({ name: "License" })
44 + }
45 +
46 + function routeHealthcheck() {
47 + return routerConstructor({ name: "Healthcheck" })
48 + }
49 +
50 + function routeGraylogMetrics() {
51 + return routerConstructor({ name: "Graylog-Metrics" })
52 + }
53 +
54 + function routeGraylogManagement(
55 + tabName?: "messages" | "alerts" | "events" | "streams" | "provisioning" | "inputs"
56 + ) {
57 + return routerConstructor({ name: "Graylog-Management", hash: tabName ? `#${tabName}` : undefined })
58 + }
59 +
60 + function routeSocAlerts() {
61 + return routerConstructor({ name: "Soc-Alerts" })
62 + }
63 +
64 + function routeAlerts() {
65 + return routerConstructor({ name: "Alerts" })
66 + }
67 +
68 + function routeConnectors() {
69 + return routerConstructor({ name: "Connectors" })
70 + }
71 +
72 + function routeGraylogPipelines(rule?: string) {
73 + return routerConstructor({ name: "Graylog-Pipelines", query: rule ? { rule } : {} })
74 + }
75 +
76 + function routeSocUsers(userId?: string | number) {
77 + return routerConstructor({ name: "Soc-Users", query: userId ? { user_id: userId } : {} })
78 + }
79 +
80 + function routeIncidentManagementAlerts(alertId?: number) {
81 + return routerConstructor({ name: "IncidentManagement-Alerts", query: alertId ? { alert_id: alertId } : {} })
82 + }
83 +
84 + function routeIncidentManagementCases(caseId?: number) {
85 + return routerConstructor({ name: "IncidentManagement-Cases", query: caseId ? { case_id: caseId } : {} })
86 + }
87 +
88 + return {
89 + routeCustomer,
90 + routeAgent,
91 + routeIndex,
92 + routeLicense,
93 + routeHealthcheck,
94 + routeGraylogMetrics,
95 + routeGraylogManagement,
96 + routeSocAlerts,
97 + routeGraylogPipelines,
98 + routeSocUsers,
99 + routeAlerts,
100 + routeConnectors,
101 + routeIncidentManagementAlerts,
102 + routeIncidentManagementCases
103 + }
104 +}
frontend/src/directives/v-shiki.ts
+15 -2
@@ -7,7 +7,11 @@ const vShiki = {
7 el: HTMLElement,
8 binding: { value: { lang?: string; fallbackLang?: string; decode?: boolean } }
9 ) => {
10 - const code = binding?.value?.decode ? decode(el.children[0].innerHTML) : el.children[0].innerHTML
10 + const children = el.children[0]
11 +
12 + if (!children) return
13 +
14 + const code = binding?.value?.decode ? decode(children.innerHTML) : children.innerHTML
15
16 let flouriteDetect = null
17 if (!binding?.value?.lang) {
@@ -18,7 +22,16 @@ const vShiki = {
22 }
23
24 const language = binding?.value?.lang || flouriteDetect || binding?.value?.fallbackLang || "text"
21 - const html = (await getHighlighter()).codeToHtml(code, {
25 +
26 + const formattedCode = (val: string) => {
27 + try {
28 + return JSON.stringify(JSON.parse(val), null, 4)
29 + } catch {
30 + return val
31 + }
32 + }
33 +
34 + const html = (await getHighlighter()).codeToHtml(formattedCode(code), {
35 lang: language,
36 themes: codeThemes
37 })
frontend/src/router/index.ts
+48 -47
@@ -6,6 +6,7 @@ import { authCheck } from "@/utils/auth"
6 import AuthPage from "@/views/Auth.vue"
7 import OverviewPage from "@/views/Overview.vue"
8
9 +// TODO: refactor
10 const router = createRouter({
11 history: createWebHistory(import.meta.env.BASE_URL),
12 routes: [
@@ -20,27 +21,27 @@ const router = createRouter({
21 meta: { title: "Overview", auth: true, roles: RouteRole.All }
22 },
23 {
23 - path: "/indices",
24 - redirect: "/indices/management",
25 - meta: {
26 - auth: true,
27 - roles: RouteRole.All
28 - },
29 - children: [
30 - {
31 - path: "management",
32 - name: "Indices",
33 - component: () => import("@/views/Indices.vue"),
34 - meta: { title: "Index Management" }
35 - },
36 - {
37 - path: "snapshots",
38 - name: "Snapshots",
39 - component: () => import("@/views/Snapshots.vue"),
40 - meta: { title: "Snapshot & Restore" }
41 - }
42 - ]
43 - },
24 + path: "/indices",
25 + redirect: "/indices/management",
26 + meta: {
27 + auth: true,
28 + roles: RouteRole.All
29 + },
30 + children: [
31 + {
32 + path: "management",
33 + name: "Indices",
34 + component: () => import("@/views/Indices.vue"),
35 + meta: { title: "Index Management" }
36 + },
37 + {
38 + path: "snapshots",
39 + name: "Snapshots",
40 + component: () => import("@/views/Snapshots.vue"),
41 + meta: { title: "Snapshot & Restore" }
42 + }
43 + ]
44 + },
45 {
46 path: "/connectors",
47 name: "Connectors",
@@ -103,11 +104,11 @@ const router = createRouter({
104 meta: { title: "SCA Overview" }
105 },
106 {
106 - path: "/patch-tuesday",
107 - name: "PatchTuesday",
108 - component: () => import("@/views/agents/PatchTuesdayOverview.vue"),
109 - meta: { title: "Patch Tuesday" }
110 - }
107 + path: "/patch-tuesday",
108 + name: "PatchTuesday",
109 + component: () => import("@/views/agents/PatchTuesdayOverview.vue"),
110 + meta: { title: "Patch Tuesday" }
111 + }
112 ]
113 },
114 {
@@ -301,33 +302,33 @@ const router = createRouter({
302 // meta: { title: "Report Creation", auth: true, roles: RouteRole.All }
303 // },
304 {
304 - path: "/report-creation",
305 - redirect: "/report-creation/general",
306 - meta: {
307 - auth: true,
308 - roles: RouteRole.All
309 - },
310 - children: [
311 - {
312 - path: "general",
313 - name: "ReportCreation",
314 - component: () => import("@/views/ReportCreation.vue"),
315 - meta: { title: "General Reports" }
316 - },
317 - {
318 - path: "vulnerability-reports",
319 - name: "VulnerabilityReports",
320 - component: () => import("@/components/vulnerabilities/VulnerabilityReports.vue"),
321 - meta: { title: "Vulnerability Reports" }
322 - },
305 + path: "/report-creation",
306 + redirect: "/report-creation/general",
307 + meta: {
308 + auth: true,
309 + roles: RouteRole.All
310 + },
311 + children: [
312 + {
313 + path: "general",
314 + name: "ReportCreation",
315 + component: () => import("@/views/ReportCreation.vue"),
316 + meta: { title: "General Reports" }
317 + },
318 + {
319 + path: "vulnerability-reports",
320 + name: "VulnerabilityReports",
321 + component: () => import("@/components/vulnerabilities/VulnerabilityReports.vue"),
322 + meta: { title: "Vulnerability Reports" }
323 + },
324 {
325 path: "sca-reports",
326 name: "SCAReports",
327 component: () => import("@/components/sca/SCAReports.vue"),
328 meta: { title: "SCA Reports" }
329 }
329 - ]
330 - },
330 + ]
331 + },
332 {
333 path: "/scheduler",
334 name: "Scheduler",
frontend/src/stores/auth.ts
+1 -1
@@ -104,7 +104,7 @@ export const useAuthStore = defineStore("auth", {
104 return state.user?.role
105 },
106 userRoleName(state): string {
107 - return AuthUserRole[(state.user?.role || 0) as number]
107 + return AuthUserRole[(state.user?.role ?? 0) as number] ?? ""
108 },
109 userPic(): string {
110 const initial = getNameInitials(this.userName)
frontend/src/stores/theme.ts
+13 -7
@@ -101,17 +101,23 @@ export const useThemeStore = defineStore("theme", {
101 if (document) {
102 const html = document.children[0] as HTMLElement
103 const body = document.getElementsByTagName("body")?.[0]
104 - if (this.isRTL && body) {
105 - body.classList.add("direction-rtl")
106 - body.classList.remove("direction-ltr")
107 - } else {
108 - body.classList.remove("direction-rtl")
109 - body.classList.add("direction-ltr")
104 + if (body) {
105 + if (this.isRTL) {
106 + body.classList.add("direction-rtl")
107 + body.classList.remove("direction-ltr")
108 + } else {
109 + body.classList.remove("direction-rtl")
110 + body.classList.add("direction-ltr")
111 + }
112 }
113 // html.dir = this.isRTL ? "rtl" : "ltr"
114 const { style: htmlStyle } = html
115 for (const key in this.style) {
114 - htmlStyle.setProperty(`--${key}`, this.style[key])
116 + const value = this.style[key]
117 +
118 + if (!value) continue
119 +
120 + htmlStyle.setProperty(`--${key}`, value)
121 }
122 }
123 },
frontend/src/theme/index.ts
+1 -1
@@ -205,7 +205,7 @@ export function getCssVars(state: ThemeState, getters: ThemeGetters): { [key: st
205 const keys = expandPattern(pattern)
206
207 for (const key of keys) {
208 - styleObject[`${key}-color`] = (state.colors[state.themeName] as Record<string, string>)[key]
208 + styleObject[`${key}-color`] = (state.colors[state.themeName] as Record<string, string>)[key] ?? ""
209 }
210 }
211
frontend/src/types/agents.d.ts
+14 -13
@@ -1,3 +1,4 @@
1 +// TODO: refactor
2 export interface Agent {
3 id?: number
4 agent_id: string
@@ -126,26 +127,26 @@ export interface AgentArtifactData {
127 export type BulkDeleteAgentStatus = "disconnected" | "never_connected" | "active"
128
129 export interface BulkDeleteAgentRequest {
129 - agent_ids: string[]
130 + agent_ids: string[]
131 }
132
133 export interface BulkDeleteFilterRequest {
133 - customer_code?: string
134 - status?: BulkDeleteAgentStatus
135 - disconnected_days?: number
134 + customer_code?: string
135 + status?: BulkDeleteAgentStatus
136 + disconnected_days?: number
137 }
138
139 export interface BulkDeleteAgentResult {
139 - agent_id: string
140 - success: boolean
141 - message: string
140 + agent_id: string
141 + success: boolean
142 + message: string
143 }
144
145 export interface BulkDeleteAgentsResponse {
145 - success: boolean
146 - message: string
147 - total_requested: number
148 - successful_deletions: number
149 - failed_deletions: number
150 - results: BulkDeleteAgentResult[]
146 + success: boolean
147 + message: string
148 + total_requested: number
149 + successful_deletions: number
150 + failed_deletions: number
151 + results: BulkDeleteAgentResult[]
152 }
frontend/src/types/artifacts.d.ts
+7 -6
@@ -1,16 +1,17 @@
1 +// TODO: refactor
2 export interface Artifact {
3 description: string
4 name: string
5 author?: string | null
5 - precondition?: string | null
6 - parameters?: ArtifactParameter[]
6 + precondition?: string | null
7 + parameters?: ArtifactParameter[]
8 }
9
10 export interface ArtifactParameter {
10 - name: string
11 - description?: string
12 - type?: string
13 - default?: string | boolean | null
11 + name: string
12 + description?: string
13 + type?: string
14 + default?: string | boolean | null
15 }
16
17 export interface CollectResult {
frontend/src/types/copilotAction.d.ts
+2 -6
@@ -40,6 +40,8 @@ export interface CopilotAction {
40 tags?: null | string[]
41 }
42
43 +export type ScriptParameterType = "boolean" | "integer" | "string"
44 +
45 export interface ScriptParameter {
46 name: string
47 type: ScriptParameterType
@@ -50,12 +52,6 @@ export interface ScriptParameter {
52 arg_position?: string
53 }
54
53 -export enum ScriptParameterType {
54 - Boolean = "boolean",
55 - Integer = "integer",
56 - String = "string"
57 -}
58 -
55 export interface CopilotActionInvokeResponse {
56 session_id?: string
57 flow_id?: string
frontend/src/types/githubAudit.d.ts
+236 -236
@@ -1,333 +1,333 @@
1 export enum AuditStatus {
2 - PASS = "pass",
3 - FAIL = "fail",
4 - WARNING = "warning",
5 - NOT_APPLICABLE = "not_applicable"
2 + PASS = "pass",
3 + FAIL = "fail",
4 + WARNING = "warning",
5 + NOT_APPLICABLE = "not_applicable"
6 }
7
8 export enum SeverityLevel {
9 - CRITICAL = "critical",
10 - HIGH = "high",
11 - MEDIUM = "medium",
12 - LOW = "low",
13 - INFO = "info"
9 + CRITICAL = "critical",
10 + HIGH = "high",
11 + MEDIUM = "medium",
12 + LOW = "low",
13 + INFO = "info"
14 }
15
16 // ==================== Request Types ====================
17
18 export interface GitHubAuditRequest {
19 - organization: string
20 - include_repos?: boolean
21 - include_workflows?: boolean
22 - include_members?: boolean
23 - repo_filter?: string[]
19 + organization: string
20 + include_repos?: boolean
21 + include_workflows?: boolean
22 + include_members?: boolean
23 + repo_filter?: string[]
24 }
25
26 export interface GitHubAuditConfigCreate {
27 - customer_code: string
28 - github_token: string
29 - organization: string
30 - token_type?: string
31 - token_expires_at?: string | null
32 - enabled?: boolean
33 - auto_audit_enabled?: boolean
34 - audit_schedule_cron?: string | null
35 - include_repos?: boolean
36 - include_workflows?: boolean
37 - include_members?: boolean
38 - include_archived_repos?: boolean
39 - repo_filter_mode?: string
40 - repo_filter_list?: string[] | null
41 - notify_on_critical?: boolean
42 - notify_on_high?: boolean
43 - notification_webhook_url?: string | null
44 - notification_email?: string | null
45 - minimum_passing_score?: number
46 - created_by?: string | null
27 + customer_code: string
28 + github_token: string
29 + organization: string
30 + token_type?: string
31 + token_expires_at?: string | null
32 + enabled?: boolean
33 + auto_audit_enabled?: boolean
34 + audit_schedule_cron?: string | null
35 + include_repos?: boolean
36 + include_workflows?: boolean
37 + include_members?: boolean
38 + include_archived_repos?: boolean
39 + repo_filter_mode?: string
40 + repo_filter_list?: string[]
41 + notify_on_critical?: boolean
42 + notify_on_high?: boolean
43 + notification_webhook_url?: string | null
44 + notification_email?: string | null
45 + minimum_passing_score?: number
46 + created_by?: string | null
47 }
48
49 export interface GitHubAuditConfigUpdate {
50 - github_token?: string | null
51 - organization?: string | null
52 - token_type?: string | null
53 - token_expires_at?: string | null
54 - enabled?: boolean | null
55 - auto_audit_enabled?: boolean | null
56 - audit_schedule_cron?: string | null
57 - include_repos?: boolean | null
58 - include_workflows?: boolean | null
59 - include_members?: boolean | null
60 - include_archived_repos?: boolean | null
61 - repo_filter_mode?: string | null
62 - repo_filter_list?: string[] | null
63 - notify_on_critical?: boolean | null
64 - notify_on_high?: boolean | null
65 - notification_webhook_url?: string | null
66 - notification_email?: string | null
67 - minimum_passing_score?: number | null
68 - updated_by?: string | null
50 + github_token?: string | null
51 + organization?: string | null
52 + token_type?: string | null
53 + token_expires_at?: string | null
54 + enabled?: boolean | null
55 + auto_audit_enabled?: boolean | null
56 + audit_schedule_cron?: string | null
57 + include_repos?: boolean | null
58 + include_workflows?: boolean | null
59 + include_members?: boolean | null
60 + include_archived_repos?: boolean | null
61 + repo_filter_mode?: string | null
62 + repo_filter_list?: string[] | null
63 + notify_on_critical?: boolean | null
64 + notify_on_high?: boolean | null
65 + notification_webhook_url?: string | null
66 + notification_email?: string | null
67 + minimum_passing_score?: number | null
68 + updated_by?: string | null
69 }
70
71 export interface GitHubAuditExclusionCreate {
72 - check_id: string
73 - resource_name?: string | null
74 - resource_type?: string | null
75 - reason: string
76 - approved_by?: string | null
77 - expires_at?: string | null
78 - created_by: string
72 + check_id: string
73 + resource_name?: string | null
74 + resource_type?: string | null
75 + reason: string
76 + approved_by?: string | null
77 + expires_at?: string | null
78 + created_by: string
79 }
80
81 export interface GitHubAuditExclusionUpdate {
82 - reason?: string | null
83 - approved_by?: string | null
84 - expires_at?: string | null
85 - enabled?: boolean | null
82 + reason?: string | null
83 + approved_by?: string | null
84 + expires_at?: string | null
85 + enabled?: boolean | null
86 }
87
88 export interface GitHubAuditBaselineCreate {
89 - name: string
90 - description?: string | null
91 - expected_checks?: Record<string, string> | null
92 - baseline_report_id?: number | null
93 - is_active?: boolean
94 - created_by: string
89 + name: string
90 + description?: string | null
91 + expected_checks?: Record<string, string> | null
92 + baseline_report_id?: number | null
93 + is_active?: boolean
94 + created_by: string
95 }
96
97 export interface GitHubAuditBaselineUpdate {
98 - name?: string | null
99 - description?: string | null
100 - is_active?: boolean | null
98 + name?: string | null
99 + description?: string | null
100 + is_active?: boolean | null
101 }
102
103 // ==================== Model Types ====================
104
105 export interface GitHubAuditConfig {
106 - id: number
107 - customer_code: string
108 - github_token: string
109 - organization: string
110 - token_type: string
111 - token_expires_at: string | null
112 - enabled: boolean
113 - auto_audit_enabled: boolean
114 - audit_schedule_cron: string | null
115 - include_repos: boolean
116 - include_workflows: boolean
117 - include_members: boolean
118 - include_archived_repos: boolean
119 - repo_filter_mode: string
120 - repo_filter_list: string[] | null
121 - notify_on_critical: boolean
122 - notify_on_high: boolean
123 - notification_webhook_url: string | null
124 - notification_email: string | null
125 - minimum_passing_score: number
126 - created_at: string
127 - updated_at: string
128 - created_by: string | null
129 - updated_by: string | null
130 - last_audit_at: string | null
131 - last_audit_score: number | null
132 - last_audit_grade: string | null
106 + id: number
107 + customer_code: string
108 + github_token: string
109 + organization: string
110 + token_type: string
111 + token_expires_at: string | null
112 + enabled: boolean
113 + auto_audit_enabled: boolean
114 + audit_schedule_cron: string | null
115 + include_repos: boolean
116 + include_workflows: boolean
117 + include_members: boolean
118 + include_archived_repos: boolean
119 + repo_filter_mode: string
120 + repo_filter_list: string[] | null
121 + notify_on_critical: boolean
122 + notify_on_high: boolean
123 + notification_webhook_url: string | null
124 + notification_email: string | null
125 + minimum_passing_score: number
126 + created_at: string
127 + updated_at: string
128 + created_by: string | null
129 + updated_by: string | null
130 + last_audit_at: string | null
131 + last_audit_score: number | null
132 + last_audit_grade: string | null
133 }
134
135 export interface GitHubAuditReportSummary {
136 - id: number
137 - config_id: number
138 - customer_code: string
139 - report_name: string
140 - organization: string
141 - audit_started_at: string
142 - audit_completed_at: string | null
143 - audit_duration_seconds: number | null
144 - total_repos_audited: number
145 - total_checks: number
146 - passed_checks: number
147 - failed_checks: number
148 - warning_checks: number
149 - critical_findings: number
150 - high_findings: number
151 - medium_findings: number
152 - low_findings: number
153 - score: number
154 - grade: string
155 - status: string
156 - triggered_by: string
157 - triggered_by_user: string | null
136 + id: number
137 + config_id: number
138 + customer_code: string
139 + report_name: string
140 + organization: string
141 + audit_started_at: string
142 + audit_completed_at: string | null
143 + audit_duration_seconds: number | null
144 + total_repos_audited: number
145 + total_checks: number
146 + passed_checks: number
147 + failed_checks: number
148 + warning_checks: number
149 + critical_findings: number
150 + high_findings: number
151 + medium_findings: number
152 + low_findings: number
153 + score: number
154 + grade: string
155 + status: string
156 + triggered_by: string
157 + triggered_by_user: string | null
158 }
159
160 export interface GitHubAuditReport extends GitHubAuditReportSummary {
161 - full_report: GitHubAuditResponse | null
162 - top_findings: AuditCheck[] | null
163 - error_message: string | null
161 + full_report: GitHubAuditResponse | null
162 + top_findings: AuditCheck[] | null
163 + error_message: string | null
164 }
165
166 export interface GitHubAuditCheckExclusion {
167 - id: number
168 - config_id: number
169 - customer_code: string
170 - check_id: string
171 - resource_name: string | null
172 - resource_type: string | null
173 - reason: string
174 - approved_by: string | null
175 - approved_at: string | null
176 - expires_at: string | null
177 - enabled: boolean
178 - created_at: string
179 - created_by: string
167 + id: number
168 + config_id: number
169 + customer_code: string
170 + check_id: string
171 + resource_name: string | null
172 + resource_type: string | null
173 + reason: string
174 + approved_by: string | null
175 + approved_at: string | null
176 + expires_at: string | null
177 + enabled: boolean
178 + created_at: string
179 + created_by: string
180 }
181
182 export interface GitHubAuditBaseline {
183 - id: number
184 - config_id: number
185 - customer_code: string
186 - name: string
187 - description: string | null
188 - expected_checks: Record<string, string> | null
189 - baseline_report_id: number | null
190 - is_active: boolean
191 - created_at: string
192 - created_by: string
183 + id: number
184 + config_id: number
185 + customer_code: string
186 + name: string
187 + description: string | null
188 + expected_checks: Record<string, string> | null
189 + baseline_report_id: number | null
190 + is_active: boolean
191 + created_at: string
192 + created_by: string
193 }
194
195 export interface AvailableCheck {
196 - id: string
197 - name: string
198 - category: string
199 - severity: string
200 - description: string
196 + id: string
197 + name: string
198 + category: string
199 + severity: string
200 + description: string
201 }
202
203 // ==================== Audit Result Types ====================
204
205 export interface AuditCheck {
206 - check_id: string
207 - check_name: string
208 - category: string
209 - status: AuditStatus
210 - severity: SeverityLevel
211 - description: string
212 - recommendation?: string | null
213 - details?: Record<string, unknown> | null
214 - resource_name?: string | null
215 - resource_type?: string | null
206 + check_id: string
207 + check_name: string
208 + category: string
209 + status: AuditStatus
210 + severity: SeverityLevel
211 + description: string
212 + recommendation?: string | null
213 + details?: Record<string, unknown> | null
214 + resource_name?: string | null
215 + resource_type?: string | null
216 }
217
218 export interface RepositoryAuditResult {
219 - repo_name: string
220 - repo_full_name: string
221 - repo_url: string
222 - is_private: boolean
223 - is_archived: boolean
224 - default_branch: string
225 - checks: AuditCheck[]
226 - passed_count: number
227 - failed_count: number
228 - warning_count: number
219 + repo_name: string
220 + repo_full_name: string
221 + repo_url: string
222 + is_private: boolean
223 + is_archived: boolean
224 + default_branch: string
225 + checks: AuditCheck[]
226 + passed_count: number
227 + failed_count: number
228 + warning_count: number
229 }
230
231 export interface OrganizationAuditResult {
232 - org_name: string
233 - org_url: string
234 - checks: AuditCheck[]
235 - passed_count: number
236 - failed_count: number
237 - warning_count: number
232 + org_name: string
233 + org_url: string
234 + checks: AuditCheck[]
235 + passed_count: number
236 + failed_count: number
237 + warning_count: number
238 }
239
240 export interface WorkflowAuditResult {
241 - repo_name: string
242 - workflow_name: string
243 - workflow_path: string
244 - checks: AuditCheck[]
241 + repo_name: string
242 + workflow_name: string
243 + workflow_path: string
244 + checks: AuditCheck[]
245 }
246
247 export interface MemberAuditResult {
248 - username: string
249 - role: string
250 - has_2fa?: boolean | null
251 - checks: AuditCheck[]
248 + username: string
249 + role: string
250 + has_2fa?: boolean | null
251 + checks: AuditCheck[]
252 }
253
254 export interface AuditSummary {
255 - organization: string
256 - audit_timestamp: string
257 - total_repos_audited: number
258 - total_checks: number
259 - passed_checks: number
260 - failed_checks: number
261 - warning_checks: number
262 - critical_findings: number
263 - high_findings: number
264 - medium_findings: number
265 - low_findings: number
266 - score: number
267 - grade: string
255 + organization: string
256 + audit_timestamp: string
257 + total_repos_audited: number
258 + total_checks: number
259 + passed_checks: number
260 + failed_checks: number
261 + warning_checks: number
262 + critical_findings: number
263 + high_findings: number
264 + medium_findings: number
265 + low_findings: number
266 + score: number
267 + grade: string
268 }
269
270 // ==================== Response Types ====================
271
272 export interface GitHubAuditResponse {
273 - success: boolean
274 - message: string
275 - summary: AuditSummary | null
276 - organization_results: OrganizationAuditResult | null
277 - repository_results: RepositoryAuditResult[]
278 - workflow_results: WorkflowAuditResult[]
279 - member_results: MemberAuditResult[]
280 - top_findings: AuditCheck[]
273 + success: boolean
274 + message: string
275 + summary: AuditSummary | null
276 + organization_results: OrganizationAuditResult | null
277 + repository_results: RepositoryAuditResult[]
278 + workflow_results: WorkflowAuditResult[]
279 + member_results: MemberAuditResult[]
280 + top_findings: AuditCheck[]
281 }
282
283 export interface GitHubAuditSummaryResponse {
284 - success: boolean
285 - message: string
286 - summary: AuditSummary | null
287 - top_findings: AuditCheck[]
284 + success: boolean
285 + message: string
286 + summary: AuditSummary | null
287 + top_findings: AuditCheck[]
288 }
289
290 export interface GitHubAuditConfigResponse {
291 - success: boolean
292 - message: string
293 - config?: GitHubAuditConfig | null
294 - configs?: GitHubAuditConfig[] | null
291 + success: boolean
292 + message: string
293 + config?: GitHubAuditConfig | null
294 + configs?: GitHubAuditConfig[] | null
295 }
296
297 export interface GitHubAuditReportListResponse {
298 - success: boolean
299 - message: string
300 - reports: GitHubAuditReportSummary[]
301 - total_count: number
298 + success: boolean
299 + message: string
300 + reports: GitHubAuditReportSummary[]
301 + total_count: number
302 }
303
304 export interface GitHubAuditReportResponse {
305 - success: boolean
306 - message: string
307 - report: GitHubAuditReport | null
305 + success: boolean
306 + message: string
307 + report: GitHubAuditReport | null
308 }
309
310 export interface GitHubAuditExclusionResponse {
311 - success: boolean
312 - message: string
313 - exclusion?: GitHubAuditCheckExclusion | null
314 - exclusions?: GitHubAuditCheckExclusion[] | null
311 + success: boolean
312 + message: string
313 + exclusion?: GitHubAuditCheckExclusion | null
314 + exclusions?: GitHubAuditCheckExclusion[] | null
315 }
316
317 export interface GitHubAuditBaselineResponse {
318 - success: boolean
319 - message: string
320 - baseline?: GitHubAuditBaseline | null
321 - baselines?: GitHubAuditBaseline[] | null
318 + success: boolean
319 + message: string
320 + baseline?: GitHubAuditBaseline | null
321 + baselines?: GitHubAuditBaseline[] | null
322 }
323
324 export interface AvailableChecksResponse {
325 - success: boolean
326 - message: string
327 - checks: AvailableCheck[]
325 + success: boolean
326 + message: string
327 + checks: AvailableCheck[]
328 }
329
330 export interface DeleteResponse {
331 - success: boolean
332 - message: string
331 + success: boolean
332 + message: string
333 }
frontend/src/types/patchTuesday.d.ts
+90 -89
@@ -1,148 +1,149 @@
1 +// TODO: refactor
2 export interface CVSSInfo {
2 - base: number | null
3 - vector: string | null
3 + base: number | null
4 + vector: string | null
5 }
6
7 export interface EPSSInfo {
7 - score: number | null
8 - percentile: number | null
9 - date: string | null
8 + score: number | null
9 + percentile: number | null
10 + date: string | null
11 }
12
13 export interface KEVInfo {
13 - in_kev: boolean
14 - date_added: string | null
15 - due_date: string | null
16 - required_action: string | null
17 - known_ransomware_campaign_use: string | null
18 - vendor_project: string | null
19 - product: string | null
20 - vulnerability_name: string | null
21 - short_description: string | null
22 - notes: string | null
14 + in_kev: boolean
15 + date_added: string | null
16 + due_date: string | null
17 + required_action: string | null
18 + known_ransomware_campaign_use: string | null
19 + vendor_project: string | null
20 + product: string | null
21 + vulnerability_name: string | null
22 + short_description: string | null
23 + notes: string | null
24 }
25
26 export interface AffectedProduct {
26 - product: string
27 - family: ProductFamily
28 - component_hint: string | null
27 + product: string
28 + family: ProductFamily
29 + component_hint: string | null
30 }
31
32 export interface RemediationInfo {
32 - kbs: string[]
33 + kbs: string[]
34 }
35
36 export interface PrioritizationInfo {
36 - priority: PriorityLevel
37 - reason: string[]
38 - suggested_sla: string
37 + priority: PriorityLevel
38 + reason: string[]
39 + suggested_sla: string
40 }
41
42 export interface SourceInfo {
42 - msrc_cvrf_id: string
43 - msrc_cvrf_url: string
44 - cisa_kev_url: string
43 + msrc_cvrf_id: string
44 + msrc_cvrf_url: string
45 + cisa_kev_url: string
46 }
47
48 export interface PatchTuesdayItem {
48 - cycle: string
49 - release_type: string
50 - cve: string
51 - title: string | null
52 - severity: string | null
53 - cvss: CVSSInfo
54 - epss: EPSSInfo
55 - kev: KEVInfo
56 - affected: AffectedProduct
57 - remediation: RemediationInfo
58 - prioritization: PrioritizationInfo
59 - source: SourceInfo
60 - timestamp_utc: string
49 + cycle: string
50 + release_type: string
51 + cve: string
52 + title: string | null
53 + severity: string | null
54 + cvss: CVSSInfo
55 + epss: EPSSInfo
56 + kev: KEVInfo
57 + affected: AffectedProduct
58 + remediation: RemediationInfo
59 + prioritization: PrioritizationInfo
60 + source: SourceInfo
61 + timestamp_utc: string
62 }
63
64 export interface PriorityCounts {
64 - P0: number
65 - P1: number
66 - P2: number
67 - P3: number
65 + P0: number
66 + P1: number
67 + P2: number
68 + P3: number
69 }
70
71 export interface PatchTuesdaySummary {
71 - cycle: string
72 - patch_tuesday_date: string
73 - generated_utc: string
74 - unique_cves: number
75 - total_records: number
76 - by_priority: PriorityCounts
77 - by_family: Record<string, number>
78 - by_severity: Record<string, number>
72 + cycle: string
73 + patch_tuesday_date: string
74 + generated_utc: string
75 + unique_cves: number
76 + total_records: number
77 + by_priority: PriorityCounts
78 + by_family: Record<string, number>
79 + by_severity: Record<string, number>
80 }
81
82 export interface PatchTuesdayResponse {
82 - success: boolean
83 - message: string
84 - summary: PatchTuesdaySummary | null
85 - items: PatchTuesdayItem[]
83 + success: boolean
84 + message: string
85 + summary: PatchTuesdaySummary | null
86 + items: PatchTuesdayItem[]
87 }
88
89 export interface PatchTuesdaySummaryResponse {
89 - success: boolean
90 - message: string
91 - summary: PatchTuesdaySummary | null
92 - top_items: PatchTuesdayItem[]
90 + success: boolean
91 + message: string
92 + summary: PatchTuesdaySummary | null
93 + top_items: PatchTuesdayItem[]
94 }
95
96 export interface AvailableCyclesResponse {
96 - success: boolean
97 - message: string
98 - cycles: string[]
99 - current_cycle: string
100 - next_patch_tuesday: string
97 + success: boolean
98 + message: string
99 + cycles: string[]
100 + current_cycle: string
101 + next_patch_tuesday: string
102 }
103
104 export interface PatchTuesdayQuery {
104 - cycle?: string
105 - include_epss?: boolean
106 - include_kev?: boolean
105 + cycle?: string
106 + include_epss?: boolean
107 + include_kev?: boolean
108 }
109
110 export interface PatchTuesdaySummaryQuery extends PatchTuesdayQuery {
110 - top_n?: number
111 + top_n?: number
112 }
113
114 export interface PatchTuesdaySearchQuery {
114 - cve_ids: string[]
115 - cycle?: string
115 + cve_ids: string[]
116 + cycle?: string
117 }
118
119 export interface PatchTuesdayPriorityQuery extends PatchTuesdayQuery {
119 - priority_level: PriorityLevel
120 + priority_level: PriorityLevel
121 }
122
123 export enum PriorityLevel {
123 - P0 = "P0",
124 - P1 = "P1",
125 - P2 = "P2",
126 - P3 = "P3"
124 + P0 = "P0",
125 + P1 = "P1",
126 + P2 = "P2",
127 + P3 = "P3"
128 }
129
130 export enum ProductFamily {
130 - Windows = "Windows",
131 - WindowsServer = "Windows Server",
132 - OfficeM365 = "Office/M365",
133 - Exchange = "Exchange",
134 - SharePoint = "SharePoint",
135 - SQLServer = "SQL Server",
136 - DeveloperPlatform = "Developer Platform",
137 - Edge = "Edge",
138 - Azure = "Azure",
139 - Dynamics = "Dynamics",
140 - Other = "Other"
131 + Windows = "Windows",
132 + WindowsServer = "Windows Server",
133 + OfficeM365 = "Office/M365",
134 + Exchange = "Exchange",
135 + SharePoint = "SharePoint",
136 + SQLServer = "SQL Server",
137 + DeveloperPlatform = "Developer Platform",
138 + Edge = "Edge",
139 + Azure = "Azure",
140 + Dynamics = "Dynamics",
141 + Other = "Other"
142 }
143
144 export enum MicrosoftSeverity {
144 - Critical = "Critical",
145 - Important = "Important",
146 - Moderate = "Moderate",
147 - Low = "Low"
145 + Critical = "Critical",
146 + Important = "Important",
147 + Moderate = "Moderate",
148 + Low = "Low"
149 }
frontend/src/types/sca.d.ts
+62 -61
@@ -1,3 +1,4 @@
1 +// TODO: refactor
2 export interface AgentScaOverviewItem {
3 agent_id: string
4 agent_name: string
@@ -72,50 +73,50 @@ export interface ScaStatsResponse {
73 }
74
75 export interface SCAReport {
75 - id: number
76 - report_name: string
77 - customer_code: string
78 - file_name: string
79 - file_size: number
80 - generated_at: string
81 - generated_by: number
82 - total_policies: number
83 - total_checks: number
84 - passed_count: number
85 - failed_count: number
86 - invalid_count: number
87 - filters_applied: Record<string, string | number | boolean>
88 - status: "processing" | "completed" | "failed"
89 - error_message?: string | null
90 - download_url: string
76 + id: number
77 + report_name: string
78 + customer_code: string
79 + file_name: string
80 + file_size: number
81 + generated_at: string
82 + generated_by: number
83 + total_policies: number
84 + total_checks: number
85 + passed_count: number
86 + failed_count: number
87 + invalid_count: number
88 + filters_applied: Record<string, string | number | boolean>
89 + status: "processing" | "completed" | "failed"
90 + error_message?: string | null
91 + download_url: string
92 }
93
94 export interface SCAReportGenerateRequest {
94 - customer_code: string
95 - report_name?: string
96 - agent_name?: string
97 - policy_id?: string
98 - min_score?: number
99 - max_score?: number
95 + customer_code: string
96 + report_name?: string
97 + agent_name?: string
98 + policy_id?: string
99 + min_score?: number
100 + max_score?: number
101 }
102
103 export interface SCAReportGenerateResponse {
103 - success: boolean
104 - message: string
105 - report?: SCAReport
106 - error?: string
104 + success: boolean
105 + message: string
106 + report?: SCAReport
107 + error?: string
108 }
109
110 export interface SCAReportListResponse {
110 - success: boolean
111 - message: string
112 - reports: SCAReport[]
113 - total_count: number
111 + success: boolean
112 + message: string
113 + reports: SCAReport[]
114 + total_count: number
115 }
116
117 export interface SCAReportDeleteResponse {
117 - success: boolean
118 - message: string
118 + success: boolean
119 + message: string
120 }
121
122 export enum ScaComplianceLevel {
@@ -128,45 +129,45 @@ export enum ScaComplianceLevel {
129
130 // Streaming event types
131 export interface ScaStreamStartEvent {
131 - total_agents: number
132 - message: string
132 + total_agents: number
133 + message: string
134 }
135
136 export interface ScaStreamAgentResult {
136 - agent_id: string
137 - agent_name: string
138 - customer_code: string | null
139 - policy_count: number
140 - policies: AgentScaOverviewItem[]
137 + agent_id: string
138 + agent_name: string
139 + customer_code: string | null
140 + policy_count: number
141 + policies: AgentScaOverviewItem[]
142 }
143
144 export interface ScaStreamProgress {
144 - processed: number
145 - total: number
146 - successful: number
147 - failed: number
148 - results_so_far: number
149 - percent_complete: number
145 + processed: number
146 + total: number
147 + successful: number
148 + failed: number
149 + results_so_far: number
150 + percent_complete: number
151 }
152
153 export interface ScaStreamComplete {
153 - total_results: number
154 - total_agents: number
155 - total_policies: number
156 - average_score: number
157 - total_checks: number
158 - total_passes: number
159 - total_fails: number
160 - total_invalid: number
161 - agents_processed: number
162 - agents_successful: number
163 - agents_failed: number
164 - message: string
154 + total_results: number
155 + total_agents: number
156 + total_policies: number
157 + average_score: number
158 + total_checks: number
159 + total_passes: number
160 + total_fails: number
161 + total_invalid: number
162 + agents_processed: number
163 + agents_successful: number
164 + agents_failed: number
165 + message: string
166 }
167
168 export interface ScaStreamError {
168 - error?: string
169 - message: string
170 - agent_id?: string
171 - agent_name?: string
169 + error?: string
170 + message: string
171 + agent_id?: string
172 + agent_name?: string
173 }
frontend/src/types/snapshots.d.ts
+126 -124
@@ -1,181 +1,183 @@
1 +import type { SafeAny } from "./common.d"
2 +
3 export interface SnapshotRepository {
2 - name: string
3 - type: string
4 - settings: Record<string, any>
4 + name: string
5 + type: string
6 + settings: Record<string, SafeAny>
7 }
8
9 export interface SnapshotRepositoryListResponse {
8 - repositories: SnapshotRepository[]
9 - success: boolean
10 - message: string
10 + repositories: SnapshotRepository[]
11 + success: boolean
12 + message: string
13 }
14
15 export interface SnapshotShardStatus {
14 - stage: string
15 - total_files?: number
16 - total_size_in_bytes?: number
17 - processed_files?: number
18 - processed_size_in_bytes?: number
16 + stage: string
17 + total_files?: number
18 + total_size_in_bytes?: number
19 + processed_files?: number
20 + processed_size_in_bytes?: number
21 }
22
23 export interface SnapshotIndexStatus {
22 - shards_stats: Record<string, any>
23 - stats: Record<string, any>
24 - shards: Record<string, SnapshotShardStatus>
24 + shards_stats: Record<string, any>
25 + stats: Record<string, any>
26 + shards: Record<string, SnapshotShardStatus>
27 }
28
29 export interface SnapshotStatus {
28 - snapshot: string
29 - repository: string
30 - uuid?: string
31 - state: string
32 - include_global_state?: boolean
33 - shards_stats: Record<string, any>
34 - stats: Record<string, any>
35 - indices: Record<string, SnapshotIndexStatus>
30 + snapshot: string
31 + repository: string
32 + uuid?: string
33 + state: string
34 + include_global_state?: boolean
35 + shards_stats: Record<string, any>
36 + stats: Record<string, any>
37 + indices: Record<string, SnapshotIndexStatus>
38 }
39
40 export interface SnapshotStatusResponse {
39 - snapshots: SnapshotStatus[]
40 - success: boolean
41 - message: string
41 + snapshots: SnapshotStatus[]
42 + success: boolean
43 + message: string
44 }
45
46 export interface SnapshotInfo {
45 - snapshot: string
46 - uuid?: string
47 - version_id?: number
48 - version?: string
49 - indices: string[]
50 - include_global_state?: boolean
51 - state: string
52 - start_time?: string
53 - start_time_in_millis?: number
54 - end_time?: string
55 - end_time_in_millis?: number
56 - duration_in_millis?: number
57 - failures: Record<string, any>[]
58 - shards: Record<string, any>
47 + snapshot: string
48 + uuid?: string
49 + version_id?: number
50 + version?: string
51 + indices: string[]
52 + include_global_state?: boolean
53 + state: string
54 + start_time?: string
55 + start_time_in_millis?: number
56 + end_time?: string
57 + end_time_in_millis?: number
58 + duration_in_millis?: number
59 + failures: Record<string, any>[]
60 + shards: Record<string, any>
61 }
62
63 export interface SnapshotListResponse {
62 - repository: string
63 - snapshots: SnapshotInfo[]
64 - success: boolean
65 - message: string
64 + repository: string
65 + snapshots: SnapshotInfo[]
66 + success: boolean
67 + message: string
68 }
69
70 export interface RestoreSnapshotRequest {
69 - repository: string
70 - snapshot: string
71 - indices?: string[]
72 - ignore_unavailable?: boolean
73 - include_global_state?: boolean
74 - rename_pattern?: string
75 - rename_replacement?: string
76 - include_aliases?: boolean
77 - partial?: boolean
71 + repository: string
72 + snapshot: string
73 + indices?: string[]
74 + ignore_unavailable?: boolean
75 + include_global_state?: boolean
76 + rename_pattern?: string
77 + rename_replacement?: string
78 + include_aliases?: boolean
79 + partial?: boolean
80 }
81
82 export interface RestoreShardInfo {
81 - total: number
82 - failed: number
83 - successful: number
83 + total: number
84 + failed: number
85 + successful: number
86 }
87
88 export interface RestoreSnapshotResponse {
87 - snapshot: string
88 - repository: string
89 - indices: string[]
90 - shards: RestoreShardInfo
91 - success: boolean
92 - message: string
89 + snapshot: string
90 + repository: string
91 + indices: string[]
92 + shards: RestoreShardInfo
93 + success: boolean
94 + message: string
95 }
96
97 export interface CreateSnapshotRequest {
96 - repository: string
97 - snapshot: string
98 - indices?: string[]
99 - ignore_unavailable?: boolean
100 - include_global_state?: boolean
101 - partial?: boolean
102 - wait_for_completion?: boolean
103 - metadata?: Record<string, any>
104 - skip_write_indices?: boolean
98 + repository: string
99 + snapshot: string
100 + indices?: string[]
101 + ignore_unavailable?: boolean
102 + include_global_state?: boolean
103 + partial?: boolean
104 + wait_for_completion?: boolean
105 + metadata?: Record<string, any>
106 + skip_write_indices?: boolean
107 }
108
109 export interface CreateSnapshotResponse {
108 - snapshot: string
109 - repository: string
110 - uuid?: string
111 - state?: string
112 - indices: string[]
113 - skipped_write_indices: string[]
114 - shards?: RestoreShardInfo
115 - accepted: boolean
116 - success: boolean
117 - message: string
110 + snapshot: string
111 + repository: string
112 + uuid?: string
113 + state?: string
114 + indices: string[]
115 + skipped_write_indices: string[]
116 + shards?: RestoreShardInfo
117 + accepted: boolean
118 + success: boolean
119 + message: string
120 }
121
122 // Snapshot Schedule Types
123 export interface SnapshotScheduleCreate {
122 - name: string
123 - index_pattern: string
124 - repository: string
125 - enabled?: boolean
126 - snapshot_prefix?: string
127 - include_global_state?: boolean
128 - skip_write_indices?: boolean
129 - retention_days?: number | null
124 + name: string
125 + index_pattern: string
126 + repository: string
127 + enabled?: boolean
128 + snapshot_prefix?: string
129 + include_global_state?: boolean
130 + skip_write_indices?: boolean
131 + retention_days?: number | null
132 }
133
134 export interface SnapshotScheduleUpdate {
133 - name?: string
134 - index_pattern?: string
135 - repository?: string
136 - enabled?: boolean
137 - snapshot_prefix?: string
138 - include_global_state?: boolean
139 - skip_write_indices?: boolean
140 - retention_days?: number | null
135 + name?: string
136 + index_pattern?: string
137 + repository?: string
138 + enabled?: boolean
139 + snapshot_prefix?: string
140 + include_global_state?: boolean
141 + skip_write_indices?: boolean
142 + retention_days?: number | null
143 }
144
145 export interface SnapshotScheduleResponse {
144 - id: number
145 - name: string
146 - index_pattern: string
147 - repository: string
148 - enabled: boolean
149 - snapshot_prefix: string
150 - include_global_state: boolean
151 - skip_write_indices: boolean
152 - retention_days?: number | null
153 - last_execution_time?: string | null
154 - last_snapshot_name?: string | null
155 - last_execution_status?: string | null
156 - created_at: string
157 - updated_at: string
146 + id: number
147 + name: string
148 + index_pattern: string
149 + repository: string
150 + enabled: boolean
151 + snapshot_prefix: string
152 + include_global_state: boolean
153 + skip_write_indices: boolean
154 + retention_days?: number | null
155 + last_execution_time?: string | null
156 + last_snapshot_name?: string | null
157 + last_execution_status?: string | null
158 + created_at: string
159 + updated_at: string
160 }
161
162 export interface SnapshotScheduleListResponse {
161 - schedules: SnapshotScheduleResponse[]
162 - success: boolean
163 - message: string
163 + schedules: SnapshotScheduleResponse[]
164 + success: boolean
165 + message: string
166 }
167
168 export interface SnapshotScheduleOperationResponse {
167 - schedule?: SnapshotScheduleResponse | null
168 - success: boolean
169 - message: string
169 + schedule?: SnapshotScheduleResponse | null
170 + success: boolean
171 + message: string
172 }
173
174 export interface ScheduledSnapshotExecutionResponse {
173 - schedule_id: number
174 - schedule_name: string
175 - snapshot_name?: string | null
176 - indices_snapshotted: string[]
177 - skipped_write_indices: string[]
178 - already_snapshotted_indices: string[]
179 - success: boolean
180 - message: string
175 + schedule_id: number
176 + schedule_name: string
177 + snapshot_name?: string | null
178 + indices_snapshotted: string[]
179 + skipped_write_indices: string[]
180 + already_snapshotted_indices: string[]
181 + success: boolean
182 + message: string
183 }
frontend/src/types/tags.d.ts
+20 -30
@@ -1,40 +1,30 @@
1 export interface AlertTag {
2 - id: number
3 - tag: string
2 + id: number
3 + tag: string
4 }
5
6 +export type UntaggedAlertBehavior = "visible_to_all" | "admin_only" | "default_tag"
7 +
8 export interface TagAccessSettings {
7 - enabled: boolean
8 - untagged_alert_behavior: "visible_to_all" | "admin_only" | "default_tag"
9 - default_tag_id?: number | null
9 + enabled: boolean
10 + untagged_alert_behavior: UntaggedAlertBehavior
11 + default_tag_id?: number | null
12 }
13
14 export interface TagAccessSettingsItem {
13 - enabled: boolean
14 - untagged_alert_behavior: "visible_to_all" | "admin_only" | "default_tag"
15 - default_tag_id: number | null
16 - default_tag_name: string | null
17 -}
18 -
19 -// Response from GET /settings - settings nested under 'settings' key
20 -export interface TagAccessSettingsResponse {
21 - settings: TagAccessSettingsItem
22 - success: boolean
23 - message: string
24 -}
25 -
26 -// Response from GET /user/{user_id}
27 -export interface UserTagsResponse {
28 - user_id: number
29 - username: string
30 - accessible_tags: AlertTag[]
31 - success: boolean
32 - message: string
15 + enabled: boolean
16 + untagged_alert_behavior: UntaggedAlertBehavior
17 + default_tag_id: number | null
18 + default_tag_name: string | null
19 }
20
35 -// Response from GET /tags
36 -export interface AvailableTagsResponse {
37 - tags: AlertTag[]
38 - success: boolean
39 - message: string
21 +export interface EffectiveAccessResponse {
22 + user_id: number
23 + username: string
24 + role_id: number
25 + role_name: string
26 + accessible_customers: string[]
27 + accessible_tags: AlertTag[]
28 + is_tag_unrestricted: boolean
29 + tag_rbac_enabled: boolean
30 }
frontend/src/types/theme.d.ts
+1
@@ -17,6 +17,7 @@ export enum ThemeNameEnum {
17 }
18
19 export type ColorScene = "" | "Suppl" | "Hover" | "Pressed"
20 +export type ColorType = "primary" | "success" | "warning" | "error" | "info"
21 export type ColorKey = `${ColorType}Color${ColorScene}`
22 export type ThemeColor = Partial<Record<ColorKey, string>>
23
frontend/src/types/vulnerabilities.d.ts
+37 -36
@@ -1,3 +1,4 @@
1 +// TODO: refactor
2 export interface VulnerabilitySearchItem {
3 cve_id: string
4 severity: VulnerabilitySeverity
@@ -51,52 +52,52 @@ export interface VulnerabilitySearchQuery {
52 }
53
54 export interface VulnerabilityReport {
54 - id: number
55 - report_name: string
56 - customer_code: string
57 - file_name: string
58 - file_size: number
59 - generated_at: string
60 - generated_by: number
61 - total_vulnerabilities: number
62 - critical_count: number
63 - high_count: number
64 - medium_count: number
65 - low_count: number
66 - filters_applied: Record<string, string | number | boolean>
67 - status: "processing" | "completed" | "failed"
68 - error_message?: string | null
69 - download_url: string
55 + id: number
56 + report_name: string
57 + customer_code: string
58 + file_name: string
59 + file_size: number
60 + generated_at: string
61 + generated_by: number
62 + total_vulnerabilities: number
63 + critical_count: number
64 + high_count: number
65 + medium_count: number
66 + low_count: number
67 + filters_applied: Record<string, string | number | boolean>
68 + status: "processing" | "completed" | "failed"
69 + error_message?: string | null
70 + download_url: string
71 }
72
73 export interface VulnerabilityReportGenerateRequest {
73 - customer_code: string
74 - report_name?: string
75 - agent_name?: string
76 - severity?: VulnerabilitySeverity
77 - cve_id?: string
78 - package_name?: string
79 - include_epss?: boolean
74 + customer_code: string
75 + report_name?: string
76 + agent_name?: string
77 + severity?: VulnerabilitySeverity
78 + cve_id?: string
79 + package_name?: string
80 + include_epss?: boolean
81 }
82
83 export interface VulnerabilityReportGenerateResponse {
83 - success: boolean
84 - message: string
85 - report?: VulnerabilityReport
86 - error?: string
84 + success: boolean
85 + message: string
86 + report?: VulnerabilityReport
87 + error?: string
88 }
89
90 export interface VulnerabilityReportListResponse {
90 - success: boolean
91 - message: string
92 - reports: VulnerabilityReport[]
93 - total_count: number
91 + success: boolean
92 + message: string
93 + reports: VulnerabilityReport[]
94 + total_count: number
95 }
96
97 export interface VulnerabilityReportDeleteResponse {
97 - success: boolean
98 - message: string
99 - report_id: number
100 - report_name: string
101 - customer_code: string
98 + success: boolean
99 + message: string
100 + report_id: number
101 + report_name: string
102 + customer_code: string
103 }
frontend/src/utils/format.ts
+14 -16
@@ -1,20 +1,18 @@
1 -export function formatDate(dateString: string): string {
2 - const date = new Date(dateString)
3 - return new Intl.DateTimeFormat("en-US", {
4 - year: "numeric",
5 - month: "short",
6 - day: "numeric",
7 - hour: "2-digit",
8 - minute: "2-digit"
9 - }).format(date)
10 -}
1 +import bytes from "bytes"
2 +import _toNumber from "lodash/toNumber"
3 +import dayjs from "@/utils/dayjs"
4
12 -export function formatBytes(bytes: number): string {
13 - if (bytes === 0) return "0 Bytes"
5 +export function formatBytes(val: string | number) {
6 + return bytes(_toNumber(val))
7 +}
8
15 - const k = 1024
16 - const sizes = ["Bytes", "KB", "MB", "GB"]
17 - const i = Math.floor(Math.log(bytes) / Math.log(k))
9 +export function formatDate(date: Date | string | number, format: string) {
10 + let parsedDate = date
11 + if (typeof date === "number" && date.toString().length >= 15) {
12 + parsedDate = date / 1000
13 + }
14 + const datejs = dayjs(parsedDate)
15 + if (!datejs.isValid()) return date
16
19 - return `${Math.round(bytes / k ** i * 100) / 100} ${sizes[i]}`
17 + return datejs.format(format)
18 }
frontend/src/utils/highlighter.ts
+2 -2
@@ -59,8 +59,8 @@ export function resetIndent(el: HTMLElement) {
59 lines.shift()
60 }
61
62 - const matches = /^\s+/.exec(lines[0])
63 - const indentation = matches !== null ? matches[0] : null
62 + const matches = /^\s+/.exec(lines[0] ?? "") ?? null
63 + const indentation = matches !== null ? (matches[0] ?? "") : ""
64 if (indentation) {
65 lines = lines.map(line => {
66 line = line.replace(indentation, "")
frontend/src/utils/index.ts
-12
@@ -6,7 +6,6 @@ import { md5 } from "js-md5"
6 import _trim from "lodash/trim"
7 import { h } from "vue"
8 import Icon from "@/components/common/Icon.vue"
9 -import dayjs from "@/utils/dayjs"
9
10 // Transform File Instance in base64 string
11 export function file2Base64(blob: Blob): Promise<string> {
@@ -99,17 +98,6 @@ export function hashMD5(text: number | string) {
98 return md5(text.toString())
99 }
100
102 -export function formatDate(date: Date | string | number, format: string) {
103 - let parsedDate = date
104 - if (typeof date === "number" && date.toString().length >= 15) {
105 - parsedDate = date / 1000
106 - }
107 - const datejs = dayjs(parsedDate)
108 - if (!datejs.isValid()) return date
109 -
110 - return datejs.format(format)
111 -}
112 -
101 export function price(
102 amount: number,
103 options: { currency?: "USD" | "EUR"; splitDecimal?: boolean } = { currency: "USD", splitDecimal: true }
frontend/src/utils/theme.ts
+5 -5
@@ -1,4 +1,4 @@
1 -import type { ColorAction, ColorKey, ThemeColor } from "@/types/theme.d"
1 +import type { ColorAction, ColorKey, ColorType, ThemeColor } from "@/types/theme"
2 import { colord } from "colord"
3 import _get from "lodash/get"
4
@@ -63,7 +63,7 @@ export function getTypeValue(origin: object, val: string) {
63 return val
64 }
65
66 -export function getThemeColors(colors: Record<string, string>) {
66 +export function getThemeColors(colors: Record<ColorType, string>) {
67 const colorActions: ColorAction[] = [
68 { scene: "", handler: color => color },
69 { scene: "Suppl", handler: color => exposure(color, 0.1) },
@@ -74,10 +74,10 @@ export function getThemeColors(colors: Record<string, string>) {
74 const themeColor: ThemeColor = {}
75
76 for (const colorName in colors) {
77 - const colorValue = colors[colorName]
77 + const colorValue = colors[colorName as ColorType]
78
79 colorActions.forEach(action => {
80 - const colorKey: ColorKey = `${colorName}Color${action.scene}`
80 + const colorKey: ColorKey = `${colorName as ColorType}Color${action.scene}`
81 themeColor[colorKey] = action.handler(colorValue)
82 })
83 }
@@ -101,7 +101,7 @@ export function expandPattern(input: string): string[] {
101
102 // Expands the first group found
103 const [fullMatch, options] = match
104 - const variants = options.split("|") // Splits the options by "|"
104 + const variants = options?.split("|") ?? [] // Splits the options by "|"
105
106 // Replaces the first group with each option and calls recursively
107 return variants.flatMap(option => expandPattern(input.replace(fullMatch, option)))
frontend/src/views/Indices.vue
+68 -67
@@ -37,6 +37,7 @@
37 </template>
38
39 <script lang="ts" setup>
40 +// TODO: refactor
41 import type { IndexStats } from "@/types/indices.d"
42 import { NCard, useMessage } from "naive-ui"
43 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
@@ -59,84 +60,84 @@ const currentIndex = ref<IndexStats | null>(null)
60 const requestedIndex = ref<string | null>(null)
61
62 function setIndex(index: IndexStats | string) {
62 - if (typeof index === "string") {
63 - const indexStats = indices.value?.find(o => o.index === index) || null
64 - indexStats && (currentIndex.value = indexStats)
65 - } else {
66 - currentIndex.value = index
67 - }
63 + if (typeof index === "string") {
64 + const indexStats = indices.value?.find(o => o.index === index) || null
65 + indexStats && (currentIndex.value = indexStats)
66 + } else {
67 + currentIndex.value = index
68 + }
69 }
70
71 function getIndices(cb?: () => void) {
71 - loadingIndex.value = true
72 -
73 - Api.wazuh.indices
74 - .getIndices()
75 - .then(res => {
76 - if (res.data.success) {
77 - indices.value = res.data.indices_stats
78 -
79 - if (cb) cb()
80 - } else {
81 - message.error(res.data?.message || "An error occurred. Please try again later.")
82 - }
83 - })
84 - .catch(err => {
85 - if (err.response?.status === 401) {
86 - message.error(
87 - err.response?.data?.message ||
88 - "Wazuh-Indexer returned Unauthorized. Please check your connector credentials."
89 - )
90 - } else if (err.response?.status === 404) {
91 - message.error(err.response?.data?.message || "No indices were found.")
92 - } else {
93 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
94 - }
95 - })
96 - .finally(() => {
97 - loadingIndex.value = false
98 - })
72 + loadingIndex.value = true
73 +
74 + Api.wazuh.indices
75 + .getIndices()
76 + .then(res => {
77 + if (res.data.success) {
78 + indices.value = res.data.indices_stats
79 +
80 + if (cb) cb()
81 + } else {
82 + message.error(res.data?.message || "An error occurred. Please try again later.")
83 + }
84 + })
85 + .catch(err => {
86 + if (err.response?.status === 401) {
87 + message.error(
88 + err.response?.data?.message ||
89 + "Wazuh-Indexer returned Unauthorized. Please check your connector credentials."
90 + )
91 + } else if (err.response?.status === 404) {
92 + message.error(err.response?.data?.message || "No indices were found.")
93 + } else {
94 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
95 + }
96 + })
97 + .finally(() => {
98 + loadingIndex.value = false
99 + })
100 }
101
102 onBeforeMount(() => {
102 - if (route.query?.index_name) {
103 - requestedIndex.value = route.query.index_name.toString()
104 - }
103 + if (route.query?.index_name) {
104 + requestedIndex.value = route.query.index_name.toString()
105 + }
106
106 - getIndices(() => {
107 - requestedIndex.value && setIndex(requestedIndex.value)
108 - })
107 + getIndices(() => {
108 + requestedIndex.value && setIndex(requestedIndex.value)
109 + })
110 })
111 </script>
112
113 <style lang="scss" scoped>
114 .page {
114 - .section {
115 - margin-bottom: calc(var(--spacing) * 6);
116 -
117 - .columns {
118 - display: flex;
119 - gap: calc(var(--spacing) * 6);
120 -
121 - .stretchy {
122 - height: 100%;
123 - }
124 - }
125 - }
126 -
127 - @media (max-width: 1000px) {
128 - .section {
129 - .columns {
130 - flex-direction: column;
131 - }
132 - }
133 - }
134 - @media (max-width: 1200px) {
135 - .section {
136 - .columns.column-1200 {
137 - flex-direction: column;
138 - }
139 - }
140 - }
115 + .section {
116 + margin-bottom: calc(var(--spacing) * 6);
117 +
118 + .columns {
119 + display: flex;
120 + gap: calc(var(--spacing) * 6);
121 +
122 + .stretchy {
123 + height: 100%;
124 + }
125 + }
126 + }
127 +
128 + @media (max-width: 1000px) {
129 + .section {
130 + .columns {
131 + flex-direction: column;
132 + }
133 + }
134 + }
135 + @media (max-width: 1200px) {
136 + .section {
137 + .columns.column-1200 {
138 + flex-direction: column;
139 + }
140 + }
141 + }
142 }
143 </style>
frontend/src/views/Overview.vue
+19 -17
@@ -45,7 +45,7 @@
45 </div>
46 </div>
47 <div class="section">
48 - <IndicesMarquee @click="gotoIndex($event.index)" />
48 + <IndicesMarquee @click="routeIndex($event.index).navigate()" />
49 </div>
50 <div class="section">
51 <div class="columns flex-col lg:flex-row">
@@ -58,7 +58,7 @@
58 </div>
59 </div>
60 <div class="section">
61 - <PipeList @open-rule="gotoGraylogPipelines($event)" />
61 + <PipeList @open-rule="routeGraylogPipelines($event).navigate()" />
62 </div>
63
64 <n-drawer
@@ -102,7 +102,7 @@ import IncidentAlerts from "@/components/overview/IncidentAlerts.vue"
102 import IncidentCases from "@/components/overview/IncidentCases.vue"
103 import StackProvisioningButton from "@/components/stackProvisioning/StackProvisioningButton.vue"
104 import WebVulnerabilityAssessmentButton from "@/components/webVulnerabilityAssessment/WebVulnerabilityAssessmentButton.vue"
105 -import { useGoto } from "@/composables/useGoto"
105 +import { useNavigation } from "@/composables/useNavigation"
106
107 const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
108
@@ -110,29 +110,31 @@ const QuickActionsIcon = "ant-design:thunderbolt-outlined"
110 const page = ref()
111 const cardDirection = ref<"horizontal" | "vertical">("horizontal")
112 const showQuickActions = ref(false)
113 -const { gotoIndex, gotoGraylogPipelines } = useGoto()
113 +const { routeIndex, routeGraylogPipelines } = useNavigation()
114
115 useResizeObserver(page, entries => {
116 - const entry = entries[0]
117 - const { width } = entry.contentRect
116 + const entry = entries[0]
117 + if (!entry) return
118
119 - cardDirection.value = width > 500 ? "horizontal" : "vertical"
119 + const { width } = entry.contentRect
120 +
121 + cardDirection.value = width > 500 ? "horizontal" : "vertical"
122 })
123 </script>
124
125 <style lang="scss" scoped>
126 .page {
125 - .section {
126 - margin-bottom: calc(var(--spacing) * 6);
127 + .section {
128 + margin-bottom: calc(var(--spacing) * 6);
129
128 - .columns {
129 - display: flex;
130 - gap: calc(var(--spacing) * 6);
130 + .columns {
131 + display: flex;
132 + gap: calc(var(--spacing) * 6);
133
132 - .stretchy {
133 - height: 100%;
134 - }
135 - }
136 - }
134 + .stretchy {
135 + height: 100%;
136 + }
137 + }
138 + }
139 }
140 </style>
frontend/src/views/Snapshots.vue
+2 -1
@@ -19,6 +19,7 @@
19 </template>
20
21 <script setup lang="ts">
22 +// TODO: refactor
23 import { NTabPane, NTabs } from "naive-ui"
24 import { ref } from "vue"
25 import SnapshotList from "@/components/snapshots/SnapshotList.vue"
@@ -30,6 +31,6 @@ const activeTab = ref("repositories")
31
32 <style scoped>
33 .page-wrapper {
33 - min-height: 100%;
34 + min-height: 100%;
35 }
36 </style>
frontend/src/views/agents/Agents.vue
+189 -189
@@ -12,7 +12,7 @@
12 :selection-mode="selectionMode"
13 :selected-count="selectedAgents.length"
14 @run="runCommand($event)"
15 - @click="gotoAgent($event.agent_id)"
15 + @click="routeAgent($event.agent_id).navigate()"
16 @bulk-delete="showBulkDeleteModal = true"
17 @update:selection-mode="selectionMode = $event"
18 @clear-selection="clearSelection"
@@ -81,11 +81,11 @@ import Api from "@/api"
81 import AgentCard from "@/components/agents/AgentCard.vue"
82 import AgentToolbar from "@/components/agents/AgentToolbar.vue"
83 import BulkDeleteModal from "@/components/agents/BulkDeleteModal.vue"
84 -import { useGoto } from "@/composables/useGoto"
84 +import { useNavigation } from "@/composables/useNavigation"
85 import { AgentStatus } from "@/types/agents.d"
86
87 const message = useMessage()
88 -const { gotoAgent } = useGoto()
88 +const { routeAgent } = useNavigation()
89 const loadingAgents = ref(false)
90 const loadingSync = ref(false)
91 const agents = ref<Agent[]>([])
@@ -101,250 +101,250 @@ const showBulkDeleteModal = ref(false)
101 const textFilterDebounced = ref("")
102
103 const update = _debounce(value => {
104 - textFilterDebounced.value = value
104 + textFilterDebounced.value = value
105 }, 100)
106
107 watch(textFilter, val => {
108 - update(val)
108 + update(val)
109 })
110
111 const agentsFiltered = computed(() => {
112 - return agents.value
113 - .filter(({ hostname, ip_address, agent_id, label }) =>
114 - (hostname + ip_address + agent_id + label)
115 - .toString()
116 - .toLowerCase()
117 - .includes(textFilterDebounced.value.toString().toLowerCase())
118 - )
119 - .sort((a, b) => Number.parseInt(a.agent_id) - Number.parseInt(b.agent_id))
112 + return agents.value
113 + .filter(({ hostname, ip_address, agent_id, label }) =>
114 + (hostname + ip_address + agent_id + label)
115 + .toString()
116 + .toLowerCase()
117 + .includes(textFilterDebounced.value.toString().toLowerCase())
118 + )
119 + .sort((a, b) => Number.parseInt(a.agent_id) - Number.parseInt(b.agent_id))
120 })
121
122 const itemsPaginated = computed(() => {
123 - const from = (page.value - 1) * pageSize.value
124 - const to = page.value * pageSize.value
123 + const from = (page.value - 1) * pageSize.value
124 + const to = page.value * pageSize.value
125
126 - return agentsFiltered.value.slice(from, to)
126 + return agentsFiltered.value.slice(from, to)
127 })
128
129 const agentsCritical = computed(() => {
130 - return agents.value.filter(({ critical_asset }) => critical_asset)
130 + return agents.value.filter(({ critical_asset }) => critical_asset)
131 })
132
133 const agentsOnline = computed(() => {
134 - return agents.value.filter(({ wazuh_agent_status }) => wazuh_agent_status === AgentStatus.Active)
134 + return agents.value.filter(({ wazuh_agent_status }) => wazuh_agent_status === AgentStatus.Active)
135 })
136
137 // Get unique customer codes for filter dropdown
138 const uniqueCustomers = computed(() => {
139 - const codes = new Set(agents.value.map(a => a.customer_code).filter(Boolean))
140 - return Array.from(codes) as string[]
139 + const codes = new Set(agents.value.map(a => a.customer_code).filter(Boolean))
140 + return Array.from(codes) as string[]
141 })
142
143 // Selection helpers
144 function isAgentSelected(agent: Agent): boolean {
145 - return selectedAgents.value.some(a => a.agent_id === agent.agent_id)
145 + return selectedAgents.value.some(a => a.agent_id === agent.agent_id)
146 }
147
148 function toggleAgentSelection(agent: Agent) {
149 - const index = selectedAgents.value.findIndex(a => a.agent_id === agent.agent_id)
150 - if (index === -1) {
151 - selectedAgents.value.push(agent)
152 - } else {
153 - selectedAgents.value.splice(index, 1)
154 - }
149 + const index = selectedAgents.value.findIndex(a => a.agent_id === agent.agent_id)
150 + if (index === -1) {
151 + selectedAgents.value.push(agent)
152 + } else {
153 + selectedAgents.value.splice(index, 1)
154 + }
155 }
156
157 function removeFromSelection(agent: Agent) {
158 - const index = selectedAgents.value.findIndex(a => a.agent_id === agent.agent_id)
159 - if (index !== -1) {
160 - selectedAgents.value.splice(index, 1)
161 - }
158 + const index = selectedAgents.value.findIndex(a => a.agent_id === agent.agent_id)
159 + if (index !== -1) {
160 + selectedAgents.value.splice(index, 1)
161 + }
162 }
163
164 function clearSelection() {
165 - selectedAgents.value = []
165 + selectedAgents.value = []
166 }
167
168 function handleAgentClick(agent: Agent) {
169 - if (selectionMode.value) {
170 - toggleAgentSelection(agent)
171 - } else {
172 - gotoAgent(agent.agent_id)
173 - }
169 + if (selectionMode.value) {
170 + toggleAgentSelection(agent)
171 + } else {
172 + routeAgent(agent.agent_id).navigate()
173 + }
174 }
175
176 function onBulkDeleteComplete() {
177 - clearSelection()
178 - selectionMode.value = false
179 - getAgents()
177 + clearSelection()
178 + selectionMode.value = false
179 + getAgents()
180 }
181
182 function runCommand(command: string) {
183 - if (command === "sync-agents") {
184 - syncAgents()
185 - } else if (_split(command, ":").length) {
186 - syncVulnerabilities(_split(command, ":")[1])
187 - }
183 + if (command === "sync-agents") {
184 + syncAgents()
185 + } else if (_split(command, ":").length) {
186 + syncVulnerabilities(_split(command, ":")[1] ?? "")
187 + }
188 }
189
190 function getAgents() {
191 - loadingAgents.value = true
192 -
193 - Api.agents
194 - .getAgents()
195 - .then(res => {
196 - if (res.data.success) {
197 - agents.value = res.data.agents || []
198 - } else {
199 - message.error(res.data?.message || "An error occurred. Please try again later.")
200 - }
201 - })
202 - .catch(err => {
203 - message.error(err.response?.data?.message || "An error occurred. Please try again later.")
204 - })
205 - .finally(() => {
206 - loadingAgents.value = false
207 - })
191 + loadingAgents.value = true
192 +
193 + Api.agents
194 + .getAgents()
195 + .then(res => {
196 + if (res.data.success) {
197 + agents.value = res.data.agents || []
198 + } else {
199 + message.error(res.data?.message || "An error occurred. Please try again later.")
200 + }
201 + })
202 + .catch(err => {
203 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
204 + })
205 + .finally(() => {
206 + loadingAgents.value = false
207 + })
208 }
209
210 function syncAgents() {
211 - loadingSync.value = true
212 -
213 - Api.agents
214 - .syncAgents()
215 - .then(res => {
216 - if (res.data.success) {
217 - message.success("Agents Synced Successfully")
218 - getAgents()
219 - } else {
220 - message.error("An error occurred. Please try again later.")
221 - }
222 - })
223 - .catch(err => {
224 - if (err.response?.status === 401) {
225 - message.error(err.response?.data?.message || "Sync returned Unauthorized.")
226 - } else {
227 - message.error(err.response?.data?.message || "Failed to Sync Agents")
228 - }
229 - })
230 - .finally(() => {
231 - loadingSync.value = false
232 - })
211 + loadingSync.value = true
212 +
213 + Api.agents
214 + .syncAgents()
215 + .then(res => {
216 + if (res.data.success) {
217 + message.success("Agents Synced Successfully")
218 + getAgents()
219 + } else {
220 + message.error("An error occurred. Please try again later.")
221 + }
222 + })
223 + .catch(err => {
224 + if (err.response?.status === 401) {
225 + message.error(err.response?.data?.message || "Sync returned Unauthorized.")
226 + } else {
227 + message.error(err.response?.data?.message || "Failed to Sync Agents")
228 + }
229 + })
230 + .finally(() => {
231 + loadingSync.value = false
232 + })
233 }
234
235 function syncVulnerabilities(customerCode: string) {
236 - loadingSync.value = true
237 -
238 - Api.agents
239 - .syncVulnerabilities(customerCode)
240 - .then(res => {
241 - if (res.data.success) {
242 - message.success("Agent vulnerabilities synced successfully")
243 - getAgents()
244 - } else {
245 - message.error("An error occurred. Please try again later.")
246 - }
247 - })
248 - .catch(err => {
249 - if (err.response?.status === 401) {
250 - message.error(err.response?.data?.message || "Sync returned Unauthorized.")
251 - } else {
252 - message.error(err.response?.data?.message || "Failed to Sync Agents")
253 - }
254 - })
255 - .finally(() => {
256 - loadingSync.value = false
257 - })
236 + loadingSync.value = true
237 +
238 + Api.agents
239 + .syncVulnerabilities(customerCode)
240 + .then(res => {
241 + if (res.data.success) {
242 + message.success("Agent vulnerabilities synced successfully")
243 + getAgents()
244 + } else {
245 + message.error("An error occurred. Please try again later.")
246 + }
247 + })
248 + .catch(err => {
249 + if (err.response?.status === 401) {
250 + message.error(err.response?.data?.message || "Sync returned Unauthorized.")
251 + } else {
252 + message.error(err.response?.data?.message || "Failed to Sync Agents")
253 + }
254 + })
255 + .finally(() => {
256 + loadingSync.value = false
257 + })
258 }
259
260 onBeforeMount(() => {
261 - getAgents()
261 + getAgents()
262 })
263 </script>
264
265 <style lang="scss" scoped>
266 .page {
267 - container-type: inline-size;
268 -
269 - .wrapper {
270 - position: relative;
271 - height: 100%;
272 - overflow: hidden;
273 -
274 - .sidebar {
275 - .agent-toolbar {
276 - height: 100%;
277 - }
278 - }
279 -
280 - :deep() {
281 - .n-spin-content {
282 - overflow: hidden;
283 - max-height: 100%;
284 - }
285 - }
286 -
287 - .main {
288 - position: relative;
289 - border-radius: var(--border-radius);
290 -
291 - :deep() {
292 - .n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical {
293 - right: 0;
294 - bottom: 50px;
295 - }
296 - }
297 -
298 - .pagination-wrapper {
299 - --size: 10px;
300 - position: absolute;
301 - bottom: 0;
302 - right: 0;
303 - background-color: var(--bg-body-color);
304 - padding-left: var(--size);
305 - padding-top: var(--size);
306 - border-top-left-radius: var(--size);
307 -
308 - &::before,
309 - &::after {
310 - content: "";
311 - position: absolute;
312 - width: var(--size);
313 - height: var(--size);
314 - left: calc(var(--size) * -1);
315 - display: block;
316 - bottom: 0px;
317 - z-index: 1;
318 - background-image: radial-gradient(
319 - circle at 0 0,
320 - rgba(0, 0, 0, 0) calc(var(--size) - 1px),
321 - var(--bg-body-color) calc(var(--size) + 0px)
322 - );
323 - }
324 -
325 - &::after {
326 - bottom: initial;
327 - left: initial;
328 - top: calc(var(--size) * -1);
329 - right: 0;
330 - }
331 - }
332 - }
333 -
334 - .agents-list {
335 - width: 100%;
336 -
337 - .item-appear {
338 - &:last-child {
339 - margin-bottom: 50px;
340 - }
341 - }
342 - }
343 - }
344 - @container (max-width: 770px) {
345 - .wrapper {
346 - flex-direction: column;
347 - }
348 - }
267 + container-type: inline-size;
268 +
269 + .wrapper {
270 + position: relative;
271 + height: 100%;
272 + overflow: hidden;
273 +
274 + .sidebar {
275 + .agent-toolbar {
276 + height: 100%;
277 + }
278 + }
279 +
280 + :deep() {
281 + .n-spin-content {
282 + overflow: hidden;
283 + max-height: 100%;
284 + }
285 + }
286 +
287 + .main {
288 + position: relative;
289 + border-radius: var(--border-radius);
290 +
291 + :deep() {
292 + .n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical {
293 + right: 0;
294 + bottom: 50px;
295 + }
296 + }
297 +
298 + .pagination-wrapper {
299 + --size: 10px;
300 + position: absolute;
301 + bottom: 0;
302 + right: 0;
303 + background-color: var(--bg-body-color);
304 + padding-left: var(--size);
305 + padding-top: var(--size);
306 + border-top-left-radius: var(--size);
307 +
308 + &::before,
309 + &::after {
310 + content: "";
311 + position: absolute;
312 + width: var(--size);
313 + height: var(--size);
314 + left: calc(var(--size) * -1);
315 + display: block;
316 + bottom: 0px;
317 + z-index: 1;
318 + background-image: radial-gradient(
319 + circle at 0 0,
320 + rgba(0, 0, 0, 0) calc(var(--size) - 1px),
321 + var(--bg-body-color) calc(var(--size) + 0px)
322 + );
323 + }
324 +
325 + &::after {
326 + bottom: initial;
327 + left: initial;
328 + top: calc(var(--size) * -1);
329 + right: 0;
330 + }
331 + }
332 + }
333 +
334 + .agents-list {
335 + width: 100%;
336 +
337 + .item-appear {
338 + &:last-child {
339 + margin-bottom: 50px;
340 + }
341 + }
342 + }
343 + }
344 + @container (max-width: 770px) {
345 + .wrapper {
346 + flex-direction: column;
347 + }
348 + }
349 }
350 </style>
frontend/src/views/agents/Overview.vue
+6 -6
@@ -1,7 +1,7 @@
1 <template>
2 <div class="page">
3 <div class="agent-toolbar">
4 - <div class="back-btn" @click="gotoAgent()">
4 + <div class="back-btn" @click="routeAgent().navigate()">
5 <Icon :name="ArrowIcon" :size="16" />
6 <span>Agents list</span>
7 </div>
@@ -160,7 +160,7 @@ import Api from "@/api"
160 import { handleDeleteAgent, toggleAgentCritical } from "@/components/agents/utils"
161 import CardEntity from "@/components/common/cards/CardEntity.vue"
162 import Icon from "@/components/common/Icon.vue"
163 -import { useGoto } from "@/composables/useGoto"
163 +import { useNavigation } from "@/composables/useNavigation"
164 import { AgentStatus } from "@/types/agents.d"
165
166 const VulnerabilitiesGrid = defineAsyncComponent(
@@ -186,7 +186,7 @@ const StarIcon = "carbon:star"
186 const QuarantinedIcon = "ph:seal-warning-light"
187 const ArrowIcon = "carbon:arrow-left"
188
189 -const { gotoAgent } = useGoto()
189 +const { routeAgent } = useNavigation()
190 const message = useMessage()
191 const router = useRouter()
192 const dialog = useDialog()
@@ -217,12 +217,12 @@ function getAgent() {
217 agent.value = res.data.agents[0] || null
218 } else {
219 message.error(res.data?.message || "An error occurred. Please try again later.")
220 - gotoAgent()
220 + routeAgent().navigate()
221 }
222 })
223 .catch(err => {
224 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
225 - gotoAgent()
225 + routeAgent().navigate()
226 })
227 .finally(() => {
228 loadingAgent.value = false
@@ -281,7 +281,7 @@ function handleDelete() {
281 loadingAgent.value = true
282 },
283 cbSuccess: () => {
284 - gotoAgent()
284 + routeAgent().navigate()
285 },
286 cbAfter: () => {
287 loadingAgent.value = false
frontend/src/views/agents/SysmonConfig.vue
+3 -3
@@ -22,7 +22,7 @@
22 >
23 <div class="flex items-center justify-between">
24 <div>{{ customer }}</div>
25 - <n-button text @click.stop="gotoCustomer({ code: customer })">
25 + <n-button text @click.stop="routeCustomer({ code: customer }).navigate()">
26 <template #icon>
27 <Icon :size="14" :name="LinkIcon" />
28 </template>
@@ -167,10 +167,10 @@ import CardEntity from "@/components/common/cards/CardEntity.vue"
167 import Icon from "@/components/common/Icon.vue"
168 import SegmentedPage from "@/components/common/SegmentedPage.vue"
169 import XMLEditor from "@/components/common/XMLEditor.vue"
170 -import { useGoto } from "@/composables/useGoto"
170 +import { useNavigation } from "@/composables/useNavigation"
171
172 const message = useMessage()
173 -const { gotoCustomer } = useGoto()
173 +const { routeCustomer } = useNavigation()
174 const loadingList = ref(false)
175 const loadingConfig = ref(false)
176 const uploadingConfig = ref(false)
frontend/src/views/graylog/Management.vue
+1 -1
@@ -51,7 +51,7 @@ const Inputs = defineAsyncComponent(() => import("@/components/graylog/Inputs/Li
51 const tabsList = ["messages", "alerts", "events", "streams", "provisioning"]
52 const drawersList = ["inputs"]
53
54 -const activeTab = ref<string>(tabsList[0])
54 +const activeTab = ref<string>(tabsList[0] || "")
55 const highlightEvent = ref<string | undefined>(undefined)
56 const showInputDrawer = ref(false)
57 const events = ref<EventDefinition[]>([])
frontend/src/views/graylog/Metrics.vue
+1 -1
@@ -48,7 +48,7 @@ import Icon from "@/components/common/Icon.vue"
48 import MetricsList from "@/components/graylog/Metrics/List.vue"
49 import UncommittedEntries from "@/components/graylog/Metrics/UncommittedEntries.vue"
50 import { useSettingsStore } from "@/stores/settings"
51 -import { formatDate } from "@/utils"
51 +import { formatDate } from "@/utils/format"
52
53 const UpdatedIcon = "carbon:update-now"
54 const StopIcon = "carbon:stop"
frontend/src/views/incidentManagement/Sources.vue
+2
@@ -73,6 +73,8 @@ function reloadExclusionRulesList() {
73
74 useResizeObserver(page, entries => {
75 const entry = entries[0]
76 + if (!entry) return
77 +
78 const { width } = entry.contentRect
79
80 showConfiguredSourcesListToolbar.value = width < 600