@cryptotaxi247 / CoPilot / commits / a5c4b0fa

Ai analyst alerts wazuh (#334)

* add ai analysts route * fetch alert details * add static return during frontend dev * feat: add AI Analyst Button * refactor: lint * hard set response payload for troubleshooting * improve dev payload * wazuh exclusion rule * feat: add justification for Wazuh exclusion rule in AI analysis * chore: update dependencies in frontend * refactor: props default * fix: improved code highlighter * feat: improved AI Analyst card * refactor: lint * fix: CodeSource component * add wazuh exclusion rule route * refactor: remove unused Wazuh exclusion rule fields from Socfortress AI alert response * feat: add Wazuh exclusion rule lookup functionality * fix: update connector_id for Duo integration in get_collect_duo_data function * fix: remove trailing hyphen from index_prefix in build_index_set_config function * chore: update dependencies in frontend * feat: add AIWazuhExclusionRuleButton component * refactor: lint * fix: raise HTTPException for unready endpoints in socfortress routes * chore: update Docker workflow to push to ai-analyst-alerts-wazuh branch and comment out Discord notifications * chore: update Docker workflow to push to main branch and re-enable Discord notifications * add rate limit reached error code * check license for AI * feat: improved License checker component * feat: add AI License check * refactor: improve report creation page style * chore: update dependencies in frontend * refactor: lint * refactor: remove unused code and clean up alert handling in SOCFORTRESS AI * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Nov 14, 2024 at 07:24 UTC a5c4b0fad397529c62da81bd08e8fb6ef0d75ea7
35 files changed +1217 -574
backend/app/integrations/duo/services/provision.py
+1 -1
@@ -50,7 +50,7 @@ async def build_index_set_config(
50 return TimeBasedIndexSet(
51 title=f"{(await get_customer(customer_code, session)).customer.customer_name} - DUO",
52 description=f"{customer_code} - DUO",
53 - index_prefix=f"duo-{customer_code.lower()}-",
53 + index_prefix=f"duo-{customer_code.lower()}",
54 rotation_strategy_class="org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategy",
55 rotation_strategy={
56 "type": "org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategyConfig",
backend/app/integrations/modules/routes/duo.py
+2 -2
@@ -41,12 +41,12 @@ async def get_collect_duo_data(duo_request, session, auth_keys):
41 integration="duo",
42 customer_code=duo_request.customer_code,
43 graylog_host=await get_connector_attribute(
44 - connector_id=14,
44 + connector_id=10,
45 column_name="connector_url",
46 session=session,
47 ),
48 graylog_port=await get_connector_attribute(
49 - connector_id=14,
49 + connector_id=10,
50 column_name="connector_extra_data",
51 session=session,
52 ),
backend/app/threat_intel/routes/socfortress.py
+65
@@ -7,14 +7,25 @@ from sqlalchemy.ext.asyncio import AsyncSession
7
8 from app.auth.utils import AuthHandler
9 from app.db.db_session import get_db
10 +from app.incidents.schema.incident_alert import CreateAlertRequest
11 +from app.incidents.schema.incident_alert import CreateAlertRequestRoute
12 +from app.incidents.schema.incident_alert import GenericAlertModel
13 +from app.incidents.services.incident_alert import get_single_alert_details
14 from app.middleware.license import get_license
15 from app.middleware.license import is_feature_enabled
16 from app.threat_intel.schema.socfortress import IoCResponse
17 +from app.threat_intel.schema.socfortress import SocfortressAiAlertRequest
18 +from app.threat_intel.schema.socfortress import SocfortressAiAlertResponse
19 +from app.threat_intel.schema.socfortress import SocfortressAiWazuhExclusionRuleResponse
20 from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisRequest
21 from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisResponse
22 from app.threat_intel.schema.socfortress import SocfortressThreatIntelRequest
23 +from app.threat_intel.services.socfortress import socfortress_ai_alert_lookup
24 from app.threat_intel.services.socfortress import socfortress_process_analysis_lookup
25 from app.threat_intel.services.socfortress import socfortress_threat_intel_lookup
26 +from app.threat_intel.services.socfortress import (
27 + socfortress_wazuh_exclusion_rule_lookup,
28 +)
29 from app.utils import get_connector_attribute
30
31 # App specific imports
@@ -117,3 +128,57 @@ async def process_name_intel_socfortress(
128 session=session,
129 )
130 return socfortress_lookup
131 +
132 +
133 +@threat_intel_socfortress_router.post(
134 + "/ai/analyze-alert",
135 + response_model=SocfortressAiAlertResponse,
136 + description="SocFortress Process Name Evaluation",
137 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
138 +)
139 +async def ai_anaylze_alert_socfortress(
140 + request: CreateAlertRequestRoute,
141 + session: AsyncSession = Depends(get_db),
142 +):
143 + # Fetch alert details
144 + alert_details = await get_single_alert_details(CreateAlertRequest(index_name=request.index_name, alert_id=request.index_id))
145 +
146 + assert isinstance(alert_details, GenericAlertModel)
147 +
148 + request = SocfortressAiAlertRequest(
149 + integration="AI",
150 + alert_payload=alert_details._source.dict(),
151 + )
152 +
153 + socfortress_lookup = await socfortress_ai_alert_lookup(
154 + lincense_key=(await get_license(session)).license_key,
155 + request=request,
156 + )
157 + return socfortress_lookup
158 +
159 +
160 +@threat_intel_socfortress_router.post(
161 + "/ai/wazuh-exclusion-rule",
162 + response_model=SocfortressAiWazuhExclusionRuleResponse,
163 + description="SocFortress Process Name Evaluation",
164 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
165 +)
166 +async def ai_wazuh_exclusion_rule_socfortress(
167 + request: CreateAlertRequestRoute,
168 + session: AsyncSession = Depends(get_db),
169 +):
170 + # Fetch alert details
171 + alert_details = await get_single_alert_details(CreateAlertRequest(index_name=request.index_name, alert_id=request.index_id))
172 +
173 + assert isinstance(alert_details, GenericAlertModel)
174 +
175 + request = SocfortressAiAlertRequest(
176 + integration="AI",
177 + alert_payload=alert_details._source.dict(),
178 + )
179 +
180 + socfortress_lookup = await socfortress_wazuh_exclusion_rule_lookup(
181 + lincense_key=(await get_license(session)).license_key,
182 + request=request,
183 + )
184 + return socfortress_lookup
backend/app/threat_intel/schema/socfortress.py
+73
@@ -1,7 +1,9 @@
1 import re
2 +from enum import Enum
3 from typing import List
4 from typing import Optional
5
6 +from fastapi import HTTPException
7 from pydantic import BaseModel
8 from pydantic import Field
9 from pydantic import validator
@@ -62,6 +64,77 @@ class SocfortressProcessNameAnalysisRequest(BaseModel):
64 return match.group() if match else v
65
66
67 +class SyslogType(str, Enum):
68 + WAZUH = "wazuh"
69 + # Add other valid syslog types here if needed
70 +
71 +
72 +class SocfortressAiAlertRequest(BaseModel):
73 + integration: str = Field(..., example="AI")
74 + alert_payload: dict = Field(..., example={"alert": "test"})
75 +
76 + @validator("integration")
77 + def check_integration(cls, v):
78 + if v != "AI":
79 + raise HTTPException(
80 + status_code=400,
81 + detail="Invalid integration. Only 'AI' is supported.",
82 + )
83 + return v
84 +
85 + @validator("alert_payload")
86 + def check_syslog_type(cls, v):
87 + if v.get("syslog_type") not in SyslogType.__members__.values():
88 + raise HTTPException(
89 + status_code=400,
90 + detail=f"Invalid syslog_type. Only {', '.join([e.value for e in SyslogType])} are supported.",
91 + )
92 + # Remove 'message' and 'full_log' fields if they exist
93 + v.pop("message", None)
94 + v.pop("full_log", None)
95 + v.pop("gl2_processing_error", None)
96 + v.pop("gl2_accounted_message_size", None)
97 + v.pop("gl2_source_input", None)
98 + v.pop("gl2_remote_ip", None)
99 + v.pop("gl2_message_id", None)
100 + v.pop("gl2_remote_port", None)
101 + return v
102 +
103 +
104 +class SocfortressAiAlertResponse(BaseModel):
105 + message: str
106 + success: bool
107 + analysis: str = Field(description="The analysis of the alert.")
108 + base64_decoded: Optional[str] = None
109 + confidence_score: float = Field(
110 + description="Confidence score for the response.",
111 + ge=0,
112 + le=1,
113 + )
114 + threat_indicators: Optional[str] = Field(
115 + default=None,
116 + description="The threat indicators that make the decoded payload potentially malicious.",
117 + )
118 +
119 + risk_evaluation: Optional[str] = Field(
120 + default=None,
121 + description="A conclusion indicating whether the content is `low`, `medium`, or `high` risk.",
122 + )
123 +
124 +
125 +class SocfortressAiWazuhExclusionRuleResponse(BaseModel):
126 + message: str
127 + success: bool
128 + wazuh_exclusion_rule: Optional[str] = Field(
129 + default=None,
130 + description="The rule that was excluded from the analysis in XML format.",
131 + )
132 + wazuh_exclusion_rule_justification: Optional[str] = Field(
133 + default=None,
134 + description="The justification for excluding the rule and the reason for selecting the field names that were selected to include within the exclusion rule.",
135 + )
136 +
137 +
138 class Path(BaseModel):
139 directory: str
140 percentage: float
backend/app/threat_intel/services/socfortress.py
+153
@@ -10,6 +10,9 @@ from app.connectors.utils import get_connector_info_from_db
10 from app.db.db_session import get_db_session
11 from app.threat_intel.schema.socfortress import IoCMapping
12 from app.threat_intel.schema.socfortress import IoCResponse
13 +from app.threat_intel.schema.socfortress import SocfortressAiAlertRequest
14 +from app.threat_intel.schema.socfortress import SocfortressAiAlertResponse
15 +from app.threat_intel.schema.socfortress import SocfortressAiWazuhExclusionRuleResponse
16 from app.threat_intel.schema.socfortress import (
17 SocfortressProcessNameAnalysisAPIResponse,
18 )
@@ -174,6 +177,60 @@ async def invoke_socfortress_process_name_api(
177 return response.json()
178
179
180 +async def invoke_socfortress_ai_alert_api(
181 + api_key: str,
182 + url: str,
183 + request: SocfortressAiAlertRequest,
184 + timeout: int = 60,
185 +) -> dict:
186 + """
187 + Invokes the Socfortress AI Alert API with the provided API key, URL, and request parameters.
188 +
189 + Args:
190 + api_key (str): The API key for authentication.
191 + url (str): The URL of the Socfortress Intel URL
192 + request (SocfortressAiAlertRequest): The request object containing the Process Name
193 +
194 + Returns:
195 + dict: The JSON response from the AI Alert API.
196 +
197 + Raises:
198 + httpx.HTTPStatusError: If the API request fails with a non-successful status code.
199 + """
200 + headers = {"module-version": "1.0", "x-api-key": api_key}
201 + query_params = {"license_key": api_key, "feature_name": "AI"}
202 + try:
203 + async with httpx.AsyncClient(timeout=timeout) as client:
204 + response = await client.post(url, params=query_params, json=request.dict(), headers=headers)
205 + response.raise_for_status() # Raise an exception for non-successful status codes
206 + return response.json()
207 + except httpx.HTTPStatusError as e:
208 + if e.response.status_code == 429:
209 + logger.error(f"Rate limit reached: {e.response.status_code} - {e.response.text}")
210 + raise HTTPException(
211 + status_code=429,
212 + detail="Rate limit reached for the month. Please try again next month.",
213 + )
214 + else:
215 + logger.error(f"HTTP error occurred: {e.response.status_code} - {e.response.text}")
216 + raise HTTPException(
217 + status_code=e.response.status_code,
218 + detail=f"HTTP error occurred: {e.response.status_code} - {e.response.text}",
219 + )
220 + except httpx.RequestError as e:
221 + logger.error(f"Request error occurred: {e}")
222 + raise HTTPException(
223 + status_code=500,
224 + detail=f"Request error occurred: {e}",
225 + )
226 + except Exception as e:
227 + logger.error(f"An unexpected error occurred: {e}")
228 + raise HTTPException(
229 + status_code=500,
230 + detail=f"An unexpected error occurred: {e}",
231 + )
232 +
233 +
234 async def get_ioc_response(
235 license_key: str,
236 request: SocfortressThreatIntelRequest,
@@ -200,6 +257,62 @@ async def get_ioc_response(
257 return IoCResponse(data=IoCMapping(**data), success=success, message=message)
258
259
260 +async def get_ai_alert_response(
261 + license_key: str,
262 + request: SocfortressAiAlertRequest,
263 +) -> SocfortressAiAlertResponse:
264 + """
265 + Retrieves IoC response from Socfortress Threat Intel API.
266 +
267 + Args:
268 + request (SocfortressAiAlertRequest): The request object containing the IoC data.
269 + session (AsyncSession): The async session object for making HTTP requests.
270 +
271 + Returns:
272 + SocfortressAiAlertResponse: The response object containing the IoC data and success status.
273 + """
274 + url = "https://ai.socfortress.co/analyze-alert"
275 +
276 + response_data = await invoke_socfortress_ai_alert_api(license_key, url, request)
277 +
278 + # If message is `Forbidden`, raise an HTTPException
279 + if response_data.get("message") == "Forbidden":
280 + raise HTTPException(
281 + status_code=403,
282 + detail="Forbidden access to the Socfortress AI Alert API",
283 + )
284 +
285 + return SocfortressAiAlertResponse(**response_data)
286 +
287 +
288 +async def get_wazuh_exclusion_rule_response(
289 + license_key: str,
290 + request: SocfortressAiAlertRequest,
291 +) -> SocfortressAiWazuhExclusionRuleResponse:
292 + """
293 + Retrieves IoC response from Socfortress Threat Intel API.
294 +
295 + Args:
296 + request (SocfortressAiAlertRequest): The request object containing the IoC data.
297 + session (AsyncSession): The async session object for making HTTP requests.
298 +
299 + Returns:
300 + SocfortressAiWazuhExclusionRuleResponse: The response object containing the IoC data and success status.
301 + """
302 + url = "https://ai.socfortress.co/wazuh-exclusion-rule"
303 +
304 + response_data = await invoke_socfortress_ai_alert_api(license_key, url, request)
305 +
306 + # If message is `Forbidden`, raise an HTTPException
307 + if response_data.get("message") == "Forbidden":
308 + raise HTTPException(
309 + status_code=403,
310 + detail="Forbidden access to the Socfortress AI Alert API",
311 + )
312 +
313 + return SocfortressAiWazuhExclusionRuleResponse(**response_data)
314 +
315 +
316 async def get_process_analysis_response(
317 license_key: str,
318 request: SocfortressProcessNameAnalysisRequest,
@@ -275,3 +388,43 @@ async def socfortress_process_analysis_lookup(
388 request=request,
389 session=session,
390 )
391 +
392 +
393 +async def socfortress_ai_alert_lookup(
394 + lincense_key: str,
395 + request: SocfortressAiAlertRequest,
396 +) -> SocfortressAiAlertResponse:
397 + """
398 + Performs a AI alert lookup using the Socfortress service.
399 +
400 + Args:
401 + request (SocfortressAiAlertRequest): The request object containing the IoC to lookup.
402 + session (AsyncSession): The async session object for making HTTP requests.
403 +
404 + Returns:
405 + IoCResponse: The response object containing the threat intelligence information.
406 + """
407 + return await get_ai_alert_response(
408 + license_key=lincense_key,
409 + request=request,
410 + )
411 +
412 +
413 +async def socfortress_wazuh_exclusion_rule_lookup(
414 + lincense_key: str,
415 + request: SocfortressAiAlertRequest,
416 +) -> SocfortressAiWazuhExclusionRuleResponse:
417 + """
418 + Performs a AI alert lookup using the Socfortress service.
419 +
420 + Args:
421 + request (SocfortressAiAlertRequest): The request object containing the IoC to lookup.
422 + session (AsyncSession): The async session object for making HTTP requests.
423 +
424 + Returns:
425 + IoCResponse: The response object containing the threat intelligence information.
426 + """
427 + return await get_wazuh_exclusion_rule_response(
428 + license_key=lincense_key,
429 + request=request,
430 + )
frontend/package-lock.json
+299 -356
@@ -32,7 +32,7 @@
32 "nanoid": "^5.0.8",
33 "password-validator": "^5.3.0",
34 "pinia": "^2.2.6",
35 - "pinia-plugin-persistedstate": "^4.1.2",
35 + "pinia-plugin-persistedstate": "^4.1.3",
36 "secure-ls": "^2.0.0",
37 "shiki": "^1.22.2",
38 "validator": "^13.12.0",
@@ -47,7 +47,7 @@
47 "vuedraggable": "^4.1.0"
48 },
49 "devDependencies": {
50 - "@antfu/eslint-config": "^3.8.0",
50 + "@antfu/eslint-config": "^3.9.1",
51 "@clack/prompts": "^0.7.0",
52 "@iconify/vue": "^4.1.2",
53 "@tsconfig/node20": "^20.1.4",
@@ -58,22 +58,22 @@
58 "@types/lodash": "^4.17.13",
59 "@types/node": "^22.9.0",
60 "@types/validator": "^13.12.2",
61 - "@vitejs/plugin-vue": "^5.1.4",
62 - "@vitejs/plugin-vue-jsx": "^4.0.1",
61 + "@vitejs/plugin-vue": "^5.2.0",
62 + "@vitejs/plugin-vue-jsx": "^4.1.0",
63 "@vue/test-utils": "^2.4.6",
64 - "@vue/tsconfig": "^0.5.1",
64 + "@vue/tsconfig": "^0.6.0",
65 "autoprefixer": "^10.4.20",
66 - "cypress": "^13.15.1",
66 + "cypress": "^13.15.2",
67 "depcheck": "^1.4.7",
68 "eslint": "^9.14.0",
69 "flourite": "^1.3.0",
70 "fs-extra": "^11.2.0",
71 "jsdom": "^25.0.1",
72 "npm-run-all2": "^7.0.1",
73 - "postcss": "^8.4.47",
73 + "postcss": "^8.4.49",
74 "prettier": "^3.3.3",
75 "prettier-plugin-tailwindcss": "^0.6.8",
76 - "sass": "^1.80.6",
76 + "sass": "^1.80.7",
77 "start-server-and-test": "^2.0.8",
78 "tailwind-config-viewer": "^2.0.4",
79 "tailwindcss": "^3.4.14",
@@ -81,18 +81,18 @@
81 "type-fest": "^4.26.1",
82 "typescript": "~5.6.3",
83 "unplugin-vue-components": "^0.27.4",
84 - "vite": "^5.4.10",
84 + "vite": "^5.4.11",
85 "vite-bundle-visualizer": "^1.2.1",
86 - "vite-plugin-vue-devtools": "^7.6.3",
86 + "vite-plugin-vue-devtools": "^7.6.4",
87 "vite-svg-loader": "^5.1.0",
88 - "vitest": "^2.1.4",
88 + "vitest": "^2.1.5",
89 "vue-tsc": "^2.1.10"
90 },
91 "engines": {
92 "node": ">=18.0.0"
93 },
94 "optionalDependencies": {
95 - "@rollup/rollup-linux-x64-gnu": "^4.24.4"
95 + "@rollup/rollup-linux-x64-gnu": "^4.26.0"
96 }
97 },
98 "node_modules/@ajoelp/json-to-formdata": {
@@ -127,38 +127,38 @@
127 }
128 },
129 "node_modules/@antfu/eslint-config": {
130 - "version": "3.8.0",
131 - "resolved": "https://registry.npmjs.org/@antfu/eslint-config/-/eslint-config-3.8.0.tgz",
132 - "integrity": "sha512-O5QSufPHpKTm0wk1OQ5c2mOZVzCqYV3hIDrt5zt+cOWqiG8YXLPkSOD4fFwjomATtOuUbcLUwkcgY5dErM7aIw==",
130 + "version": "3.9.1",
131 + "resolved": "https://registry.npmjs.org/@antfu/eslint-config/-/eslint-config-3.9.1.tgz",
132 + "integrity": "sha512-a/xubkbJ9i6U6jX5ZUB3GeXahhorpMWgDRwdga297ilmadcJFrepBRjGf8SnA+RlPrVRI4cqPdQeQZZKR+Mjiw==",
133 "dev": true,
134 "dependencies": {
135 "@antfu/install-pkg": "^0.4.1",
136 "@clack/prompts": "^0.7.0",
137 - "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
138 - "@eslint/markdown": "^6.2.0",
139 - "@stylistic/eslint-plugin": "^2.9.0",
140 - "@typescript-eslint/eslint-plugin": "^8.9.0",
141 - "@typescript-eslint/parser": "^8.9.0",
142 - "@vitest/eslint-plugin": "^1.1.7",
137 + "@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
138 + "@eslint/markdown": "^6.2.1",
139 + "@stylistic/eslint-plugin": "^2.10.1",
140 + "@typescript-eslint/eslint-plugin": "^8.14.0",
141 + "@typescript-eslint/parser": "^8.14.0",
142 + "@vitest/eslint-plugin": "^1.1.10",
143 "eslint-config-flat-gitignore": "^0.3.0",
144 "eslint-flat-config-utils": "^0.4.0",
145 "eslint-merge-processors": "^0.1.0",
146 "eslint-plugin-antfu": "^2.7.0",
147 "eslint-plugin-command": "^0.2.6",
148 - "eslint-plugin-import-x": "^4.3.1",
149 - "eslint-plugin-jsdoc": "^50.4.1",
150 - "eslint-plugin-jsonc": "^2.16.0",
151 - "eslint-plugin-n": "^17.11.1",
148 + "eslint-plugin-import-x": "^4.4.2",
149 + "eslint-plugin-jsdoc": "^50.5.0",
150 + "eslint-plugin-jsonc": "^2.18.1",
151 + "eslint-plugin-n": "^17.13.1",
152 "eslint-plugin-no-only-tests": "^3.3.0",
153 - "eslint-plugin-perfectionist": "^3.9.0",
153 + "eslint-plugin-perfectionist": "^3.9.1",
154 "eslint-plugin-regexp": "^2.6.0",
155 "eslint-plugin-toml": "^0.11.1",
156 "eslint-plugin-unicorn": "^56.0.0",
157 "eslint-plugin-unused-imports": "^4.1.4",
158 - "eslint-plugin-vue": "^9.29.0",
159 - "eslint-plugin-yml": "^1.14.0",
158 + "eslint-plugin-vue": "^9.31.0",
159 + "eslint-plugin-yml": "^1.15.0",
160 "eslint-processor-vue-blocks": "^0.1.2",
161 - "globals": "^15.11.0",
161 + "globals": "^15.12.0",
162 "jsonc-eslint-parser": "^2.4.0",
163 "local-pkg": "^0.5.0",
164 "parse-gitignore": "^2.0.0",
@@ -232,6 +232,18 @@
232 }
233 }
234 },
235 + "node_modules/@antfu/eslint-config/node_modules/globals": {
236 + "version": "15.12.0",
237 + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz",
238 + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==",
239 + "dev": true,
240 + "engines": {
241 + "node": ">=18"
242 + },
243 + "funding": {
244 + "url": "https://github.com/sponsors/sindresorhus"
245 + }
246 + },
247 "node_modules/@antfu/install-pkg": {
248 "version": "0.4.1",
249 "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz",
@@ -742,7 +754,6 @@
754 },
755 "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
756 "version": "1.3.0",
745 - "extraneous": true,
757 "inBundle": true,
758 "license": "MIT",
759 "engines": {
@@ -779,9 +790,9 @@
790 }
791 },
792 "node_modules/@cypress/request": {
782 - "version": "3.0.5",
783 - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
784 - "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
793 + "version": "3.0.6",
794 + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz",
795 + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==",
796 "dev": true,
797 "dependencies": {
798 "aws-sign2": "~0.7.0",
@@ -799,7 +810,7 @@
810 "performance-now": "^2.1.0",
811 "qs": "6.13.0",
812 "safe-buffer": "^5.1.2",
802 - "tough-cookie": "^4.1.3",
813 + "tough-cookie": "^5.0.0",
814 "tunnel-agent": "^0.6.0",
815 "uuid": "^8.3.2"
816 },
@@ -1230,9 +1241,9 @@
1241 }
1242 },
1243 "node_modules/@eslint-community/eslint-plugin-eslint-comments": {
1233 - "version": "4.4.0",
1234 - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.0.tgz",
1235 - "integrity": "sha512-yljsWl5Qv3IkIRmJ38h3NrHXFCm4EUl55M8doGTF6hvzvFF8kRpextgSrg2dwHev9lzBZyafCr9RelGIyQm6fw==",
1244 + "version": "4.4.1",
1245 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.1.tgz",
1246 + "integrity": "sha512-lb/Z/MzbTf7CaVYM9WCFNQZ4L1yi3ev2fsFPF99h31ljhSEyUoyEsKsNWiU+qD1glbYTDJdqgyaLKtyTkkqtuQ==",
1247 "dev": true,
1248 "dependencies": {
1249 "escape-string-regexp": "^4.0.0",
@@ -1249,16 +1260,19 @@
1260 }
1261 },
1262 "node_modules/@eslint-community/eslint-utils": {
1252 - "version": "4.4.0",
1253 - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
1254 - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
1263 + "version": "4.4.1",
1264 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
1265 + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
1266 "dev": true,
1267 "dependencies": {
1257 - "eslint-visitor-keys": "^3.3.0"
1268 + "eslint-visitor-keys": "^3.4.3"
1269 },
1270 "engines": {
1271 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1272 },
1273 + "funding": {
1274 + "url": "https://opencollective.com/eslint"
1275 + },
1276 "peerDependencies": {
1277 "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
1278 }
@@ -2366,9 +2380,9 @@
2380 ]
2381 },
2382 "node_modules/@rollup/rollup-linux-x64-gnu": {
2369 - "version": "4.24.4",
2370 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz",
2371 - "integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==",
2383 + "version": "4.26.0",
2384 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.26.0.tgz",
2385 + "integrity": "sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==",
2386 "cpu": [
2387 "x64"
2388 ],
@@ -2517,14 +2531,14 @@
2531 }
2532 },
2533 "node_modules/@stylistic/eslint-plugin": {
2520 - "version": "2.9.0",
2521 - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.9.0.tgz",
2522 - "integrity": "sha512-OrDyFAYjBT61122MIY1a3SfEgy3YCMgt2vL4eoPmvTwDBwyQhAXurxNQznlRD/jESNfYWfID8Ej+31LljvF7Xg==",
2534 + "version": "2.10.1",
2535 + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.10.1.tgz",
2536 + "integrity": "sha512-U+4yzNXElTf9q0kEfnloI9XbOyD4cnEQCxjUI94q0+W++0GAEQvJ/slwEj9lwjDHfGADRSr+Tco/z0XJvmDfCQ==",
2537 "dev": true,
2538 "dependencies": {
2525 - "@typescript-eslint/utils": "^8.8.0",
2526 - "eslint-visitor-keys": "^4.1.0",
2527 - "espree": "^10.2.0",
2539 + "@typescript-eslint/utils": "^8.12.2",
2540 + "eslint-visitor-keys": "^4.2.0",
2541 + "espree": "^10.3.0",
2542 "estraverse": "^5.3.0",
2543 "picomatch": "^4.0.2"
2544 },
@@ -2740,16 +2754,16 @@
2754 }
2755 },
2756 "node_modules/@typescript-eslint/eslint-plugin": {
2743 - "version": "8.11.0",
2744 - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz",
2745 - "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==",
2757 + "version": "8.14.0",
2758 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz",
2759 + "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==",
2760 "dev": true,
2761 "dependencies": {
2762 "@eslint-community/regexpp": "^4.10.0",
2749 - "@typescript-eslint/scope-manager": "8.11.0",
2750 - "@typescript-eslint/type-utils": "8.11.0",
2751 - "@typescript-eslint/utils": "8.11.0",
2752 - "@typescript-eslint/visitor-keys": "8.11.0",
2763 + "@typescript-eslint/scope-manager": "8.14.0",
2764 + "@typescript-eslint/type-utils": "8.14.0",
2765 + "@typescript-eslint/utils": "8.14.0",
2766 + "@typescript-eslint/visitor-keys": "8.14.0",
2767 "graphemer": "^1.4.0",
2768 "ignore": "^5.3.1",
2769 "natural-compare": "^1.4.0",
@@ -2773,15 +2787,15 @@
2787 }
2788 },
2789 "node_modules/@typescript-eslint/parser": {
2776 - "version": "8.11.0",
2777 - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz",
2778 - "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==",
2790 + "version": "8.14.0",
2791 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz",
2792 + "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==",
2793 "dev": true,
2794 "dependencies": {
2781 - "@typescript-eslint/scope-manager": "8.11.0",
2782 - "@typescript-eslint/types": "8.11.0",
2783 - "@typescript-eslint/typescript-estree": "8.11.0",
2784 - "@typescript-eslint/visitor-keys": "8.11.0",
2795 + "@typescript-eslint/scope-manager": "8.14.0",
2796 + "@typescript-eslint/types": "8.14.0",
2797 + "@typescript-eslint/typescript-estree": "8.14.0",
2798 + "@typescript-eslint/visitor-keys": "8.14.0",
2799 "debug": "^4.3.4"
2800 },
2801 "engines": {
@@ -2801,13 +2815,13 @@
2815 }
2816 },
2817 "node_modules/@typescript-eslint/scope-manager": {
2804 - "version": "8.11.0",
2805 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz",
2806 - "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==",
2818 + "version": "8.14.0",
2819 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz",
2820 + "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==",
2821 "dev": true,
2822 "dependencies": {
2809 - "@typescript-eslint/types": "8.11.0",
2810 - "@typescript-eslint/visitor-keys": "8.11.0"
2823 + "@typescript-eslint/types": "8.14.0",
2824 + "@typescript-eslint/visitor-keys": "8.14.0"
2825 },
2826 "engines": {
2827 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2818,13 +2832,13 @@
2832 }
2833 },
2834 "node_modules/@typescript-eslint/type-utils": {
2821 - "version": "8.11.0",
2822 - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz",
2823 - "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==",
2835 + "version": "8.14.0",
2836 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz",
2837 + "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==",
2838 "dev": true,
2839 "dependencies": {
2826 - "@typescript-eslint/typescript-estree": "8.11.0",
2827 - "@typescript-eslint/utils": "8.11.0",
2840 + "@typescript-eslint/typescript-estree": "8.14.0",
2841 + "@typescript-eslint/utils": "8.14.0",
2842 "debug": "^4.3.4",
2843 "ts-api-utils": "^1.3.0"
2844 },
@@ -2842,9 +2856,9 @@
2856 }
2857 },
2858 "node_modules/@typescript-eslint/types": {
2845 - "version": "8.11.0",
2846 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz",
2847 - "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==",
2859 + "version": "8.14.0",
2860 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz",
2861 + "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==",
2862 "dev": true,
2863 "engines": {
2864 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2855,13 +2869,13 @@
2869 }
2870 },
2871 "node_modules/@typescript-eslint/typescript-estree": {
2858 - "version": "8.13.0",
2859 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.13.0.tgz",
2860 - "integrity": "sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==",
2872 + "version": "8.14.0",
2873 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz",
2874 + "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==",
2875 "dev": true,
2876 "dependencies": {
2863 - "@typescript-eslint/types": "8.13.0",
2864 - "@typescript-eslint/visitor-keys": "8.13.0",
2877 + "@typescript-eslint/types": "8.14.0",
2878 + "@typescript-eslint/visitor-keys": "8.14.0",
2879 "debug": "^4.3.4",
2880 "fast-glob": "^3.3.2",
2881 "is-glob": "^4.0.3",
@@ -2882,58 +2896,16 @@
2896 }
2897 }
2898 },
2885 - "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": {
2886 - "version": "8.13.0",
2887 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz",
2888 - "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==",
2889 - "dev": true,
2890 - "engines": {
2891 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2892 - },
2893 - "funding": {
2894 - "type": "opencollective",
2895 - "url": "https://opencollective.com/typescript-eslint"
2896 - }
2897 - },
2898 - "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": {
2899 - "version": "8.13.0",
2900 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz",
2901 - "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==",
2902 - "dev": true,
2903 - "dependencies": {
2904 - "@typescript-eslint/types": "8.13.0",
2905 - "eslint-visitor-keys": "^3.4.3"
2906 - },
2907 - "engines": {
2908 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2909 - },
2910 - "funding": {
2911 - "type": "opencollective",
2912 - "url": "https://opencollective.com/typescript-eslint"
2913 - }
2914 - },
2915 - "node_modules/@typescript-eslint/typescript-estree/node_modules/eslint-visitor-keys": {
2916 - "version": "3.4.3",
2917 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
2918 - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
2919 - "dev": true,
2920 - "engines": {
2921 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2922 - },
2923 - "funding": {
2924 - "url": "https://opencollective.com/eslint"
2925 - }
2926 - },
2899 "node_modules/@typescript-eslint/utils": {
2928 - "version": "8.11.0",
2929 - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz",
2930 - "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==",
2900 + "version": "8.14.0",
2901 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz",
2902 + "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==",
2903 "dev": true,
2904 "dependencies": {
2905 "@eslint-community/eslint-utils": "^4.4.0",
2934 - "@typescript-eslint/scope-manager": "8.11.0",
2935 - "@typescript-eslint/types": "8.11.0",
2936 - "@typescript-eslint/typescript-estree": "8.11.0"
2906 + "@typescript-eslint/scope-manager": "8.14.0",
2907 + "@typescript-eslint/types": "8.14.0",
2908 + "@typescript-eslint/typescript-estree": "8.14.0"
2909 },
2910 "engines": {
2911 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2947,12 +2919,12 @@
2919 }
2920 },
2921 "node_modules/@typescript-eslint/visitor-keys": {
2950 - "version": "8.11.0",
2951 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz",
2952 - "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==",
2922 + "version": "8.14.0",
2923 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz",
2924 + "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==",
2925 "dev": true,
2926 "dependencies": {
2955 - "@typescript-eslint/types": "8.11.0",
2927 + "@typescript-eslint/types": "8.14.0",
2928 "eslint-visitor-keys": "^3.4.3"
2929 },
2930 "engines": {
@@ -3354,9 +3326,9 @@
3326 }
3327 },
3328 "node_modules/@vitejs/plugin-vue": {
3357 - "version": "5.1.4",
3358 - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz",
3359 - "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
3329 + "version": "5.2.0",
3330 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.0.tgz",
3331 + "integrity": "sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==",
3332 "dev": true,
3333 "engines": {
3334 "node": "^18.0.0 || >=20.0.0"
@@ -3367,14 +3339,14 @@
3339 }
3340 },
3341 "node_modules/@vitejs/plugin-vue-jsx": {
3370 - "version": "4.0.1",
3371 - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-4.0.1.tgz",
3372 - "integrity": "sha512-7mg9HFGnFHMEwCdB6AY83cVK4A6sCqnrjFYF4WIlebYAQVVJ/sC/CiTruVdrRlhrFoeZ8rlMxY9wYpPTIRhhAg==",
3342 + "version": "4.1.0",
3343 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-4.1.0.tgz",
3344 + "integrity": "sha512-KuRejz7KAFvhXDzOudlaS2IyygAwoAEEMtHAdcRSy/8cA5iKH043Qudcz48zsC0M0vvN5iKwIwNMuWbBYn6/Yg==",
3345 "dev": true,
3346 "dependencies": {
3375 - "@babel/core": "^7.24.7",
3376 - "@babel/plugin-transform-typescript": "^7.24.7",
3377 - "@vue/babel-plugin-jsx": "^1.2.2"
3347 + "@babel/core": "^7.26.0",
3348 + "@babel/plugin-transform-typescript": "^7.25.9",
3349 + "@vue/babel-plugin-jsx": "^1.2.5"
3350 },
3351 "engines": {
3352 "node": "^18.0.0 || >=20.0.0"
@@ -3385,9 +3357,9 @@
3357 }
3358 },
3359 "node_modules/@vitest/eslint-plugin": {
3388 - "version": "1.1.7",
3389 - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.7.tgz",
3390 - "integrity": "sha512-pTWGW3y6lH2ukCuuffpan6kFxG6nIuoesbhMiQxskyQMRcCN5t9SXsKrNHvEw3p8wcCsgJoRqFZVkOTn6TjclA==",
3360 + "version": "1.1.10",
3361 + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.10.tgz",
3362 + "integrity": "sha512-uScH5Kz5v32vvtQYB2iodpoPg2mGASK+VKpjlc2IUgE0+16uZKqVKi2vQxjxJ6sMCQLBs4xhBFZlmZBszsmfKQ==",
3363 "dev": true,
3364 "peerDependencies": {
3365 "@typescript-eslint/utils": ">= 8.0",
@@ -3405,13 +3377,13 @@
3377 }
3378 },
3379 "node_modules/@vitest/expect": {
3408 - "version": "2.1.4",
3409 - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz",
3410 - "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==",
3380 + "version": "2.1.5",
3381 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz",
3382 + "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==",
3383 "dev": true,
3384 "dependencies": {
3413 - "@vitest/spy": "2.1.4",
3414 - "@vitest/utils": "2.1.4",
3385 + "@vitest/spy": "2.1.5",
3386 + "@vitest/utils": "2.1.5",
3387 "chai": "^5.1.2",
3388 "tinyrainbow": "^1.2.0"
3389 },
@@ -3420,12 +3392,12 @@
3392 }
3393 },
3394 "node_modules/@vitest/mocker": {
3423 - "version": "2.1.4",
3424 - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz",
3425 - "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==",
3395 + "version": "2.1.5",
3396 + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz",
3397 + "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==",
3398 "dev": true,
3399 "dependencies": {
3428 - "@vitest/spy": "2.1.4",
3400 + "@vitest/spy": "2.1.5",
3401 "estree-walker": "^3.0.3",
3402 "magic-string": "^0.30.12"
3403 },
@@ -3455,9 +3427,9 @@
3427 }
3428 },
3429 "node_modules/@vitest/pretty-format": {
3458 - "version": "2.1.4",
3459 - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz",
3460 - "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==",
3430 + "version": "2.1.5",
3431 + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz",
3432 + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==",
3433 "dev": true,
3434 "dependencies": {
3435 "tinyrainbow": "^1.2.0"
@@ -3467,12 +3439,12 @@
3439 }
3440 },
3441 "node_modules/@vitest/runner": {
3470 - "version": "2.1.4",
3471 - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz",
3472 - "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==",
3442 + "version": "2.1.5",
3443 + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz",
3444 + "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==",
3445 "dev": true,
3446 "dependencies": {
3475 - "@vitest/utils": "2.1.4",
3447 + "@vitest/utils": "2.1.5",
3448 "pathe": "^1.1.2"
3449 },
3450 "funding": {
@@ -3480,12 +3452,12 @@
3452 }
3453 },
3454 "node_modules/@vitest/snapshot": {
3483 - "version": "2.1.4",
3484 - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz",
3485 - "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==",
3455 + "version": "2.1.5",
3456 + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz",
3457 + "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==",
3458 "dev": true,
3459 "dependencies": {
3488 - "@vitest/pretty-format": "2.1.4",
3460 + "@vitest/pretty-format": "2.1.5",
3461 "magic-string": "^0.30.12",
3462 "pathe": "^1.1.2"
3463 },
@@ -3494,9 +3466,9 @@
3466 }
3467 },
3468 "node_modules/@vitest/spy": {
3497 - "version": "2.1.4",
3498 - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz",
3499 - "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==",
3469 + "version": "2.1.5",
3470 + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz",
3471 + "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==",
3472 "dev": true,
3473 "dependencies": {
3474 "tinyspy": "^3.0.2"
@@ -3506,12 +3478,12 @@
3478 }
3479 },
3480 "node_modules/@vitest/utils": {
3509 - "version": "2.1.4",
3510 - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz",
3511 - "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==",
3481 + "version": "2.1.5",
3482 + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz",
3483 + "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==",
3484 "dev": true,
3485 "dependencies": {
3514 - "@vitest/pretty-format": "2.1.4",
3486 + "@vitest/pretty-format": "2.1.5",
3487 "loupe": "^3.1.2",
3488 "tinyrainbow": "^1.2.0"
3489 },
@@ -3655,13 +3627,13 @@
3627 "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
3628 },
3629 "node_modules/@vue/devtools-core": {
3658 - "version": "7.6.3",
3659 - "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.6.3.tgz",
3660 - "integrity": "sha512-C7FOuh3Z+EmXXzDU9eRjHQL7zW7/CFovM6yCNNpUb+zXxhrn4fiqTum+a3gNau9DuzYfEtQXwZ9F7MeK0JKYVw==",
3630 + "version": "7.6.4",
3631 + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.6.4.tgz",
3632 + "integrity": "sha512-blSwGVYpb7b5TALMjjoBiAl5imuBF7WEOAtaJaBMNikR8SQkm6mkUt4YlIKh9874/qoimwmpDOm+GHBZ4Y5m+g==",
3633 "dev": true,
3634 "dependencies": {
3663 - "@vue/devtools-kit": "^7.6.3",
3664 - "@vue/devtools-shared": "^7.6.3",
3635 + "@vue/devtools-kit": "^7.6.4",
3636 + "@vue/devtools-shared": "^7.6.4",
3637 "mitt": "^3.0.1",
3638 "nanoid": "^3.3.4",
3639 "pathe": "^1.1.2",
@@ -3690,12 +3662,12 @@
3662 }
3663 },
3664 "node_modules/@vue/devtools-kit": {
3693 - "version": "7.6.3",
3694 - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.6.3.tgz",
3695 - "integrity": "sha512-ETsFc8GlOp04rSFN79tB2TpVloWfsSx9BoCSElV3w3CaJTSBfz42KsIi5Ka+dNTJs1jY7QVLTDeoBmUGgA9h2A==",
3665 + "version": "7.6.4",
3666 + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.6.4.tgz",
3667 + "integrity": "sha512-Zs86qIXXM9icU0PiGY09PQCle4TI750IPLmAJzW5Kf9n9t5HzSYf6Rz6fyzSwmfMPiR51SUKJh9sXVZu78h2QA==",
3668 "dev": true,
3669 "dependencies": {
3698 - "@vue/devtools-shared": "^7.6.3",
3670 + "@vue/devtools-shared": "^7.6.4",
3671 "birpc": "^0.2.19",
3672 "hookable": "^5.5.3",
3673 "mitt": "^3.0.1",
@@ -3705,9 +3677,9 @@
3677 }
3678 },
3679 "node_modules/@vue/devtools-shared": {
3708 - "version": "7.6.3",
3709 - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.6.3.tgz",
3710 - "integrity": "sha512-wJW5QF27i16+sNQIaes8QoEZg1eqEgF83GkiPUlEQe9k7ZoHXHV7PRrnrxOKem42sIHPU813J2V/ZK1uqTJe6g==",
3680 + "version": "7.6.4",
3681 + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.6.4.tgz",
3682 + "integrity": "sha512-nD6CUvBEel+y7zpyorjiUocy0nh77DThZJ0k1GRnJeOmY3ATq2fWijEp7wk37gb023Cb0R396uYh5qMSBQ5WFg==",
3683 "dev": true,
3684 "dependencies": {
3685 "rfdc": "^1.4.1"
@@ -3793,10 +3765,22 @@
3765 }
3766 },
3767 "node_modules/@vue/tsconfig": {
3796 - "version": "0.5.1",
3797 - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz",
3798 - "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
3799 - "dev": true
3768 + "version": "0.6.0",
3769 + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.6.0.tgz",
3770 + "integrity": "sha512-MHXNd6lzugsEHvuA6l1GqrF5jROqUon8sP/HInLPnthJiYvB0VvpHMywg7em1dBZfFZNBSkR68qH37zOdRHmCw==",
3771 + "dev": true,
3772 + "peerDependencies": {
3773 + "typescript": "5.x",
3774 + "vue": "^3.3.0"
3775 + },
3776 + "peerDependenciesMeta": {
3777 + "typescript": {
3778 + "optional": true
3779 + },
3780 + "vue": {
3781 + "optional": true
3782 + }
3783 + }
3784 },
3785 "node_modules/@vueuse/core": {
3786 "version": "11.2.0",
@@ -5236,13 +5220,13 @@
5220 "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
5221 },
5222 "node_modules/cypress": {
5239 - "version": "13.15.1",
5240 - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.1.tgz",
5241 - "integrity": "sha512-DwUFiKXo4lef9kA0M4iEhixFqoqp2hw8igr0lTqafRb9qtU3X0XGxKbkSYsUFdkrAkphc7MPDxoNPhk5pj9PVg==",
5223 + "version": "13.15.2",
5224 + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz",
5225 + "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==",
5226 "dev": true,
5227 "hasInstallScript": true,
5228 "dependencies": {
5245 - "@cypress/request": "^3.0.4",
5229 + "@cypress/request": "^3.0.6",
5230 "@cypress/xvfb": "^1.2.4",
5231 "@types/sinonjs__fake-timers": "8.1.1",
5232 "@types/sizzle": "^2.3.2",
@@ -5253,6 +5237,7 @@
5237 "cachedir": "^2.3.0",
5238 "chalk": "^4.1.0",
5239 "check-more-types": "^2.24.0",
5240 + "ci-info": "^4.0.0",
5241 "cli-cursor": "^3.1.0",
5242 "cli-table3": "~0.6.1",
5243 "commander": "^6.2.1",
@@ -5267,7 +5252,6 @@
5252 "figures": "^3.2.0",
5253 "fs-extra": "^9.1.0",
5254 "getos": "^3.2.1",
5270 - "is-ci": "^3.0.1",
5255 "is-installed-globally": "~0.4.0",
5256 "lazy-ass": "^1.6.0",
5257 "listr2": "^3.8.3",
@@ -6158,6 +6142,27 @@
6142 "ms": "^2.1.1"
6143 }
6144 },
6145 + "node_modules/eslint-json-compat-utils": {
6146 + "version": "0.2.1",
6147 + "resolved": "https://registry.npmjs.org/eslint-json-compat-utils/-/eslint-json-compat-utils-0.2.1.tgz",
6148 + "integrity": "sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==",
6149 + "dev": true,
6150 + "dependencies": {
6151 + "esquery": "^1.6.0"
6152 + },
6153 + "engines": {
6154 + "node": ">=12"
6155 + },
6156 + "peerDependencies": {
6157 + "eslint": "*",
6158 + "jsonc-eslint-parser": "^2.4.0"
6159 + },
6160 + "peerDependenciesMeta": {
6161 + "@eslint/json": {
6162 + "optional": true
6163 + }
6164 + }
6165 + },
6166 "node_modules/eslint-merge-processors": {
6167 "version": "0.1.0",
6168 "resolved": "https://registry.npmjs.org/eslint-merge-processors/-/eslint-merge-processors-0.1.0.tgz",
@@ -6222,9 +6227,9 @@
6227 }
6228 },
6229 "node_modules/eslint-plugin-import-x": {
6225 - "version": "4.3.1",
6226 - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.3.1.tgz",
6227 - "integrity": "sha512-5TriWkXulDl486XnYYRgsL+VQoS/7mhN/2ci02iLCuL7gdhbiWxnsuL/NTcaKY9fpMgsMFjWZBtIGW7pb+RX0g==",
6230 + "version": "4.4.2",
6231 + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.4.2.tgz",
6232 + "integrity": "sha512-mDRXPSLQ0UQZQw91QdG4/qZT6hgeW2MJTczAbgPseUZuPEtIjjdPOolXroRkulnOn3fzj6gNgvk+wchMJiHElg==",
6233 "dev": true,
6234 "dependencies": {
6235 "@typescript-eslint/utils": "^8.1.0",
@@ -6246,15 +6251,15 @@
6251 }
6252 },
6253 "node_modules/eslint-plugin-import-x/node_modules/tslib": {
6249 - "version": "2.8.0",
6250 - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
6251 - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
6254 + "version": "2.8.1",
6255 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
6256 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
6257 "dev": true
6258 },
6259 "node_modules/eslint-plugin-jsdoc": {
6255 - "version": "50.4.3",
6256 - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.4.3.tgz",
6257 - "integrity": "sha512-uWtwFxGRv6B8sU63HZM5dAGDhgsatb+LONwmILZJhdRALLOkCX2HFZhdL/Kw2ls8SQMAVEfK+LmnEfxInRN8HA==",
6260 + "version": "50.5.0",
6261 + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.5.0.tgz",
6262 + "integrity": "sha512-xTkshfZrUbiSHXBwZ/9d5ulZ2OcHXxSvm/NPo494H/hadLRJwOq5PMV0EUpMqsb9V+kQo+9BAgi6Z7aJtdBp2A==",
6263 "dev": true,
6264 "dependencies": {
6265 "@es-joy/jsdoccomment": "~0.49.0",
@@ -6307,19 +6312,20 @@
6312 }
6313 },
6314 "node_modules/eslint-plugin-jsdoc/node_modules/tslib": {
6310 - "version": "2.8.0",
6311 - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
6312 - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
6315 + "version": "2.8.1",
6316 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
6317 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
6318 "dev": true
6319 },
6320 "node_modules/eslint-plugin-jsonc": {
6316 - "version": "2.16.0",
6317 - "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz",
6318 - "integrity": "sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==",
6321 + "version": "2.18.1",
6322 + "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.18.1.tgz",
6323 + "integrity": "sha512-6qY8zDpxOwPQNcr8eZ+RxwGX6IPHws5/Qef7aBEjER8rB9+UMB6zQWVIVcbP7xzFmEMHAesNFPe/sIlU4c78dg==",
6324 "dev": true,
6325 "dependencies": {
6326 "@eslint-community/eslint-utils": "^4.2.0",
6322 - "eslint-compat-utils": "^0.5.0",
6327 + "eslint-compat-utils": "^0.6.0",
6328 + "eslint-json-compat-utils": "^0.2.1",
6329 "espree": "^9.6.1",
6330 "graphemer": "^1.4.0",
6331 "jsonc-eslint-parser": "^2.0.4",
@@ -6336,6 +6342,21 @@
6342 "eslint": ">=6.0.0"
6343 }
6344 },
6345 + "node_modules/eslint-plugin-jsonc/node_modules/eslint-compat-utils": {
6346 + "version": "0.6.0",
6347 + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.0.tgz",
6348 + "integrity": "sha512-1vVBdI/HLS6HTHVQCJGlN+LOF0w1Rs/WB9se23mQr84cRM0iMM8PulMFFhQdQ1BvS0cGwjpis4xziI91Rk0l6g==",
6349 + "dev": true,
6350 + "dependencies": {
6351 + "semver": "^7.5.4"
6352 + },
6353 + "engines": {
6354 + "node": ">=12"
6355 + },
6356 + "peerDependencies": {
6357 + "eslint": ">=6.0.0"
6358 + }
6359 + },
6360 "node_modules/eslint-plugin-jsonc/node_modules/eslint-visitor-keys": {
6361 "version": "3.4.3",
6362 "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
@@ -6378,25 +6399,25 @@
6399 }
6400 },
6401 "node_modules/eslint-plugin-jsonc/node_modules/tslib": {
6381 - "version": "2.8.0",
6382 - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
6383 - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
6402 + "version": "2.8.1",
6403 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
6404 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
6405 "dev": true
6406 },
6407 "node_modules/eslint-plugin-n": {
6387 - "version": "17.11.1",
6388 - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.11.1.tgz",
6389 - "integrity": "sha512-93IUD82N6tIEgjztVI/l3ElHtC2wTa9boJHrD8iN+NyDxjxz/daZUZKfkedjBZNdg6EqDk4irybUsiPwDqXAEA==",
6408 + "version": "17.13.1",
6409 + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.13.1.tgz",
6410 + "integrity": "sha512-97qzhk1z3DdSJNCqT45EslwCu5+LB9GDadSyBItgKUfGsXAmN/aa7LRQ0ZxHffUxUzvgbTPJL27/pE9ZQWHy7A==",
6411 "dev": true,
6412 "dependencies": {
6392 - "@eslint-community/eslint-utils": "^4.4.0",
6393 - "enhanced-resolve": "^5.17.0",
6394 - "eslint-plugin-es-x": "^7.5.0",
6395 - "get-tsconfig": "^4.7.0",
6396 - "globals": "^15.8.0",
6397 - "ignore": "^5.2.4",
6413 + "@eslint-community/eslint-utils": "^4.4.1",
6414 + "enhanced-resolve": "^5.17.1",
6415 + "eslint-plugin-es-x": "^7.8.0",
6416 + "get-tsconfig": "^4.8.1",
6417 + "globals": "^15.11.0",
6418 + "ignore": "^5.3.2",
6419 "minimatch": "^9.0.5",
6399 - "semver": "^7.5.3"
6420 + "semver": "^7.6.3"
6421 },
6422 "engines": {
6423 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6544,9 +6565,9 @@
6565 }
6566 },
6567 "node_modules/eslint-plugin-vue": {
6547 - "version": "9.29.1",
6548 - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.1.tgz",
6549 - "integrity": "sha512-MH/MbVae4HV/tM8gKAVWMPJbYgW04CK7SuzYRrlNERpxbO0P3+Zdsa2oAcFBW6xNu7W6lIkGOsFAMCRTYmrlWQ==",
6568 + "version": "9.31.0",
6569 + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.31.0.tgz",
6570 + "integrity": "sha512-aYMUCgivhz1o4tLkRHj5oq9YgYPM4/EJc0M7TAKRLCUA5OYxRLAhYEVD2nLtTwLyixEFI+/QXSvKU9ESZFgqjQ==",
6571 "dev": true,
6572 "dependencies": {
6573 "@eslint-community/eslint-utils": "^4.4.0",
@@ -6593,9 +6614,9 @@
6614 }
6615 },
6616 "node_modules/eslint-plugin-yml": {
6596 - "version": "1.14.0",
6597 - "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.14.0.tgz",
6598 - "integrity": "sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==",
6617 + "version": "1.15.0",
6618 + "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.15.0.tgz",
6619 + "integrity": "sha512-leC8APYVOsKyWUlvRwVhewytK5wS70BfMqIaUplFstRfzCoVp0YoEroV4cUEvQrBj93tQ3M9LcjO/ewr6D4kjA==",
6620 "dev": true,
6621 "dependencies": {
6622 "debug": "^4.3.2",
@@ -7851,9 +7872,9 @@
7872 }
7873 },
7874 "node_modules/immutable": {
7854 - "version": "4.3.7",
7855 - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
7856 - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
7875 + "version": "5.0.2",
7876 + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz",
7877 + "integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==",
7878 "dev": true
7879 },
7880 "node_modules/import-fresh": {
@@ -8006,33 +8027,6 @@
8027 "url": "https://github.com/sponsors/sindresorhus"
8028 }
8029 },
8009 - "node_modules/is-ci": {
8010 - "version": "3.0.1",
8011 - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
8012 - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
8013 - "dev": true,
8014 - "dependencies": {
8015 - "ci-info": "^3.2.0"
8016 - },
8017 - "bin": {
8018 - "is-ci": "bin.js"
8019 - }
8020 - },
8021 - "node_modules/is-ci/node_modules/ci-info": {
8022 - "version": "3.9.0",
8023 - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
8024 - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
8025 - "dev": true,
8026 - "funding": [
8027 - {
8028 - "type": "github",
8029 - "url": "https://github.com/sponsors/sibiraj-s"
8030 - }
8031 - ],
8032 - "engines": {
8033 - "node": ">=8"
8034 - }
8035 - },
8030 "node_modules/is-core-module": {
8031 "version": "2.15.1",
8032 "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
@@ -8401,18 +8395,6 @@
8395 }
8396 }
8397 },
8404 - "node_modules/jsdom/node_modules/tough-cookie": {
8405 - "version": "5.0.0",
8406 - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
8407 - "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
8408 - "dev": true,
8409 - "dependencies": {
8410 - "tldts": "^6.1.32"
8411 - },
8412 - "engines": {
8413 - "node": ">=16"
8414 - }
8415 - },
8398 "node_modules/jsdom/node_modules/xml-name-validator": {
8399 "version": "5.0.0",
8400 "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
@@ -10942,9 +10924,9 @@
10924 }
10925 },
10926 "node_modules/pinia-plugin-persistedstate": {
10945 - "version": "4.1.2",
10946 - "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.1.2.tgz",
10947 - "integrity": "sha512-oh4y4lmtXcgbE3BDWTDBUl9F4G1lhtgDI+GnF+cDDZ/fF6wnGYp4TKQ6FCuv9hMLAkNjs6IzADZHwPBYq10ksQ==",
10927 + "version": "4.1.3",
10928 + "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.1.3.tgz",
10929 + "integrity": "sha512-5Rad7oSoEh0na+j4jEViVQMtAYE38KjU7ixKc+am33QX/clJ1mb19zTZqMJXvq+PnQW7uuEuL33Q6NL0GbkfWw==",
10930 "dependencies": {
10931 "@nuxt/kit": "^3.13.2",
10932 "deep-pick-omit": "^1.2.1",
@@ -11058,9 +11040,9 @@
11040 }
11041 },
11042 "node_modules/postcss": {
11061 - "version": "8.4.47",
11062 - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
11063 - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
11043 + "version": "8.4.49",
11044 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
11045 + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
11046 "funding": [
11047 {
11048 "type": "opencollective",
@@ -11077,7 +11059,7 @@
11059 ],
11060 "dependencies": {
11061 "nanoid": "^3.3.7",
11080 - "picocolors": "^1.1.0",
11062 + "picocolors": "^1.1.1",
11063 "source-map-js": "^1.2.1"
11064 },
11065 "engines": {
@@ -11390,12 +11372,6 @@
11372 "node": ">= 0.10"
11373 }
11374 },
11393 - "node_modules/psl": {
11394 - "version": "1.9.0",
11395 - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
11396 - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
11397 - "dev": true
11398 - },
11375 "node_modules/pump": {
11376 "version": "3.0.2",
11377 "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
@@ -11438,12 +11414,6 @@
11414 "url": "https://github.com/sponsors/ljharb"
11415 }
11416 },
11441 - "node_modules/querystringify": {
11442 - "version": "2.2.0",
11443 - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
11444 - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
11445 - "dev": true
11446 - },
11417 "node_modules/queue-microtask": {
11418 "version": "1.2.3",
11419 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -11770,12 +11740,6 @@
11740 "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==",
11741 "dev": true
11742 },
11773 - "node_modules/requires-port": {
11774 - "version": "1.0.0",
11775 - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
11776 - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
11777 - "dev": true
11778 - },
11743 "node_modules/resolve": {
11744 "version": "1.22.8",
11745 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -12155,13 +12119,13 @@
12119 "dev": true
12120 },
12121 "node_modules/sass": {
12158 - "version": "1.80.6",
12159 - "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz",
12160 - "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==",
12122 + "version": "1.80.7",
12123 + "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.7.tgz",
12124 + "integrity": "sha512-MVWvN0u5meytrSjsU7AWsbhoXi1sc58zADXFllfZzbsBT1GHjjar6JwBINYPRrkx/zqnQ6uqbQuHgE95O+C+eQ==",
12125 "dev": true,
12126 "dependencies": {
12127 "chokidar": "^4.0.0",
12164 - "immutable": "^4.0.0",
12128 + "immutable": "^5.0.2",
12129 "source-map-js": ">=0.6.2 <2.0.0"
12130 },
12131 "bin": {
@@ -12627,9 +12591,9 @@
12591 }
12592 },
12593 "node_modules/std-env": {
12630 - "version": "3.7.0",
12631 - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
12632 - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg=="
12594 + "version": "3.8.0",
12595 + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
12596 + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w=="
12597 },
12598 "node_modules/stream-combiner": {
12599 "version": "0.0.4",
@@ -13324,27 +13288,15 @@
13288 }
13289 },
13290 "node_modules/tough-cookie": {
13327 - "version": "4.1.4",
13328 - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
13329 - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
13291 + "version": "5.0.0",
13292 + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
13293 + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
13294 "dev": true,
13295 "dependencies": {
13332 - "psl": "^1.1.33",
13333 - "punycode": "^2.1.1",
13334 - "universalify": "^0.2.0",
13335 - "url-parse": "^1.5.3"
13296 + "tldts": "^6.1.32"
13297 },
13298 "engines": {
13338 - "node": ">=6"
13339 - }
13340 - },
13341 - "node_modules/tough-cookie/node_modules/universalify": {
13342 - "version": "0.2.0",
13343 - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
13344 - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
13345 - "dev": true,
13346 - "engines": {
13347 - "node": ">= 4.0.0"
13299 + "node": ">=16"
13300 }
13301 },
13302 "node_modules/tr46": {
@@ -14214,16 +14166,6 @@
14166 "punycode": "^2.1.0"
14167 }
14168 },
14217 - "node_modules/url-parse": {
14218 - "version": "1.5.10",
14219 - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
14220 - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
14221 - "dev": true,
14222 - "dependencies": {
14223 - "querystringify": "^2.1.1",
14224 - "requires-port": "^1.0.0"
14225 - }
14226 - },
14169 "node_modules/util-deprecate": {
14170 "version": "1.0.2",
14171 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -14327,9 +14269,9 @@
14269 }
14270 },
14271 "node_modules/vite": {
14330 - "version": "5.4.10",
14331 - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
14332 - "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
14272 + "version": "5.4.11",
14273 + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
14274 + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
14275 "dev": true,
14276 "dependencies": {
14277 "esbuild": "^0.21.3",
@@ -14416,13 +14358,14 @@
14358 }
14359 },
14360 "node_modules/vite-node": {
14419 - "version": "2.1.4",
14420 - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz",
14421 - "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==",
14361 + "version": "2.1.5",
14362 + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz",
14363 + "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==",
14364 "dev": true,
14365 "dependencies": {
14366 "cac": "^6.7.14",
14367 "debug": "^4.3.7",
14368 + "es-module-lexer": "^1.5.4",
14369 "pathe": "^1.1.2",
14370 "vite": "^5.0.0"
14371 },
@@ -14527,14 +14470,14 @@
14470 }
14471 },
14472 "node_modules/vite-plugin-vue-devtools": {
14530 - "version": "7.6.3",
14531 - "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.6.3.tgz",
14532 - "integrity": "sha512-p1rZMKzreWqxj9U05RaxY1vDoOhGYhA6iX8vKfo4nD6jqTmVoGjjk+U1g5HYwwTCdr/eck3kzO2f4gnPCjqVKA==",
14473 + "version": "7.6.4",
14474 + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.6.4.tgz",
14475 + "integrity": "sha512-jxSsLyuETfmZ1OSrmnDp28BG6rmURrP7lkeyHW2gBFDyo+4dUcqVeQNMhbV7uKZn80mDdv06Mysw/5AdGxDvJQ==",
14476 "dev": true,
14477 "dependencies": {
14535 - "@vue/devtools-core": "^7.6.3",
14536 - "@vue/devtools-kit": "^7.6.3",
14537 - "@vue/devtools-shared": "^7.6.3",
14478 + "@vue/devtools-core": "^7.6.4",
14479 + "@vue/devtools-kit": "^7.6.4",
14480 + "@vue/devtools-shared": "^7.6.4",
14481 "execa": "^8.0.1",
14482 "sirv": "^3.0.0",
14483 "vite-plugin-inspect": "^0.8.7",
@@ -14714,30 +14657,30 @@
14657 }
14658 },
14659 "node_modules/vitest": {
14717 - "version": "2.1.4",
14718 - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz",
14719 - "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==",
14660 + "version": "2.1.5",
14661 + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz",
14662 + "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==",
14663 "dev": true,
14664 "dependencies": {
14722 - "@vitest/expect": "2.1.4",
14723 - "@vitest/mocker": "2.1.4",
14724 - "@vitest/pretty-format": "^2.1.4",
14725 - "@vitest/runner": "2.1.4",
14726 - "@vitest/snapshot": "2.1.4",
14727 - "@vitest/spy": "2.1.4",
14728 - "@vitest/utils": "2.1.4",
14665 + "@vitest/expect": "2.1.5",
14666 + "@vitest/mocker": "2.1.5",
14667 + "@vitest/pretty-format": "^2.1.5",
14668 + "@vitest/runner": "2.1.5",
14669 + "@vitest/snapshot": "2.1.5",
14670 + "@vitest/spy": "2.1.5",
14671 + "@vitest/utils": "2.1.5",
14672 "chai": "^5.1.2",
14673 "debug": "^4.3.7",
14674 "expect-type": "^1.1.0",
14675 "magic-string": "^0.30.12",
14676 "pathe": "^1.1.2",
14734 - "std-env": "^3.7.0",
14677 + "std-env": "^3.8.0",
14678 "tinybench": "^2.9.0",
14679 "tinyexec": "^0.3.1",
14680 "tinypool": "^1.0.1",
14681 "tinyrainbow": "^1.2.0",
14682 "vite": "^5.0.0",
14740 - "vite-node": "2.1.4",
14683 + "vite-node": "2.1.5",
14684 "why-is-node-running": "^2.3.0"
14685 },
14686 "bin": {
@@ -14752,8 +14695,8 @@
14695 "peerDependencies": {
14696 "@edge-runtime/vm": "*",
14697 "@types/node": "^18.0.0 || >=20.0.0",
14755 - "@vitest/browser": "2.1.4",
14756 - "@vitest/ui": "2.1.4",
14698 + "@vitest/browser": "2.1.5",
14699 + "@vitest/ui": "2.1.5",
14700 "happy-dom": "*",
14701 "jsdom": "*"
14702 },
frontend/package.json
+15 -15
@@ -59,7 +59,7 @@
59 "nanoid": "^5.0.8",
60 "password-validator": "^5.3.0",
61 "pinia": "^2.2.6",
62 - "pinia-plugin-persistedstate": "^4.1.2",
62 + "pinia-plugin-persistedstate": "^4.1.3",
63 "secure-ls": "^2.0.0",
64 "shiki": "^1.22.2",
65 "validator": "^13.12.0",
@@ -74,10 +74,10 @@
74 "vuedraggable": "^4.1.0"
75 },
76 "optionalDependencies": {
77 - "@rollup/rollup-linux-x64-gnu": "^4.24.4"
77 + "@rollup/rollup-linux-x64-gnu": "^4.26.0"
78 },
79 "devDependencies": {
80 - "@antfu/eslint-config": "^3.8.0",
80 + "@antfu/eslint-config": "^3.9.1",
81 "@clack/prompts": "^0.7.0",
82 "@iconify/vue": "^4.1.2",
83 "@tsconfig/node20": "^20.1.4",
@@ -88,22 +88,22 @@
88 "@types/lodash": "^4.17.13",
89 "@types/node": "^22.9.0",
90 "@types/validator": "^13.12.2",
91 - "@vitejs/plugin-vue": "^5.1.4",
92 - "@vitejs/plugin-vue-jsx": "^4.0.1",
91 + "@vitejs/plugin-vue": "^5.2.0",
92 + "@vitejs/plugin-vue-jsx": "^4.1.0",
93 "@vue/test-utils": "^2.4.6",
94 - "@vue/tsconfig": "^0.5.1",
94 + "@vue/tsconfig": "^0.6.0",
95 "autoprefixer": "^10.4.20",
96 - "cypress": "^13.15.1",
96 + "cypress": "^13.15.2",
97 "depcheck": "^1.4.7",
98 "eslint": "^9.14.0",
99 "flourite": "^1.3.0",
100 "fs-extra": "^11.2.0",
101 "jsdom": "^25.0.1",
102 "npm-run-all2": "^7.0.1",
103 - "postcss": "^8.4.47",
103 + "postcss": "^8.4.49",
104 "prettier": "^3.3.3",
105 "prettier-plugin-tailwindcss": "^0.6.8",
106 - "sass": "^1.80.6",
106 + "sass": "^1.80.7",
107 "start-server-and-test": "^2.0.8",
108 "tailwind-config-viewer": "^2.0.4",
109 "tailwindcss": "^3.4.14",
@@ -111,18 +111,18 @@
111 "type-fest": "^4.26.1",
112 "typescript": "~5.6.3",
113 "unplugin-vue-components": "^0.27.4",
114 - "vite": "^5.4.10",
114 + "vite": "^5.4.11",
115 "vite-bundle-visualizer": "^1.2.1",
116 - "vite-plugin-vue-devtools": "^7.6.3",
116 + "vite-plugin-vue-devtools": "^7.6.4",
117 "vite-svg-loader": "^5.1.0",
118 - "vitest": "^2.1.4",
118 + "vitest": "^2.1.5",
119 "vue-tsc": "^2.1.10"
120 },
121 "pnpm": {
122 "overrides": {
123 - "@typescript-eslint/eslint-plugin": "^8.13.0",
123 + "@typescript-eslint/eslint-plugin": "^8.14.0",
124 "@typescript-eslint/eslint-plugin>eslint": "$eslint",
125 - "@typescript-eslint/parser": "^8.13.0",
125 + "@typescript-eslint/parser": "^8.14.0",
126 "@typescript-eslint/parser>eslint": "$eslint",
127 "eslint": "$eslint"
128 }
@@ -134,6 +134,6 @@
134 "@typescript-eslint/parser": {
135 "eslint": "^9.14.0"
136 },
137 - "@typescript-eslint/typescript-estree": "^8.13.0"
137 + "@typescript-eslint/typescript-estree": "^8.14.0"
138 }
139 }
frontend/src/api/endpoints/threatIntel.ts
+22 -1
@@ -1,5 +1,11 @@
1 import type { FlaskBaseResponse } from "@/types/flask.d"
2 -import type { EpssScore, EvaluationData, ThreatIntelResponse } from "@/types/threatIntel.d"
2 +import type {
3 + AiAnalysisResponse,
4 + AiWazuhExclusionRuleResponse,
5 + EpssScore,
6 + EvaluationData,
7 + ThreatIntelResponse
8 +} from "@/types/threatIntel.d"
9 import { HttpClient } from "../httpClient"
10
11 export default {
@@ -23,5 +29,20 @@ export default {
29 `/threat_intel/epss`,
30 body
31 )
32 + },
33 + aiAlertAnalysis({ indexId, indexName }: { indexId: string; indexName: string }) {
34 + return HttpClient.post<FlaskBaseResponse & AiAnalysisResponse>(`/threat_intel/ai/analyze-alert`, {
35 + index_name: indexName,
36 + index_id: indexId
37 + })
38 + },
39 + aiWazuhExclusionRule({ indexId, indexName }: { indexId: string; indexName: string }) {
40 + return HttpClient.post<FlaskBaseResponse & AiWazuhExclusionRuleResponse>(
41 + `/threat_intel/ai/wazuh-exclusion-rule`,
42 + {
43 + index_name: indexName,
44 + index_id: indexId
45 + }
46 + )
47 }
48 }
frontend/src/app-layouts/HorizontalNav/SidebarFooter.vue
+4 -9
@@ -8,16 +8,11 @@
8 import { useThemeStore } from "@/stores/theme"
9 import { renderIcon } from "@/utils"
10 import { NMenu } from "naive-ui"
11 -import { computed, h, ref, toRefs } from "vue"
12 -
13 -const props = withDefaults(
14 - defineProps<{
15 - collapsed?: boolean
16 - }>(),
17 - { collapsed: false }
18 -)
19 -const { collapsed } = toRefs(props)
11 +import { computed, h, ref } from "vue"
12
13 +const { collapsed = false } = defineProps<{
14 + collapsed?: boolean
15 +}>()
16 const ContactIcon = "ic:outline-alternate-email"
17 const menuOptions = ref([
18 {
frontend/src/app-layouts/common/Logo.vue
+7 -12
@@ -15,20 +15,15 @@
15
16 <script lang="ts" setup>
17 import { useThemeStore } from "@/stores/theme"
18 -import { computed, toRefs } from "vue"
19 -
20 -const props = withDefaults(
21 - defineProps<{
22 - mini: boolean
23 - dark?: boolean
24 - }>(),
25 - { dark: undefined }
26 -)
27 -const { mini, dark } = toRefs(props)
18 +import { computed } from "vue"
19
20 +const { mini, dark } = defineProps<{
21 + mini: boolean
22 + dark?: boolean
23 +}>()
24 const themeStore = useThemeStore()
30 -const isDark = computed<boolean>(() => dark.value ?? themeStore.isThemeDark)
31 -const isLight = computed<boolean>(() => !dark.value || themeStore.isThemeLight)
25 +const isDark = computed<boolean>(() => dark ?? themeStore.isThemeDark)
26 +const isLight = computed<boolean>(() => !dark || themeStore.isThemeLight)
27 </script>
28
29 <style lang="scss" scoped>
frontend/src/app-layouts/common/Navbar/index.vue
+5 -10
@@ -25,19 +25,14 @@ import type { MenuMixedOption } from "naive-ui/es/menu/src/interface"
25 import { useThemeStore } from "@/stores/theme"
26 import _uniq from "lodash/uniq"
27 import { type MenuInst, NMenu } from "naive-ui"
28 -import { computed, onBeforeMount, ref, toRefs } from "vue"
28 +import { computed, onBeforeMount, ref } from "vue"
29 import { type RouteRecordNormalized, useRoute, useRouter } from "vue-router"
30 import getItems from "./items"
31
32 -const props = withDefaults(
33 - defineProps<{
34 - mode?: "vertical" | "horizontal"
35 - collapsed?: boolean
36 - }>(),
37 - { mode: "vertical", collapsed: false }
38 -)
39 -const { mode, collapsed } = toRefs(props)
40 -
32 +const { mode = "horizontal", collapsed = false } = defineProps<{
33 + mode?: "vertical" | "horizontal"
34 + collapsed?: boolean
35 +}>()
36 const route = useRoute()
37 const router = useRouter()
38 const selectedKey = ref<string | null>(null)
frontend/src/assets/scss/common.scss
-2
@@ -160,8 +160,6 @@
160 align-items: center;
161 justify-content: center;
162 padding: 40px;
163 - text-align: center;
164 - font-size: 20px;
163
164 .n-alert {
165 width: 90vw;
frontend/src/assets/scss/overrides/naive-override.scss
+26 -16
@@ -99,23 +99,33 @@
99 }
100 }
101
102 -.n-spin-container .n-spin-body {
103 - inset: 0 !important;
104 - transform: none !important;
105 - display: block;
106 -
107 - .n-base-loading {
108 - position: sticky;
109 - top: 0;
110 - bottom: 0;
111 - left: 50%;
112 - height: min(calc(100vh / 1.5), 100%);
113 - transform: translateX(-50%);
114 -
115 - .n-base-loading__transition-wrapper {
116 - transform: translateY(-50%);
117 - height: fit-content;
102 +.n-spin-container {
103 + .n-spin-body {
104 + inset: 0 !important;
105 + transform: none !important;
106 + display: block;
107 +
108 + .n-base-loading {
109 + position: sticky;
110 top: 50%;
111 + margin-top: min(50%, 100px);
112 + width: 100%;
113 + display: flex;
114 + justify-content: center;
115 + transform: translateY(-70px);
116 +
117 + .n-base-loading__transition-wrapper {
118 + width: 1em;
119 + height: 1em;
120 + }
121 + }
122 +
123 + .n-spin-description {
124 + position: sticky;
125 + top: calc(50% + 50px);
126 + left: 50%;
127 + transform: translateX(-50%) translateY(-40px);
128 + margin-top: 0;
129 }
130 }
131 }
frontend/src/components/common/CodeSource.vue
+14 -6
@@ -6,10 +6,6 @@
6 </div>
7 </n-card>
8
9 - <div class="flex justify-end">
10 - <n-button quaternary size="tiny" @click="showSource = !showSource">toggle source view</n-button>
11 - </div>
12 -
9 <n-input
10 v-if="showSource"
11 :value="source"
@@ -22,6 +18,10 @@
18 maxRows: 18
19 }"
20 />
21 +
22 + <div v-if="showToggleButton" class="flex justify-end">
23 + <n-button quaternary size="tiny" @click="showSource = !showSource">toggle source view</n-button>
24 + </div>
25 </div>
26 </template>
27
@@ -30,12 +30,20 @@ import vShiki from "@/directives/v-shiki"
30 import { NButton, NCard, NInput } from "naive-ui"
31 import { computed, ref } from "vue"
32
33 -const { code, lang } = defineProps<{
33 +const {
34 + code,
35 + lang,
36 + decode,
37 + showToggleButton = true
38 +} = defineProps<{
39 code: string | object | number
40 lang?: string
41 decode?: boolean
42 + showToggleButton?: boolean
43 }>()
44
45 const showSource = ref(false)
40 -const source = computed(() => JSON.stringify(code, null, "\t"))
46 +const source = computed(() =>
47 + typeof code === "string" || typeof code === "number" ? `${code}` : JSON.stringify(code, null, "\t")
48 +)
49 </script>
frontend/src/components/common/ImageCropper.vue
+6 -11
@@ -36,27 +36,22 @@
36
37 <script lang="ts" setup>
38 import { NButton, NCard, NModal, NUpload, NUploadDragger, type UploadSettledFileInfo } from "naive-ui"
39 -import { computed, ref, toRefs } from "vue"
39 +import { computed, ref } from "vue"
40 import { CircleStencil, Cropper, type CropperResult, RectangleStencil } from "vue-advanced-cropper"
41 import "vue-advanced-cropper/dist/style.css"
42
43 export type ImageCropperResult = CropperResult
44
45 -const props = withDefaults(
46 - defineProps<{
47 - placeholder?: string
48 - shape?: "square" | "circle"
49 - }>(),
50 - { placeholder: "Select an image", shape: "square" }
51 -)
45 +const { placeholder = "Select an image", shape = "square" } = defineProps<{
46 + placeholder?: string
47 + shape?: "square" | "circle"
48 +}>()
49
50 const emit = defineEmits<{
51 (e: "crop", value: ImageCropperResult): void
52 }>()
53
57 -const { placeholder, shape } = toRefs(props)
58 -
59 -const stencil = computed(() => (shape.value === "circle" ? CircleStencil : RectangleStencil))
54 +const stencil = computed(() => (shape === "circle" ? CircleStencil : RectangleStencil))
55 const img = ref("")
56 const showCropper = ref(false)
57 const cropper = ref<typeof Cropper | null>(null)
frontend/src/components/common/Percentage.vue
+9 -9
@@ -44,7 +44,6 @@
44 <script setup lang="ts">
45 import Icon from "@/components/common/Icon.vue"
46 import { NProgress } from "naive-ui"
47 -import { toRefs } from "vue"
47
48 export interface PercentageProps {
49 value: number
@@ -56,14 +55,15 @@ export interface PercentageProps {
55 direction?: "up" | "down"
56 }
57
59 -const props = withDefaults(defineProps<PercentageProps>(), {
60 - useColor: true,
61 - useBackground: false,
62 - useOpacity: false,
63 - icon: "arrow",
64 - progress: false
65 -})
66 -const { value, useColor, useBackground, useOpacity, icon, progress, direction } = toRefs(props)
58 +const {
59 + value,
60 + direction,
61 + useColor = true,
62 + useBackground = false,
63 + useOpacity = false,
64 + icon = "arrow",
65 + progress = false
66 +} = defineProps<PercentageProps>()
67
68 const ChevronUp = "tabler:chevron-up"
69 const ChevronDown = "tabler:chevron-down"
frontend/src/components/common/SegmentedPage.vue
+30 -35
@@ -76,7 +76,7 @@
76 import Icon from "@/components/common/Icon.vue"
77 import { onClickOutside, useWindowSize } from "@vueuse/core"
78 import { NButton, NScrollbar, NSplit } from "naive-ui"
79 -import { computed, onMounted, ref, toRefs, useSlots, watch } from "vue"
79 +import { computed, onMounted, ref, useSlots, watch } from "vue"
80
81 type SidebarPosition = "left" | "right"
82
@@ -86,49 +86,44 @@ export interface CtxSegmentedPage {
86 openSidebar: () => void
87 }
88
89 -const props = withDefaults(
90 - defineProps<{
91 - sidebarPosition?: SidebarPosition
92 - hideMenuBtn?: boolean
93 - useMainScroll?: boolean
94 - mainContentStyle?: string
95 - mainContentClass?: string
96 - sidebarContentStyle?: string
97 - sidebarContentClass?: string
98 - enableResize?: boolean
99 - disableContainerQuery?: boolean
100 - defaultSplit?: number
101 - maxSidebarWidth?: number
102 - minSidebarWidth?: number
103 - }>(),
104 - { sidebarPosition: "left", useMainScroll: true, defaultSplit: 0.3, maxSidebarWidth: 450, minSidebarWidth: 250 }
105 -)
106 -
107 -const emit = defineEmits<{
108 - (e: "mounted", value: CtxSegmentedPage): void
109 - (e: "sidebar", value: boolean): void
110 -}>()
111 -
89 const {
113 - sidebarPosition,
90 hideMenuBtn,
115 - useMainScroll,
91 mainContentStyle,
92 mainContentClass,
93 sidebarContentStyle,
94 sidebarContentClass,
95 enableResize,
96 disableContainerQuery,
122 - defaultSplit,
123 - maxSidebarWidth,
124 - minSidebarWidth
125 -} = toRefs(props)
97 + sidebarPosition = "left",
98 + useMainScroll = true,
99 + defaultSplit = 0.3,
100 + maxSidebarWidth = 450,
101 + minSidebarWidth = 250
102 +} = defineProps<{
103 + sidebarPosition?: SidebarPosition
104 + hideMenuBtn?: boolean
105 + useMainScroll?: boolean
106 + mainContentStyle?: string
107 + mainContentClass?: string
108 + sidebarContentStyle?: string
109 + sidebarContentClass?: string
110 + enableResize?: boolean
111 + disableContainerQuery?: boolean
112 + defaultSplit?: number
113 + maxSidebarWidth?: number
114 + minSidebarWidth?: number
115 +}>()
116 +
117 +const emit = defineEmits<{
118 + (e: "mounted", value: CtxSegmentedPage): void
119 + (e: "sidebar", value: boolean): void
120 +}>()
121
122 const MenuIcon = "ph:list-light"
123 const SplitIcon = "carbon:draggable"
124
125 const splitPane = ref()
131 -const sanitizedDefaultSplit = ref(defaultSplit.value)
126 +const sanitizedDefaultSplit = ref(defaultSplit)
127 const splitDisabled = ref(false)
128
129 const slots = useSlots()
@@ -139,12 +134,12 @@ const { width } = useWindowSize()
134 const sidebarAvailable = computed(
135 () => !!slots["sidebar-header"] || !!slots["sidebar-content"] || !!slots["sidebar-footer"]
136 )
142 -const isSidebarLeft = computed<boolean>(() => sidebarPosition.value === "left")
137 +const isSidebarLeft = computed<boolean>(() => sidebarPosition === "left")
138 const tplNameMain = computed<1 | 2>(() => (isSidebarLeft.value ? 2 : 1))
139 const tplNameSide = computed<1 | 2>(() => (isSidebarLeft.value ? 1 : 2))
140 const pane1Style = computed(() => ({
146 - maxWidth: isSidebarLeft.value ? `${maxSidebarWidth.value}px` : `calc(100% - ${minSidebarWidth.value}px)`,
147 - minWidth: isSidebarLeft.value ? `${minSidebarWidth.value}px` : `calc(100% - ${maxSidebarWidth.value}px)`
141 + maxWidth: isSidebarLeft.value ? `${maxSidebarWidth}px` : `calc(100% - ${minSidebarWidth}px)`,
142 + minWidth: isSidebarLeft.value ? `${minSidebarWidth}px` : `calc(100% - ${maxSidebarWidth}px)`
143 }))
144
145 function closeSidebar() {
@@ -168,7 +163,7 @@ watch(
163 watch(
164 width,
165 val => {
171 - sanitizedDefaultSplit.value = val <= 700 ? 0 : isSidebarLeft.value ? defaultSplit.value : 1 - defaultSplit.value
166 + sanitizedDefaultSplit.value = val <= 700 ? 0 : isSidebarLeft.value ? defaultSplit : 1 - defaultSplit
167 splitDisabled.value = val <= 700
168 },
169 { immediate: true }
frontend/src/components/common/cards/CardStatsIcon.vue
+17 -15
@@ -11,25 +11,27 @@
11 <script setup lang="ts">
12 import Icon from "@/components/common/Icon.vue"
13 import { useThemeStore } from "@/stores/theme"
14 -import { computed, toRefs } from "vue"
14 +import { computed } from "vue"
15
16 -const props = withDefaults(
17 - defineProps<{
18 - boxSize?: number
19 - iconSize?: number
20 - iconName?: string
21 - boxed?: boolean
22 - color?: string
23 - }>(),
24 - { boxSize: 40, iconSize: 28, boxed: false }
25 -)
26 -const { boxed, boxSize, iconSize, iconName, color } = toRefs(props)
16 +const {
17 + boxSize = 40,
18 + iconSize = 28,
19 + boxed = false,
20 + iconName,
21 + color
22 +} = defineProps<{
23 + boxSize?: number
24 + iconSize?: number
25 + iconName?: string
26 + boxed?: boolean
27 + color?: string
28 +}>()
29
30 const style = computed(() => useThemeStore().style)
31
30 -const iconColor = computed(() => color?.value || style.value["primary-color"])
31 -const iconBoxedSize = computed(() => (boxSize.value / 100) * 45)
32 -const iconFinalSize = computed(() => (boxed?.value ? iconBoxedSize.value : iconSize.value))
32 +const iconColor = computed(() => color || style.value["primary-color"])
33 +const iconBoxedSize = computed(() => (boxSize / 100) * 45)
34 +const iconFinalSize = computed(() => (boxed ? iconBoxedSize.value : iconSize))
35 </script>
36
37 <style scoped lang="scss">
frontend/src/components/incidentManagement/alerts/AlertAsset.vue
+9 -4
@@ -52,13 +52,14 @@
52 segmented
53 >
54 <template #header>
55 - <div class="min-h-8">
55 + <div class="min-h-8 whitespace-nowrap">
56 {{ assetNameTruncated }}
57 </div>
58 </template>
59 <template #header-extra>
60 - <div class="min-h-8">
61 - <ArtifactRecommendation v-if="alertContext" :context="alertContext.context" />
60 + <div class="flex min-h-8 flex-wrap gap-3">
61 + <AIWazuhExclusionRuleButton :index-id="asset.index_id" :index-name="asset.index_name" />
62 + <AIAnalystButton :index-id="asset.index_id" :index-name="asset.index_name" />
63 </div>
64 </template>
65 <n-tabs type="line" animated :tabs-padding="24">
@@ -147,7 +148,11 @@ const { asset, embedded, badge } = defineProps<{ asset: AlertAsset; embedded?: b
148
149 const AlertAssetInfo = defineAsyncComponent(() => import("./AlertAssetInfo.vue"))
150 const AlertDetailTimeline = defineAsyncComponent(() => import("./AlertDetailTimeline.vue"))
150 -const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
151 +// const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
152 +const AIAnalystButton = defineAsyncComponent(() => import("@/components/threatIntel/AIAnalystButton.vue"))
153 +const AIWazuhExclusionRuleButton = defineAsyncComponent(
154 + () => import("@/components/threatIntel/AIWazuhExclusionRuleButton.vue")
155 +)
156 const ThreatIntelProcessEvaluationProvider = defineAsyncComponent(
157 () => import("@/components/threatIntel/ThreatIntelProcessEvaluationProvider.vue")
158 )
frontend/src/components/incidentManagement/alerts/AlertIoCsList.vue
-2
@@ -51,8 +51,6 @@
51 import type { AlertIOC } from "@/types/incidentManagement/alerts.d"
52 import CollapseKeepAlive from "@/components/common/CollapseKeepAlive.vue"
53 import Icon from "@/components/common/Icon.vue"
54 -import _get from "lodash/get"
55 -import _trim from "lodash/trim"
54 import { NButton, NCollapseTransition, NEmpty } from "naive-ui"
55 import { computed, ref, toRefs } from "vue"
56 import AlertIoCItem from "./AlertIoCItem.vue"
frontend/src/components/incidentManagement/alerts/AlertsList.vue
+1 -1
@@ -145,7 +145,7 @@ import Icon from "@/components/common/Icon.vue"
145 import { useResizeObserver, useStorage } from "@vueuse/core"
146 import axios from "axios"
147 import _orderBy from "lodash/orderBy"
148 -import { NBadge, NButton, NCard, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
148 +import { NBadge, NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
149 import { computed, nextTick, onBeforeMount, provide, ref, watch } from "vue"
150 import AlertItem from "./AlertItem.vue"
151 import AlertsFilters from "./AlertsFilters.vue"
frontend/src/components/license/LicenseFeatureCheck.vue new
+139
@@ -0,0 +1,139 @@
1 +<template>
2 + <div>
3 + <n-tooltip v-if="feedback === 'tooltip'" to="body" :disabled="!showFeedback">
4 + <template #trigger>
5 + <slot v-if="$slots.default" />
6 + <template v-else>
7 + <Icon v-if="showFeedback" :name="LockIcon" :size="18" />
8 + <span v-else></span>
9 + </template>
10 + </template>
11 + <div class="w-90vw flex !max-w-lg flex-col gap-3">
12 + <div>
13 + It seems that the feature you are looking for is currently unavailable. To unlock and use it, you
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()">
18 + <template #icon>
19 + <Icon :name="LicenseIcon"></Icon>
20 + </template>
21 + View license
22 + </n-button>
23 + </div>
24 + </div>
25 + </n-tooltip>
26 +
27 + <div v-if="showFeedback && feedback === 'overlay'" class="over-layer animate-fade">
28 + <n-card class="w-90vw !max-w-lg">
29 + <template #header>
30 + <div class="flex items-center gap-3">
31 + <Icon :name="AlertIcon" :size="18" />
32 + <span>Feature required</span>
33 + </div>
34 + </template>
35 + <div>
36 + It seems that the feature you are looking for is currently unavailable. To unlock and use it, you
37 + need to enable this feature. You can manage your features from the License page.
38 + </div>
39 + <template #footer>
40 + <div class="flex justify-end">
41 + <n-button @click="gotoLicense()">
42 + <template #icon>
43 + <Icon :name="LicenseIcon"></Icon>
44 + </template>
45 + View license
46 + </n-button>
47 + </div>
48 + </template>
49 + </n-card>
50 + </div>
51 +
52 + <n-modal v-model:show="showModal" class="w-90vw !max-w-lg" preset="card">
53 + <template #header>
54 + <div class="flex items-center gap-3">
55 + <Icon :name="AlertIcon" :size="18" />
56 + <span>Feature required</span>
57 + </div>
58 + </template>
59 + <div>
60 + It seems that the feature you are looking for is currently unavailable. To unlock and use it, you need
61 + to enable this feature. You can manage your features from the License page.
62 + </div>
63 + <template #footer>
64 + <div class="flex justify-end">
65 + <n-button @click="gotoLicense()">
66 + <template #icon>
67 + <Icon :name="LicenseIcon"></Icon>
68 + </template>
69 + View license
70 + </n-button>
71 + </div>
72 + </template>
73 + </n-modal>
74 + </div>
75 +</template>
76 +
77 +<script setup lang="ts">
78 +import type { LicenseFeatures } from "@/types/license.d"
79 +import Api from "@/api"
80 +import Icon from "@/components/common/Icon.vue"
81 +import { useGoto } from "@/composables/useGoto"
82 +import { NButton, NCard, NModal, NTooltip } from "naive-ui"
83 +import { onBeforeMount, ref, watch } from "vue"
84 +
85 +const { feature, feedback } = defineProps<{ feature: LicenseFeatures; feedback?: "overlay" | "alert" | "tooltip" }>()
86 +
87 +const emit = defineEmits<{
88 + (e: "response", value: boolean): void
89 + (e: "startLoading"): void
90 + (e: "stopLoading"): void
91 +}>()
92 +
93 +const LockIcon = "carbon:locked"
94 +const LicenseIcon = "carbon:license"
95 +const AlertIcon = "mdi:alert-outline"
96 +const loading = ref(false)
97 +const { gotoLicense } = useGoto()
98 +const showFeedback = ref(false)
99 +const showModal = ref(false)
100 +
101 +function checkFeature(feature: LicenseFeatures) {
102 + loading.value = true
103 + Api.license
104 + .isFeatureEnabled(feature)
105 + .then(res => {
106 + if (!res.data.success) {
107 + showFeedback.value = true
108 + emit("response", false)
109 + } else {
110 + emit("response", true)
111 + }
112 + })
113 + .catch(() => {
114 + showFeedback.value = true
115 + emit("response", false)
116 + })
117 + .finally(() => {
118 + loading.value = false
119 + })
120 +}
121 +
122 +watch(showFeedback, val => {
123 + if (val && feedback === "alert") {
124 + showModal.value = true
125 + }
126 +})
127 +
128 +watch(loading, val => {
129 + if (val) {
130 + emit("startLoading")
131 + } else {
132 + emit("stopLoading")
133 + }
134 +})
135 +
136 +onBeforeMount(() => {
137 + checkFeature(feature)
138 +})
139 +</script>
frontend/src/components/license/LicenseFeatureOverlay.vue deleted
-57
@@ -1,57 +0,0 @@
1 -<template>
2 - <div v-if="showBanner" class="over-layer feature-layer">
3 - <n-alert title="Feature required">
4 - <template #icon>
5 - <Icon :name="AlertIcon" :size="18"></Icon>
6 - </template>
7 - <div class="flex flex-col gap-4">
8 - <div>
9 - It seems that the feature you are looking for is currently unavailable. To unlock and use it, you
10 - need to enable this feature. You can manage your features from the License page.
11 - </div>
12 - <div class="flex justify-end">
13 - <n-button @click="gotoLicense()">
14 - <template #icon>
15 - <Icon :name="LicenseIcon"></Icon>
16 - </template>
17 - View license
18 - </n-button>
19 - </div>
20 - </div>
21 - </n-alert>
22 - </div>
23 -</template>
24 -
25 -<script setup lang="ts">
26 -import type { LicenseFeatures } from "@/types/license.d"
27 -import Api from "@/api"
28 -import Icon from "@/components/common/Icon.vue"
29 -import { useGoto } from "@/composables/useGoto"
30 -import { NAlert, NButton } from "naive-ui"
31 -import { onBeforeMount, ref } from "vue"
32 -
33 -const { feature } = defineProps<{ feature: LicenseFeatures }>()
34 -
35 -const LicenseIcon = "carbon:license"
36 -const AlertIcon = "mdi:alert-outline"
37 -
38 -const { gotoLicense } = useGoto()
39 -const showBanner = ref(false)
40 -
41 -function checkFeature(feature: LicenseFeatures) {
42 - Api.license
43 - .isFeatureEnabled(feature)
44 - .then(res => {
45 - if (!res.data.success) {
46 - showBanner.value = true
47 - }
48 - })
49 - .catch(() => {
50 - showBanner.value = true
51 - })
52 -}
53 -
54 -onBeforeMount(() => {
55 - checkFeature(feature)
56 -})
57 -</script>
frontend/src/components/license/deprecated/LicenseCheckout.vue renamed
frontend/src/components/license/deprecated/LicenseEditor.vue renamed
frontend/src/components/reportCreation/Panels.vue
+2
@@ -435,6 +435,8 @@ onMounted(() => {
435 align-items: center;
436 justify-content: center;
437 text-align: center;
438 + line-height: 1.3;
439 + gap: 6px;
440 }
441 .report-panels {
442 overflow: hidden;
frontend/src/components/sigma/QueriesList.vue
+1 -1
@@ -134,7 +134,7 @@ import Icon from "@/components/common/Icon.vue"
134 import { useResizeObserver, useStorage } from "@vueuse/core"
135 import _cloneDeep from "lodash/cloneDeep"
136 import _orderBy from "lodash/orderBy"
137 -import { NBadge, NButton, NCard, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
137 +import { NBadge, NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
138 import { computed, onBeforeMount, ref, watch } from "vue"
139 import QueriesActions from "./QueriesActions.vue"
140 import QueryItem from "./QueryItem.vue"
frontend/src/components/threatIntel/AIAnalystButton.vue new
+150
@@ -0,0 +1,150 @@
1 +<template>
2 + <div>
3 + <LicenseFeatureCheck
4 + feature="SOCFORTRESS AI"
5 + feedback="tooltip"
6 + @response="
7 + (() => {
8 + licenseChecked = true
9 + licenseResponse = $event
10 + })()
11 + "
12 + @start-loading="licenseChecking = true"
13 + @stop-loading="licenseChecking = false"
14 + >
15 + <n-button
16 + :size="size || 'small'"
17 + ghost
18 + type="primary"
19 + :loading="loading || licenseChecking"
20 + :disabled="!licenseChecked || !licenseResponse"
21 + @click="analysis()"
22 + >
23 + <template #icon>
24 + <Icon :name="AiIcon" />
25 + </template>
26 + <div class="flex items-center gap-2">
27 + <span>AI Analyst</span>
28 + <Icon v-if="!licenseResponse && licenseChecked" :name="LockIcon" :size="14" />
29 + </div>
30 + </n-button>
31 + </LicenseFeatureCheck>
32 +
33 + <n-modal
34 + v-model:show="showModal"
35 + preset="card"
36 + content-class="!p-0"
37 + :style="{ maxWidth: 'min(710px, 90vw)', minHeight: 'min(500px, 90vh)' }"
38 + :bordered="false"
39 + segmented
40 + >
41 + <template #header>
42 + <div class="flex flex-wrap items-center gap-2">
43 + <span>AI Analysis</span>
44 + <code v-if="analysisResponse?.risk_evaluation" class="px-2 py-1">
45 + RISK:
46 + <strong
47 + :class="{
48 + 'text-warning': analysisResponse?.risk_evaluation === 'medium',
49 + 'text-error': analysisResponse?.risk_evaluation === 'high'
50 + }"
51 + >
52 + {{ analysisResponse.risk_evaluation }}
53 + </strong>
54 + </code>
55 + </div>
56 + </template>
57 + <template v-if="analysisResponse" #header-extra>
58 + <code class="px-2 py-1">
59 + CONFIDENCE SCORE:
60 + <strong>{{ analysisResponse.confidence_score * 100 }}%</strong>
61 + </code>
62 + </template>
63 + <n-tabs type="line" animated :tabs-padding="24">
64 + <n-tab-pane v-if="analysisResponse?.analysis" name="Analysis" tab="Analysis" display-directive="show">
65 + <div class="p-7 pt-4">
66 + <Suspense>
67 + <Markdown :source="analysisResponse.analysis" />
68 + </Suspense>
69 + </div>
70 + </n-tab-pane>
71 + <n-tab-pane
72 + v-if="analysisResponse?.base64_decoded"
73 + name="Base64Decoded"
74 + tab="Base64 Decoded"
75 + display-directive="show"
76 + >
77 + <div class="p-7 pt-4">
78 + <CodeSource :code="analysisResponse.base64_decoded" :decode="true" />
79 + </div>
80 + </n-tab-pane>
81 + <n-tab-pane
82 + v-if="analysisResponse?.threat_indicators"
83 + name="ThreatIndicators"
84 + tab="Threat Indicators"
85 + display-directive="show"
86 + >
87 + <div class="p-7 pt-4">
88 + <Suspense>
89 + <Markdown :source="analysisResponse.threat_indicators" />
90 + </Suspense>
91 + </div>
92 + </n-tab-pane>
93 + </n-tabs>
94 + </n-modal>
95 + </div>
96 +</template>
97 +
98 +<script setup lang="ts">
99 +import type { AiAnalysisResponse } from "@/types/threatIntel.d"
100 +import type { Size } from "naive-ui/es/button/src/interface"
101 +import Api from "@/api"
102 +import Icon from "@/components/common/Icon.vue"
103 +import LicenseFeatureCheck from "@/components/license/LicenseFeatureCheck.vue"
104 +import { NButton, NModal, NTabPane, NTabs, useMessage } from "naive-ui"
105 +import { defineAsyncComponent, ref } from "vue"
106 +
107 +const { indexName, indexId, size } = defineProps<{
108 + indexName: string
109 + indexId: string
110 + size?: Size
111 +}>()
112 +
113 +const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
114 +const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
115 +
116 +const LockIcon = "carbon:locked"
117 +const AiIcon = "mage:stars-c"
118 +const showModal = ref<boolean>(false)
119 +const loading = ref<boolean>(false)
120 +const message = useMessage()
121 +const analysisResponse = ref<AiAnalysisResponse | null>(null)
122 +const licenseChecking = ref(false)
123 +const licenseChecked = ref(false)
124 +const licenseResponse = ref(false)
125 +
126 +function openResponse() {
127 + showModal.value = true
128 +}
129 +
130 +function analysis() {
131 + loading.value = true
132 +
133 + Api.threatIntel
134 + .aiAlertAnalysis({ indexName, indexId })
135 + .then(res => {
136 + if (res.data.success) {
137 + analysisResponse.value = res.data
138 + openResponse()
139 + } else {
140 + message.warning(res.data?.message || "An error occurred. Please try again later.")
141 + }
142 + })
143 + .catch(err => {
144 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
145 + })
146 + .finally(() => {
147 + loading.value = false
148 + })
149 +}
150 +</script>
frontend/src/components/threatIntel/AIWazuhExclusionRuleButton.vue new
+120
@@ -0,0 +1,120 @@
1 +<template>
2 + <div>
3 + <LicenseFeatureCheck
4 + feature="SOCFORTRESS AI"
5 + feedback="tooltip"
6 + @response="
7 + (() => {
8 + licenseChecked = true
9 + licenseResponse = $event
10 + })()
11 + "
12 + @start-loading="licenseChecking = true"
13 + @stop-loading="licenseChecking = false"
14 + >
15 + <n-button
16 + :size="size || 'small'"
17 + ghost
18 + type="primary"
19 + :loading="loading || licenseChecking"
20 + :disabled="!licenseChecked || !licenseResponse"
21 + @click="analysis()"
22 + >
23 + <template #icon>
24 + <Icon :name="AiIcon" />
25 + </template>
26 + <div class="flex items-center gap-2">
27 + <span>Generate Wazuh Exclusion Rule</span>
28 + <Icon v-if="!licenseResponse && licenseChecked" :name="LockIcon" :size="14" />
29 + </div>
30 + </n-button>
31 + </LicenseFeatureCheck>
32 +
33 + <n-modal
34 + v-model:show="showModal"
35 + preset="card"
36 + content-class="!p-0"
37 + :style="{ maxWidth: 'min(710px, 90vw)', minHeight: 'min(500px, 90vh)' }"
38 + :bordered="false"
39 + title="Wazuh Exclusion Rule"
40 + segmented
41 + >
42 + <div
43 + v-if="analysisResponse?.wazuh_exclusion_rule || analysisResponse?.wazuh_exclusion_rule_justification"
44 + class="flex flex-col gap-7 !p-7"
45 + >
46 + <div v-if="analysisResponse?.wazuh_exclusion_rule">
47 + <CodeSource :code="analysisResponse.wazuh_exclusion_rule" :decode="true" />
48 + </div>
49 + <div v-if="analysisResponse?.wazuh_exclusion_rule_justification">
50 + <Suspense>
51 + <Markdown :source="analysisResponse.wazuh_exclusion_rule_justification" />
52 + </Suspense>
53 + </div>
54 + </div>
55 + <n-empty v-else description="No rules found" class="h-48 justify-center" />
56 + </n-modal>
57 + </div>
58 +</template>
59 +
60 +<script setup lang="ts">
61 +import type { AiWazuhExclusionRuleResponse } from "@/types/threatIntel.d"
62 +import type { Size } from "naive-ui/es/button/src/interface"
63 +import Api from "@/api"
64 +import Icon from "@/components/common/Icon.vue"
65 +import LicenseFeatureCheck from "@/components/license/LicenseFeatureCheck.vue"
66 +import { NButton, NEmpty, NModal, useMessage } from "naive-ui"
67 +import { defineAsyncComponent, ref } from "vue"
68 +
69 +const { indexName, indexId, size } = defineProps<{
70 + indexName: string
71 + indexId: string
72 + size?: Size
73 +}>()
74 +
75 +const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
76 +const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
77 +
78 +const LockIcon = "carbon:locked"
79 +const AiIcon = "mage:stars-c"
80 +const showModal = ref<boolean>(false)
81 +const loading = ref<boolean>(false)
82 +const message = useMessage()
83 +const analysisResponse = ref<AiWazuhExclusionRuleResponse | null>(null)
84 +const licenseChecking = ref(false)
85 +const licenseChecked = ref(false)
86 +const licenseResponse = ref(false)
87 +
88 +function openResponse() {
89 + showModal.value = true
90 +}
91 +
92 +function analysis() {
93 + loading.value = true
94 +
95 + Api.threatIntel
96 + .aiWazuhExclusionRule({ indexName, indexId })
97 + .then(res => {
98 + if (res.data.success) {
99 + analysisResponse.value = res.data
100 +
101 + if (res.data.wazuh_exclusion_rule) {
102 + analysisResponse.value.wazuh_exclusion_rule = res.data.wazuh_exclusion_rule.replace(
103 + /\\\\/g,
104 + "\\\\\\\\"
105 + )
106 + }
107 +
108 + openResponse()
109 + } else {
110 + message.warning(res.data?.message || "An error occurred. Please try again later.")
111 + }
112 + })
113 + .catch(err => {
114 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
115 + })
116 + .finally(() => {
117 + loading.value = false
118 + })
119 +}
120 +</script>
frontend/src/types/license.d.ts
+1 -1
@@ -41,7 +41,7 @@ export interface LicenseDataObject {
41 intValue: number
42 }
43
44 -export type LicenseFeatures = "REPORTING" | "THREAT INTEL" | "HUNTRESS" | "MIMECAST" | "CARBONBLACK"
44 +export type LicenseFeatures = "REPORTING" | "THREAT INTEL" | "HUNTRESS" | "MIMECAST" | "CARBONBLACK" | "SOCFORTRESS AI"
45
46 export type LicenseKey = `${string}-${string}-${string}-${string}`
47
frontend/src/types/threatIntel.d.ts
+13
@@ -73,3 +73,16 @@ export interface EvaluationDataTruncated {
73 network: number
74 hashes: number
75 }
76 +
77 +export interface AiAnalysisResponse {
78 + analysis: string
79 + base64_decoded: string
80 + confidence_score: number
81 + threat_indicators: string
82 + risk_evaluation: "low" | "medium" | "high"
83 +}
84 +
85 +export interface AiWazuhExclusionRuleResponse {
86 + wazuh_exclusion_rule: string
87 + wazuh_exclusion_rule_justification: string
88 +}
frontend/src/utils/highlighter.ts
+2 -1
@@ -21,7 +21,8 @@ export async function getHighlighter() {
21 import("shiki/langs/csharp.mjs"),
22 import("shiki/langs/http.mjs"),
23 import("shiki/langs/sql.mjs"),
24 - import("shiki/langs/lua.mjs")
24 + import("shiki/langs/lua.mjs"),
25 + import("shiki/langs/php.mjs")
26 ],
27 loadWasm: getWasm
28 })
frontend/src/views/ReportCreation.vue
+27 -6
@@ -1,14 +1,25 @@
1 <template>
2 <div class="page page-wrapped page-without-footer flex flex-col gap-5">
3 <ReportWizard
4 + v-if="licenseResponse"
5 hide-panels-select
6 + class="animate-fade"
7 @timerange="timerange = $event"
8 @organization="org = $event"
9 @dashboard="dashboard = $event"
10 @panels="panels = $event"
11 />
10 - <ReportPanels :timerange="timerange" :org="org" :dashboard="dashboard" :panels="panels" />
11 - <div class="over-layer mobile-layer">
12 +
13 + <ReportPanels
14 + v-if="licenseResponse"
15 + :timerange="timerange"
16 + :org="org"
17 + :dashboard="dashboard"
18 + :panels="panels"
19 + class="animate-fade"
20 + />
21 +
22 + <div v-if="licenseResponse" class="over-layer mobile-layer">
23 <n-alert>
24 <template #icon>
25 <Icon :name="AlertIcon" :size="18"></Icon>
@@ -16,7 +27,16 @@
27 This function is available only for desktop devices
28 </n-alert>
29 </div>
19 - <LicenseFeatureOverlay feature="REPORTING" />
30 +
31 + <LicenseFeatureCheck
32 + feature="REPORTING"
33 + feedback="overlay"
34 + @response="licenseResponse = $event"
35 + @start-loading="licenseChecking = true"
36 + @stop-loading="licenseChecking = false"
37 + />
38 +
39 + <n-spin v-if="licenseChecking" class="min-h-52"></n-spin>
40 </div>
41 </template>
42
@@ -24,18 +44,19 @@
44 import type { ReportTimeRange } from "@/api/endpoints/reporting"
45 import type { Dashboard, Org, Panel } from "@/types/reporting.d"
46 import Icon from "@/components/common/Icon.vue"
27 -import LicenseFeatureOverlay from "@/components/license/LicenseFeatureOverlay.vue"
47 +import LicenseFeatureCheck from "@/components/license/LicenseFeatureCheck.vue"
48 import ReportPanels from "@/components/reportCreation/Panels.vue"
49 import ReportWizard from "@/components/reportCreation/Wizard.vue"
30 -import { NAlert } from "naive-ui"
50 +import { NAlert, NSpin } from "naive-ui"
51 import { ref } from "vue"
52
53 const AlertIcon = "mdi:alert-outline"
34 -
54 const timerange = ref<ReportTimeRange | null>(null)
55 const org = ref<Org | null>(null)
56 const dashboard = ref<Dashboard | null>(null)
57 const panels = ref<Panel[]>([])
58 +const licenseChecking = ref(false)
59 +const licenseResponse = ref(false)
60 </script>
61
62 <style lang="scss" scoped>
frontend/tailwind.config.js
+2 -1
@@ -65,7 +65,8 @@ export default {
65 },
66 width: {
67 5.5: "1.375rem",
68 - 150: "37.5rem"
68 + 150: "37.5rem",
69 + "90vw": "90vw"
70 },
71 spacing: {
72 "20vh": "20vh",
frontend/tsconfig.app.json
+2
@@ -3,6 +3,8 @@
3 "compilerOptions": {
4 "composite": true,
5 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
6 + "jsx": "preserve",
7 + "jsxImportSource": "vue",
8 "baseUrl": ".",
9 "moduleResolution": "node",
10 "paths": {