@cryptotaxi247 / CoPilot / commits / 41ad9929

Subscribe (#183)

* Add GetLicenseResponse model and get_license endpoint * Add get_license_features endpoint to retrieve license features * Remove report template files * sap siem send to shuffle extra data * remove space in sap siem rule names * add alert_type for shuffle logic * Add DashboardProvisionRequest to provision_dashboards_route * Add example for dashboards_to_include in ProvisionDashboardRequest * Refactor customer provisioning code to make Wazuh worker and HAProxy provisioning optional * remove event_limit field depending on graylog version * Expose ports for Graylog Alerting and Docs * added license route * license response fixes * added license apis * added license types * added license editor * refactor date method * added license viewer * shuffle workflow execution * Refactor wait_for_workflow_execution_results function to handle exceptions and increase sleep time exponentially * Refactor license retrieval in get_license_key function * Refactor license-related middleware functions*** * added license creation form * Add success and message fields to CreateCustomerKeyResponseModel * Refactor license creation route response model * updated license viewer * fixed types import * Add Register Routes * updated dependencies * added infrastructure in customer provision * Add ThreatIntelRegisterRequest and ThreatIntelRegisterResponse models and register_to_threat_intel endpoint * Update branch name in GitHub Actions workflow * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Mar 28, 2024 at 15:34 UTC 41ad9929e2e45aac8dee8c03c665c0ea0baa3d09
78 files changed +1752 -1113
.github/workflows/docker.yml
+1
@@ -32,6 +32,7 @@ jobs:
32 CRYPTOLENS_AUTH=${{ secrets.CRYPTOLENS_AUTH }}
33 RSA_PUBLIC_KEY=${{ secrets.RSA_PUBLIC_KEY }}
34 PRODUCT_ID=${{ secrets.PRODUCT_ID }}
35 + COPILOT_API_KEY=${{ secrets.COPILOT_API_KEY }}
36
37 build-frontend:
38 runs-on: ubuntu-latest
backend/Dockerfile
+3
@@ -118,5 +118,8 @@ ENV RSA_PUBLIC_KEY=$RSA_PUBLIC_KEY
118 ARG PRODUCT_ID
119 ENV PRODUCT_ID=$PRODUCT_ID
120
121 +ARG COPILOT_API_KEY
122 +ENV COPILOT_API_KEY=$COPILOT_API_KEY
123 +
124 # Run your application
125 CMD ["sh", "-c", "ls -la && /opt/venv/bin/python copilot.py"]
backend/app/connectors/shuffle/routes/workflows.py
+53
@@ -4,15 +4,36 @@ from fastapi import Security
4 from loguru import logger
5
6 from app.auth.utils import AuthHandler
7 +from app.connectors.shuffle.schema.workflows import RequestWorkflowExecutionModel
8 +from app.connectors.shuffle.schema.workflows import RequestWorkflowExecutionResponse
9 from app.connectors.shuffle.schema.workflows import WorkflowExecutionBodyModel
10 from app.connectors.shuffle.schema.workflows import WorkflowExecutionResponseModel
11 from app.connectors.shuffle.schema.workflows import WorkflowsResponse
12 +from app.connectors.shuffle.services.workflows import execute_workflow
13 from app.connectors.shuffle.services.workflows import get_workflow_executions
14 from app.connectors.shuffle.services.workflows import get_workflows
15
16 shuffle_workflows_router = APIRouter()
17
18
19 +async def validate_execution_id(workflow_id: str) -> bool:
20 + """
21 + Validate the execution ID.
22 +
23 + Args:
24 + workflow_id (str): The workflow ID.
25 +
26 + Returns:
27 + bool: True if the workflow ID is valid, False otherwise.
28 + """
29 + workflows = await get_workflows()
30 + for workflow in workflows.workflows:
31 + if workflow["id"] == workflow_id:
32 + logger.info("Workflow validation successful")
33 + return True
34 + raise HTTPException(status_code=404, detail="Workflow not found")
35 +
36 +
37 @shuffle_workflows_router.get(
38 "",
39 response_model=WorkflowsResponse,
@@ -76,3 +97,35 @@ async def get_all_workflow_executions() -> WorkflowExecutionResponseModel:
97 )
98 else:
99 raise HTTPException(status_code=404, detail="No workflows found")
100 +
101 +
102 +@shuffle_workflows_router.post(
103 + "/execute",
104 + response_model=RequestWorkflowExecutionResponse,
105 + description="Execute a workflow",
106 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
107 +)
108 +async def execute_workflow_request(
109 + workflow_execution_body: RequestWorkflowExecutionModel,
110 +) -> RequestWorkflowExecutionResponse:
111 + """
112 + Execute a workflow.
113 +
114 + Args:
115 + workflow_execution_body (WorkflowExecutionBodyModel): The workflow execution body model.
116 +
117 + Returns:
118 + RequestWorkflowExecutionResponse: The response model containing the workflow executions.
119 +
120 + Raises:
121 + HTTPException: If the workflow is not found.
122 + """
123 + logger.info(f"Executing workflow with ID: {workflow_execution_body.workflow_id}")
124 +
125 + await validate_execution_id(workflow_execution_body.workflow_id)
126 +
127 + return RequestWorkflowExecutionResponse(
128 + success=True,
129 + message="Successfully executed workflow",
130 + data=await execute_workflow(workflow_execution_body),
131 + )
backend/app/connectors/shuffle/schema/workflows.py
+18
@@ -3,6 +3,7 @@ from typing import Dict
3 from typing import List
4 from typing import Optional
5
6 +from pydantic import UUID4
7 from pydantic import BaseModel
8 from pydantic import Field
9
@@ -46,3 +47,20 @@ class WorkflowExecutionResponseModel(BaseModel):
47 ...,
48 description="List of workflow objects",
49 )
50 +
51 +
52 +class RequestWorkflowExecutionModel(BaseModel):
53 + workflow_id: str = Field(..., description="Unique identifier for the workflow")
54 + execution_argument: str = Field(..., description="Execution argument for the workflow")
55 +
56 +
57 +class ExecuteWorklow(BaseModel):
58 + success: bool = Field(..., description="Indicates if the workflow execution was successful")
59 + execution_id: UUID4 = Field(..., description="The unique identifier for the workflow execution")
60 + authorization: UUID4 = Field(..., description="The authorization token for the workflow execution")
61 +
62 +
63 +class RequestWorkflowExecutionResponse(BaseModel):
64 + message: str = Field(..., description="Response message")
65 + success: bool = Field(..., description="Success status")
66 + data: Dict[str, Any] = Field(..., description="Data object")
backend/app/connectors/shuffle/services/workflows.py
+83
@@ -1,12 +1,16 @@
1 +import asyncio
2 from typing import List
3
4 from fastapi import HTTPException
5 from loguru import logger
6
7 +from app.connectors.shuffle.schema.workflows import ExecuteWorklow
8 +from app.connectors.shuffle.schema.workflows import RequestWorkflowExecutionModel
9 from app.connectors.shuffle.schema.workflows import WorkflowExecutionBodyModel
10 from app.connectors.shuffle.schema.workflows import WorkflowExecutionStatusResponseModel
11 from app.connectors.shuffle.schema.workflows import WorkflowsResponse
12 from app.connectors.shuffle.utils.universal import send_get_request
13 +from app.connectors.shuffle.utils.universal import send_post_request
14
15
16 def remove_large_images_from_actions(workflows: List) -> List:
@@ -98,3 +102,82 @@ async def get_workflow_executions(
102 status_code=500,
103 detail=f"Failed to get workflow executions with error: {e}",
104 )
105 +
106 +
107 +async def execute_workflow(workflow_execution_body: RequestWorkflowExecutionModel):
108 + """
109 + Execute a workflow.
110 +
111 + Args:
112 + workflow_execution_body (WorkflowExecutionBodyModel): The workflow execution body model.
113 +
114 + Returns:
115 + WorkflowExecutionResponseModel: The response model containing the workflow executions.
116 +
117 + Raises:
118 + HTTPException: If the workflow is not found.
119 + """
120 + logger.info(f"Executing workflow with ID: {workflow_execution_body.workflow_id}")
121 + response = ExecuteWorklow(
122 + **(
123 + await send_post_request(
124 + f"/api/v1/workflows/{workflow_execution_body.workflow_id}/execute",
125 + {"execution_argument": workflow_execution_body.execution_argument},
126 + )
127 + )["data"],
128 + )
129 + logger.info(f"Response from executing workflow: {response}")
130 + try:
131 + if response.success:
132 + workflow_completed = await wait_for_workflow_execution_results(response)
133 + if workflow_completed:
134 + logger.info(f"Successfully executed workflow with ID: {workflow_execution_body.workflow_id}")
135 + return await get_workflow_exectution_results(response)
136 + else:
137 + raise HTTPException(
138 + status_code=404,
139 + detail="Failed to execute workflow",
140 + )
141 + except Exception as e:
142 + logger.error(f"Failed to execute workflow with error: {e}")
143 + raise HTTPException(
144 + status_code=500,
145 + detail=f"Failed to execute workflow with error: {e}",
146 + )
147 +
148 +
149 +async def wait_for_workflow_execution_results(execution: ExecuteWorklow):
150 + """
151 + Function to get the workflow results until the status of `FINISHED` is reached.
152 + """
153 + logger.info(f"Retrieving workflow execution results for execution ID: {execution.execution_id}")
154 + for i in range(10):
155 + try:
156 + response = await send_post_request(
157 + "/api/v1/streams/results",
158 + {"execution_id": str(execution.execution_id), "authorization": str(execution.authorization)},
159 + )
160 + status = response.get("data", {}).get("status")
161 + if status == "FINISHED":
162 + logger.info(f"Workflow execution with ID {execution.execution_id} has finished")
163 + return True
164 + except Exception as e:
165 + logger.error(f"Error retrieving workflow execution results: {e}")
166 + await asyncio.sleep(2**i)
167 + logger.info(f"Workflow execution with ID {execution.execution_id} did not finish after 5 attempts")
168 + raise HTTPException(
169 + status_code=500,
170 + detail=f"Workflow execution with ID {execution.execution_id} did not finish after 5 attempts",
171 + )
172 +
173 +
174 +async def get_workflow_exectution_results(execution: ExecuteWorklow):
175 + """
176 + Function to get the workflow results.
177 + """
178 + logger.info(f"Retrieving workflow execution results for execution ID: {execution.execution_id}")
179 + response = await send_post_request(
180 + "/api/v1/streams/results",
181 + {"execution_id": str(execution.execution_id), "authorization": str(execution.authorization)},
182 + )
183 + return response.get("data", {})
backend/app/connectors/shuffle/utils/universal.py
+4 -10
@@ -117,7 +117,7 @@ async def send_get_request(
117 }
118
119
120 -def send_post_request(
120 +async def send_post_request(
121 endpoint: str,
122 data: Dict[str, Any] = None,
123 connector_name: str = "Shuffle",
@@ -134,13 +134,11 @@ def send_post_request(
134 Dict[str, Any]: The response from the POST request.
135 """
136 logger.info(f"Sending POST request to {endpoint}")
137 - attributes = get_connector_info_from_db(connector_name)
137 + async with get_db_session() as session: # This will correctly enter the context manager
138 + attributes = await get_connector_info_from_db(connector_name, session)
139 if attributes is None:
140 logger.error("No Shuffle connector found in the database")
140 - return {
141 - "success": False,
142 - "message": "No Shuffle connector found in the database",
143 - }
141 + return None
142
143 try:
144 HEADERS = {
@@ -149,10 +147,6 @@ def send_post_request(
147 response = requests.post(
148 f"{attributes['connector_url']}{endpoint}",
149 headers=HEADERS,
152 - auth=(
153 - attributes["connector_username"],
154 - attributes["connector_password"],
155 - ),
150 json=data,
151 verify=False,
152 )
backend/app/customer_provisioning/routes/provision.py
+35
@@ -8,15 +8,19 @@ from sqlalchemy.ext.asyncio import AsyncSession
8 from sqlalchemy.future import select
9
10 from app.auth.utils import AuthHandler
11 +from app.connectors.grafana.schema.dashboards import DashboardProvisionRequest
12 from app.connectors.grafana.schema.dashboards import WazuhDashboard
13 from app.customer_provisioning.schema.provision import CustomerProvisionResponse
14 from app.customer_provisioning.schema.provision import CustomersMetaResponse
15 from app.customer_provisioning.schema.provision import CustomerSubsctipion
16 from app.customer_provisioning.schema.provision import GetDashboardsResponse
17 from app.customer_provisioning.schema.provision import GetSubscriptionsResponse
18 +from app.customer_provisioning.schema.provision import ProvisionDashboardRequest
19 +from app.customer_provisioning.schema.provision import ProvisionDashboardResponse
20 from app.customer_provisioning.schema.provision import ProvisionNewCustomer
21 from app.customer_provisioning.schema.wazuh_worker import ProvisionWorkerRequest
22 from app.customer_provisioning.schema.wazuh_worker import ProvisionWorkerResponse
23 +from app.customer_provisioning.services.provision import provision_dashboards
24 from app.customer_provisioning.services.provision import provision_wazuh_customer
25 from app.customer_provisioning.services.provision import provision_wazuh_worker
26 from app.db.db_session import get_db
@@ -327,3 +331,34 @@ async def get_customer_meta(
331 success=True,
332 customer_meta=customer_meta,
333 )
334 +
335 +
336 +@customer_provisioning_router.post(
337 + "/provision/dashboards",
338 + response_model=ProvisionDashboardResponse,
339 + description="Return the list of dashboards available for provisioning",
340 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
341 +)
342 +async def provision_dashboards_route(
343 + request: ProvisionDashboardRequest = Body(...),
344 + session: AsyncSession = Depends(get_db),
345 +):
346 + """
347 + Provision dashboards for a customer.
348 +
349 + Args:
350 + request (ProvisionDashboardsRequest): The request data for provisioning dashboards.
351 + session (AsyncSession): The database session.
352 +
353 + Returns:
354 + ProvisionDashboardsResponse: The response data for the provisioned dashboards.
355 + """
356 + logger.info("Provisioning dashboards")
357 + return await provision_dashboards(
358 + DashboardProvisionRequest(
359 + dashboards=request.dashboards_to_include.dashboards,
360 + organizationId=request.grafana_org_id,
361 + folderId=request.grafana_folder_id,
362 + datasourceUid=request.grafana_datasource_uid,
363 + ),
364 + )
backend/app/customer_provisioning/schema/provision.py
+75 -8
@@ -61,16 +61,16 @@ class ProvisionNewCustomer(BaseModel):
61 ...,
62 description="Dashboards to include in the customer's Grafana instance",
63 )
64 - wazuh_auth_password: str = Field(..., description="Password for the Wazuh API user")
65 - wazuh_registration_port: str = Field(
66 - ...,
64 + wazuh_auth_password: Optional[str] = Field("n/a", description="Password for the Wazuh API user")
65 + wazuh_registration_port: Optional[str] = Field(
66 + "n/a",
67 description="Port for the Wazuh registration service",
68 )
69 - wazuh_logs_port: str = Field(..., description="Port for the Wazuh logs service")
70 - wazuh_api_port: str = Field(..., description="Port for the Wazuh API service")
71 - wazuh_cluster_name: str = Field(..., description="Name of the Wazuh cluster")
72 - wazuh_cluster_key: str = Field(..., description="Password for the Wazuh cluster")
73 - wazuh_master_ip: str = Field(..., description="IP address of the Wazuh master")
69 + wazuh_logs_port: Optional[str] = Field("n/a", description="Port for the Wazuh logs service")
70 + wazuh_api_port: Optional[str] = Field("n/a", description="Port for the Wazuh API service")
71 + wazuh_cluster_name: Optional[str] = Field("n/a", description="Name of the Wazuh cluster")
72 + wazuh_cluster_key: Optional[str] = Field("n/a", description="Password for the Wazuh cluster")
73 + wazuh_master_ip: Optional[str] = Field("n/a", description="IP address of the Wazuh master")
74 grafana_url: str = Field(..., description="URL of the Grafana instance")
75 only_insert_into_db: Optional[bool] = Field(
76 False,
@@ -92,6 +92,14 @@ class ProvisionNewCustomer(BaseModel):
92 None,
93 description="Hostname of the Wazuh worker",
94 )
95 + provision_wazuh_worker: bool = Field(
96 + False,
97 + description="Whether to provision a Wazuh worker for the customer",
98 + )
99 + provision_ha_proxy: bool = Field(
100 + False,
101 + description="Whether to provision an HAProxy for the customer",
102 + )
103
104 @validator("customer_index_name")
105 def validate_customer_index_name(cls, v):
@@ -189,3 +197,62 @@ class ProvisionHaProxyRequest(BaseModel):
197 example="worker1",
198 description="The hostname of the Wazuh worker",
199 )
200 +
201 +
202 +class ProvisionDashboardRequest(BaseModel):
203 + customer_name: str = Field(
204 + ...,
205 + example="SOCFortress",
206 + description="The name of the customer",
207 + )
208 + dashboards_to_include: DashboardProvisionRequest = Field(
209 + ...,
210 + description="Dashboards to include in the customer's Grafana instance",
211 + example={
212 + "dashboards": [
213 + "WAZUH_SUMMARY",
214 + "EDR_WINDOWS_EVENT_LOGS",
215 + "EDR_WAZUH_INVENOTRY",
216 + "EDR_USERS_AND_GROUPS",
217 + "EDR_SYSTEM_VULNERABILITIES",
218 + "EDR_SYSTEM_SECURITY_AUDIT",
219 + "EDR_SYSTEM_PROCESSES",
220 + "EDR_PROCESS_INJECTION",
221 + "EDR_OPEN_AUDIT",
222 + "EDR_NETWORK_SCAN",
223 + "EDR_NETWORK_CONNECTIONS",
224 + "EDR_MITRE",
225 + "EDR_FIM",
226 + "EDR_DOCKER_MONITORING",
227 + "EDR_DNS_REQUESTS",
228 + "EDR_DLL_SIDE_LOADING",
229 + "EDR_COMPLIANCE",
230 + "EDR_AV_MALWARE_IOC",
231 + "EDR_AGENT_INVENTORY",
232 + "EDR_AD_INVENOTRY",
233 + ],
234 + "organizationId": 1,
235 + "folderId": 1,
236 + "datasourceUid": "wazuh",
237 + },
238 + )
239 + grafana_org_id: int = Field(
240 + ...,
241 + description="ID of the Grafana organization",
242 + )
243 + grafana_datasource_uid: str = Field(
244 + ...,
245 + description="UID of the Grafana datasource",
246 + )
247 + grafana_folder_id: int = Field(
248 + ...,
249 + description="ID of the Grafana folder",
250 + )
251 +
252 +
253 +class ProvisionDashboardResponse(BaseModel):
254 + message: str = Field(
255 + ...,
256 + description="Message indicating the status of the request",
257 + )
258 + success: bool = Field(..., description="Whether the request was successful or not")
backend/app/customer_provisioning/services/provision.py
+37 -35
@@ -107,46 +107,48 @@ async def provision_wazuh_customer(
107 session,
108 )
109
110 - provision_worker = await provision_wazuh_worker(
111 - ProvisionWorkerRequest(
112 - customer_name=request.customer_name,
113 - wazuh_auth_password=request.wazuh_auth_password,
114 - wazuh_registration_port=request.wazuh_registration_port,
115 - wazuh_logs_port=request.wazuh_logs_port,
116 - wazuh_api_port=request.wazuh_api_port,
117 - wazuh_cluster_name=request.wazuh_cluster_name,
118 - wazuh_cluster_key=request.wazuh_cluster_key,
119 - wazuh_master_ip=request.wazuh_master_ip,
120 - ),
121 - session,
122 - )
123 -
124 - if provision_worker.success is False:
125 - return CustomerProvisionResponse(
126 - message=f"Customer {request.customer_name} provisioned successfully, but the Wazuh worker failed to provision",
127 - success=True,
128 - customer_meta=customer_meta.dict(),
129 - wazuh_worker_provisioned=False,
110 + if request.provision_wazuh_worker is True:
111 + provision_worker = await provision_wazuh_worker(
112 + ProvisionWorkerRequest(
113 + customer_name=request.customer_name,
114 + wazuh_auth_password=request.wazuh_auth_password,
115 + wazuh_registration_port=request.wazuh_registration_port,
116 + wazuh_logs_port=request.wazuh_logs_port,
117 + wazuh_api_port=request.wazuh_api_port,
118 + wazuh_cluster_name=request.wazuh_cluster_name,
119 + wazuh_cluster_key=request.wazuh_cluster_key,
120 + wazuh_master_ip=request.wazuh_master_ip,
121 + ),
122 + session,
123 )
124
132 - provsion_haproxy = await provision_haproxy(
133 - ProvisionHaProxyRequest(
134 - customer_name=request.customer_name,
135 - wazuh_registration_port=request.wazuh_registration_port,
136 - wazuh_logs_port=request.wazuh_logs_port,
137 - wazuh_worker_hostname=request.wazuh_worker_hostname,
138 - ),
139 - session,
140 - )
125 + if provision_worker.success is False:
126 + return CustomerProvisionResponse(
127 + message=f"Customer {request.customer_name} provisioned successfully, but the Wazuh worker failed to provision",
128 + success=True,
129 + customer_meta=customer_meta.dict(),
130 + wazuh_worker_provisioned=False,
131 + )
132
142 - if provsion_haproxy.success is False:
143 - return CustomerProvisionResponse(
144 - message=f"Customer {request.customer_name} provisioned successfully, but the HAProxy failed to provision",
145 - success=True,
146 - customer_meta=customer_meta.dict(),
147 - wazuh_worker_provisioned=True,
133 + if request.provision_ha_proxy is True:
134 + provsion_haproxy = await provision_haproxy(
135 + ProvisionHaProxyRequest(
136 + customer_name=request.customer_name,
137 + wazuh_registration_port=request.wazuh_registration_port,
138 + wazuh_logs_port=request.wazuh_logs_port,
139 + wazuh_worker_hostname=request.wazuh_worker_hostname,
140 + ),
141 + session,
142 )
143
144 + if provsion_haproxy.success is False:
145 + return CustomerProvisionResponse(
146 + message=f"Customer {request.customer_name} provisioned successfully, but the HAProxy failed to provision",
147 + success=True,
148 + customer_meta=customer_meta.dict(),
149 + wazuh_worker_provisioned=True,
150 + )
151 +
152 return CustomerProvisionResponse(
153 message=f"Customer {request.customer_name} provisioned successfully",
154 success=True,
backend/app/integrations/monitoring_alert/services/provision.py
+76 -67
@@ -47,6 +47,7 @@ from app.integrations.monitoring_alert.schema.provision import (
47 from app.integrations.monitoring_alert.schema.provision import (
48 ProvisionWazuhMonitoringAlertResponse,
49 )
50 +from app.stack_provisioning.graylog.routes.provision import get_graylog_version
51
52 load_dotenv()
53 import uuid
@@ -244,6 +245,14 @@ async def provision_alert_definition(
245 Returns:
246 bool: True if the alert definition was provisioned successfully, False otherwise.
247 """
248 + # If the graylog version is less than 5.2, remove the `event_limit` key from the config
249 + graylog_version = await get_graylog_version()
250 + logger.info(f"Graylog version: {graylog_version}")
251 + if graylog_version < "5.2":
252 + logger.info("Graylog version is less than 5.2. Removing event_limit from config")
253 + if hasattr(alert_definition_model.config, "event_limit"):
254 + delattr(alert_definition_model.config, "event_limit")
255 +
256 response = await send_post_request(
257 endpoint="/api/events/definitions",
258 data=alert_definition_model.dict(),
@@ -297,75 +306,75 @@ async def provision_wazuh_monitoring_alert(
306 },
307 ),
308 )
300 - logger.info(f"SEND TO COPILOT Webhook provisioned with id: {notification_id}")
301 - await provision_alert_definition(
302 - GraylogAlertProvisionModel(
303 - title="WAZUH SYSLOG LEVEL ALERT",
304 - description="Alert on Wazuh syslog level equal to ALERT",
305 - priority=2,
306 - config=GraylogAlertProvisionConfig(
307 - type="aggregation-v1",
308 - query="syslog_level:ALERT AND syslog_type:wazuh AND NOT (rule_group1:office365 OR rule_group1:vulnerability-detector)",
309 - query_parameters=[],
310 - streams=[],
311 - group_by=[],
312 - series=[],
313 - conditions={
314 - "expression": None,
315 - },
316 - search_within_ms=await convert_seconds_to_milliseconds(
317 - request.search_within_last,
318 - ),
319 - execute_every_ms=await convert_seconds_to_milliseconds(
320 - request.execute_every,
321 - ),
322 - event_limit=1000,
323 - ),
324 - field_spec={
325 - "ALERT_ID": GraylogAlertProvisionFieldSpecItem(
326 - data_type="string",
327 - providers=[
328 - GraylogAlertProvisionProvider(
329 - type="template-v1",
330 - template="${source._id}",
331 - require_values=True,
332 - ),
333 - ],
334 - ),
335 - "CUSTOMER_CODE": GraylogAlertProvisionFieldSpecItem(
336 - data_type="string",
337 - providers=[
338 - GraylogAlertProvisionProvider(
339 - type="template-v1",
340 - template="${source.agent_labels_customer}",
341 - require_values=True,
342 - ),
343 - ],
344 - ),
345 - "ALERT_SOURCE": GraylogAlertProvisionFieldSpecItem(
346 - data_type="string",
347 - providers=[
348 - GraylogAlertProvisionProvider(
349 - type="template-v1",
350 - template="WAZUH",
351 - require_values=True,
352 - ),
353 - ],
354 - ),
309 + notification_id = await get_notification_id("SEND TO COPILOT")
310 + await provision_alert_definition(
311 + GraylogAlertProvisionModel(
312 + title="WAZUH SYSLOG LEVEL ALERT",
313 + description="Alert on Wazuh syslog level equal to ALERT",
314 + priority=2,
315 + config=GraylogAlertProvisionConfig(
316 + type="aggregation-v1",
317 + query="syslog_level:ALERT AND syslog_type:wazuh AND NOT (rule_group1:office365 OR rule_group1:vulnerability-detector)",
318 + query_parameters=[],
319 + streams=[],
320 + group_by=[],
321 + series=[],
322 + conditions={
323 + "expression": None,
324 },
356 - key_spec=[],
357 - notification_settings=GraylogAlertProvisionNotificationSettings(
358 - grace_period_ms=0,
359 - backlog_size=None,
360 - ),
361 - notifications=[
362 - GraylogAlertProvisionNotification(
363 - notification_id=notification_id,
364 - ),
365 - ],
366 - alert=True,
325 + search_within_ms=await convert_seconds_to_milliseconds(
326 + request.search_within_last,
327 + ),
328 + execute_every_ms=await convert_seconds_to_milliseconds(
329 + request.execute_every,
330 + ),
331 + event_limit=1000,
332 ),
368 - )
333 + field_spec={
334 + "ALERT_ID": GraylogAlertProvisionFieldSpecItem(
335 + data_type="string",
336 + providers=[
337 + GraylogAlertProvisionProvider(
338 + type="template-v1",
339 + template="${source._id}",
340 + require_values=True,
341 + ),
342 + ],
343 + ),
344 + "CUSTOMER_CODE": GraylogAlertProvisionFieldSpecItem(
345 + data_type="string",
346 + providers=[
347 + GraylogAlertProvisionProvider(
348 + type="template-v1",
349 + template="${source.agent_labels_customer}",
350 + require_values=True,
351 + ),
352 + ],
353 + ),
354 + "ALERT_SOURCE": GraylogAlertProvisionFieldSpecItem(
355 + data_type="string",
356 + providers=[
357 + GraylogAlertProvisionProvider(
358 + type="template-v1",
359 + template="WAZUH",
360 + require_values=True,
361 + ),
362 + ],
363 + ),
364 + },
365 + key_spec=[],
366 + notification_settings=GraylogAlertProvisionNotificationSettings(
367 + grace_period_ms=0,
368 + backlog_size=None,
369 + ),
370 + notifications=[
371 + GraylogAlertProvisionNotification(
372 + notification_id=notification_id,
373 + ),
374 + ],
375 + alert=True,
376 + ),
377 + )
378
379 return ProvisionWazuhMonitoringAlertResponse(
380 success=True,
backend/app/integrations/sap_siem/services/sap_siem_brute_force_same_ip.py
+3
@@ -61,6 +61,9 @@ async def handle_common_suspicious_login_tasks(
61 alert_source_link=f"{alert_source_link}/case?cid={case.data.case_id}",
62 rule_description=f"{case.data.case_name}",
63 hostname=suspicious_login.ip,
64 + rule_name="Rule:_Logins_from_the_same_IP_address",
65 + affected_ip=suspicious_login.ip,
66 + alert_type="ip",
67 ),
68 session=session,
69 )
backend/app/integrations/sap_siem/services/sap_siem_brute_forced_failed_logins.py
+3
@@ -61,6 +61,9 @@ async def handle_common_suspicious_login_tasks(
61 alert_source_link=f"{alert_source_link}/case?cid={case.data.case_id}",
62 rule_description=f"{case.data.case_name}",
63 hostname=suspicious_login.ip,
64 + rule_name="Rule:_Logins_from_different_IP_addresses",
65 + affected_ip=suspicious_login.ip,
66 + alert_type="ip",
67 ),
68 session=session,
69 )
backend/app/integrations/sap_siem/services/sap_siem_failed_same_user_different_geo_location.py
+3
@@ -61,6 +61,9 @@ async def handle_common_suspicious_login_tasks(
61 alert_source_link=f"{alert_source_link}/case?cid={case.data.case_id}",
62 rule_description=f"{case.data.case_name}",
63 hostname=suspicious_login.ip,
64 + rule_name="Rule:_Same_user_from_different_geo_locations",
65 + affected_user=suspicious_login.loginID,
66 + alert_type="user",
67 ),
68 session=session,
69 )
backend/app/integrations/sap_siem/services/sap_siem_failed_same_user_from_different_ip.py
+3
@@ -61,6 +61,9 @@ async def handle_common_suspicious_login_tasks(
61 alert_source_link=f"{alert_source_link}/case?cid={case.data.case_id}",
62 rule_description=f"{case.data.case_name}",
63 hostname=suspicious_login.ip,
64 + rule_name="Rule:_Same_user_from_different_IP_addresses",
65 + affected_user=suspicious_login.loginID,
66 + alert_type="user",
67 ),
68 session=session,
69 )
backend/app/integrations/sap_siem/services/sap_siem_successful_login_same_ip_after_multiple_failures.py
+3
@@ -61,6 +61,9 @@ async def handle_common_suspicious_login_tasks(
61 alert_source_link=f"{alert_source_link}/case?cid={case.data.case_id}",
62 rule_description=f"{case.data.case_name}",
63 hostname=suspicious_login.ip,
64 + rule_name="Rule:_Successful_login_after_multiple_failed_logins",
65 + affected_user=suspicious_login.loginID,
66 + alert_type="user",
67 ),
68 session=session,
69 )
backend/app/integrations/sap_siem/services/sap_siem_successful_same_user_different_geo_location.py
+3
@@ -61,6 +61,9 @@ async def handle_common_suspicious_login_tasks(
61 alert_source_link=f"{alert_source_link}/case?cid={case.data.case_id}",
62 rule_description=f"{case.data.case_name}",
63 hostname=suspicious_login.ip,
64 + rule_name="Rule:_Same_user_from_different_geo_locations",
65 + affected_user=suspicious_login.loginID,
66 + alert_type="user",
67 ),
68 session=session,
69 )
backend/app/integrations/sap_siem/services/sap_siem_successful_user_login_after_using_different_ip.py
+3
@@ -62,6 +62,9 @@ async def handle_common_suspicious_login_tasks(
62 alert_source_link=f"{alert_source_link}/case?cid={case.data.case_id}",
63 rule_description=f"{case.data.case_name}",
64 hostname=suspicious_login.ip,
65 + rule_name="Rule:_Successful_user_login_after_using_different_IP_addresses",
66 + affected_user=suspicious_login.loginID,
67 + alert_type="user",
68 ),
69 session=session,
70 )
backend/app/integrations/utils/schema.py
+3
@@ -193,6 +193,9 @@ class ShufflePayload(BaseModel):
193 examples="test-hostname",
194 )
195
196 + class Config:
197 + extra = Extra.allow
198 +
199 def to_dict(self):
200 return self.dict(exclude_none=True)
201
backend/app/middleware/license.py
+189 -9
@@ -2,9 +2,11 @@ import os
2 from datetime import datetime as dt
3 from enum import Enum
4 from typing import Any
5 +from typing import Dict
6 from typing import List
7 from typing import Optional
8
9 +import requests
10 from fastapi import APIRouter
11 from fastapi import Depends
12 from fastapi import HTTPException
@@ -19,10 +21,34 @@ from pydantic import Field
21 from sqlalchemy import select
22 from sqlalchemy.ext.asyncio import AsyncSession
23
24 +from app.connectors.schema import UpdateConnector
25 +from app.connectors.services import ConnectorServices
26 from app.db.db_session import get_db
27 from app.db.universal_models import License
28
29
30 +class ThreatIntelRegisterRequest(BaseModel):
31 + """
32 + A Pydantic model for registering to the SOCFortress Threat Intel Feed which
33 + requires a valid API key.
34 + """
35 +
36 + customer_name: str = Field(..., description="The customer name")
37 + requested_by: str = Field("CoPilot", description="The system requesting access")
38 + registration_url: str = Field("https://intel.socfortress.co/register", description="The registration URL")
39 + requesting_api_key: str = Field(os.getenv("COPILOT_API_KEY"), description="The requesting API key")
40 +
41 +
42 +class ThreatIntelRegisterResponse(BaseModel):
43 + """
44 + A Pydantic model for the response to registering to the SOCFortress Threat Intel Feed.
45 + """
46 +
47 + api_key: str = Field(..., description="The API key")
48 + success: bool = Field(..., description="Indicates if the registration was successful")
49 + message: str = Field(..., description="The message")
50 +
51 +
52 class ReplaceLicenseRequest(BaseModel):
53 """
54 A Pydantic model for replacing a license.
@@ -66,6 +92,12 @@ class CreateCustomerKeyResponseModel(BaseModel):
92 response: List[Optional[CreateCustomerKeyResult]]
93
94
95 +class CreateCustomerKeyRouteResponse(BaseModel):
96 + response: List[Optional[CreateCustomerKeyResult]]
97 + success: bool = Field(..., title="Indicates if the key creation was successful")
98 + message: str = Field(..., title="The message")
99 +
100 +
101 class Customer(BaseModel):
102 Id: int
103 Name: str
@@ -110,6 +142,24 @@ class LicenseResponse(BaseModel):
142 reseller: Optional[Any]
143
144
145 +class VerifyLicenseResponse(BaseModel):
146 + license: LicenseResponse
147 + success: bool
148 + message: str
149 +
150 +
151 +class GetLicenseResponse(BaseModel):
152 + license_key: str
153 + success: bool
154 + message: str
155 +
156 +
157 +class GetLicenseFeaturesResponse(BaseModel):
158 + features: List[str]
159 + success: bool
160 + message: str
161 +
162 +
163 class Feature(Enum):
164 MIMECAST = "MIMECAST"
165 SAP_SIEM = "SAP SIEM"
@@ -129,6 +179,19 @@ class Feature(Enum):
179 return feature_map.get(feature_name)
180
181
182 +class SubscriptionCatalog(str, Enum):
183 + """
184 + The subscription catalog.
185 + """
186 +
187 + MIMECAST = (
188 + "Integrate your SIEM stack with Mimecast to detect and respond to advanced threats."
189 + "This integration includes ingesting of Mimecast logs into your SIEM stack, Grafana dashboards,"
190 + "and alerts for advanced threat detection.",
191 + )
192 + HUNTRESS = "Integrate your SIEM stack with Huntress to detect and respond to advanced threats."
193 +
194 +
195 license_router = APIRouter()
196
197
@@ -189,7 +252,6 @@ def create_key(auth, request):
252 email=request.email,
253 company_name=request.company_name,
254 )
192 - logger.info(result)
255 result = CreateCustomerKeyResponseModel(response=[result])
256 return result
257
@@ -220,6 +282,7 @@ async def get_license(session: AsyncSession) -> License:
282
283
284 def check_license(license: License):
285 + logger.info(f"Checking license: {license}")
286 result, _ = Key.activate(
287 token=get_auth_token(),
288 rsa_pub_key=get_rsa_pub_key(),
@@ -299,9 +362,10 @@ async def create_trial_license_key(request: CreateLicenseRequest, session: Async
362
363 @license_router.post(
364 "/create_new_key",
365 + response_model=CreateCustomerKeyRouteResponse,
366 description="Create a new license key",
367 )
304 -async def create_new_license_key(request: CreateLicenseRequest, session: AsyncSession = Depends(get_db)):
368 +async def create_new_license_key(request: CreateLicenseRequest, session: AsyncSession = Depends(get_db)) -> CreateCustomerKeyRouteResponse:
369 """
370 Create a new license key.
371
@@ -315,8 +379,9 @@ async def create_new_license_key(request: CreateLicenseRequest, session: AsyncSe
379 await check_if_license_exists(session)
380 auth = get_auth_token()
381 result = create_key(auth, request)
382 + logger.info(f"Result: {result}")
383 await add_license_to_db(session, result, request)
319 - return result
384 + return CreateCustomerKeyRouteResponse(response=result.response, success=True, message="License created successfully")
385
386
387 @license_router.post(
@@ -337,8 +402,8 @@ async def extend_license_key(period: int, session: AsyncSession = Depends(get_db
402 try:
403 license = await get_license(session)
404 logger.info(f"License: {license}")
340 - result = extend_license(license, period)
341 - return result
405 + extend_license(license, period)
406 + return {"message": "License extended successfully", "success": True}
407 except Exception as e:
408 logger.error(e)
409 raise HTTPException(status_code=400, detail="License extension failed")
@@ -346,10 +411,10 @@ async def extend_license_key(period: int, session: AsyncSession = Depends(get_db
411
412 @license_router.get(
413 "/verify_license",
349 - response_model=LicenseResponse,
414 + response_model=VerifyLicenseResponse,
415 description="Verify a license key",
416 )
352 -async def verify_license_key(session: AsyncSession = Depends(get_db)) -> LicenseResponse:
417 +async def verify_license_key(session: AsyncSession = Depends(get_db)) -> VerifyLicenseResponse:
418 """ "
419 Verify a license key.
420
@@ -359,20 +424,69 @@ async def verify_license_key(session: AsyncSession = Depends(get_db)) -> License
424 Returns:
425 LicenseVerificationResponse: A Pydantic model containing the verification status and message.
426 """
427 + license = await get_license(session)
428 try:
363 - license = await get_license(session)
429 logger.info(f"License: {license}")
430 result = check_license(license)
431 result = result.__dict__
432 logger.info(result)
433 if is_license_expired(result):
434 raise HTTPException(status_code=400, detail="License is expired")
370 - return result
435 + return VerifyLicenseResponse(license=result, success=True, message="License verified successfully")
436 except Exception as e:
437 logger.error(e)
438 raise HTTPException(status_code=400, detail="License verification failed")
439
440
441 +@license_router.get(
442 + "/get_license",
443 + description="Get a license",
444 +)
445 +async def get_license_key(session: AsyncSession = Depends(get_db)) -> GetLicenseResponse:
446 + """ "
447 + Get a license key.
448 +
449 + Args:
450 + license_key (str): The license key to verify.
451 +
452 + Returns:
453 + LicenseVerificationResponse: A Pydantic model containing the verification status and message.
454 + """
455 + license = await get_license(session)
456 + return GetLicenseResponse(license_key=license.license_key, success=True, message="License retrieved successfully")
457 +
458 +
459 +@license_router.get(
460 + "/get_license_features",
461 + response_model=GetLicenseFeaturesResponse,
462 + description="Get license features",
463 +)
464 +async def get_license_features(session: AsyncSession = Depends(get_db)) -> GetLicenseFeaturesResponse:
465 + """
466 + Get the features enabled in a license.
467 +
468 + Args:
469 + session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
470 +
471 + Returns:
472 + dict: A dictionary containing the features enabled in the license.
473 + """
474 + license = await get_license(session)
475 + try:
476 + license_details = LicenseResponse(**check_license(license).__dict__)
477 + features = {}
478 + for data_object in license_details.data_objects:
479 + features[data_object["Name"]] = data_object["IntValue"]
480 + return GetLicenseFeaturesResponse(
481 + features=[feature for feature, value in features.items() if value == 1],
482 + success=True,
483 + message="License features retrieved successfully",
484 + )
485 + except Exception as e:
486 + logger.error(e)
487 + raise HTTPException(status_code=400, detail="Failed to get license features")
488 +
489 +
490 @license_router.post(
491 "/add_feature/{feature_name}",
492 description="Add a feature to a license",
@@ -440,3 +554,69 @@ async def replace_license_in_db(request: ReplaceLicenseRequest, session: AsyncSe
554 except Exception as e:
555 logger.error(e)
556 raise HTTPException(status_code=400, detail="License replacement failed")
557 +
558 +
559 +def create_headers(request: ThreatIntelRegisterRequest) -> Dict[str, str]:
560 + return {
561 + "x-api-key": request.requesting_api_key,
562 + "Content-Type": "application/json",
563 + "module": "1.0",
564 + "SOCFortress_Threat_Intel": "c1f882d9-cd09-4f9c-81a6-71fe0fb53129",
565 + }
566 +
567 +
568 +def create_payload(request: ThreatIntelRegisterRequest) -> Dict[str, str]:
569 + return {
570 + "customer_name": request.customer_name,
571 + "requested_by": request.requested_by,
572 + }
573 +
574 +
575 +async def update_connector(response: ThreatIntelRegisterResponse, session: AsyncSession):
576 + await ConnectorServices.update_connector_by_id(
577 + connector_id=10,
578 + connector=UpdateConnector(
579 + connector_api_key=response.api_key,
580 + connector_url="https://intel.socfortress.co/search",
581 + ),
582 + session=session,
583 + )
584 +
585 +
586 +@license_router.post(
587 + "/register_to_threat_intel",
588 + description="Register to the SOCFortress Threat Intel Feed",
589 +)
590 +async def register_to_threat_intel(
591 + request: ThreatIntelRegisterRequest,
592 + session: AsyncSession = Depends(get_db),
593 +):
594 + """
595 + Register to the SOCFortress Threat Intel Feed.
596 +
597 + Args:
598 + request (ThreatIntelRegisterRequest): The request containing the customer name.
599 +
600 + Returns:
601 + ThreatIntelRegisterResponse: A Pydantic model containing the API key, success status, and message.
602 + """
603 + logger.info(f"Registering to the SOCFortress Threat Intel Feed: {request}")
604 + try:
605 + headers = create_headers(request)
606 + payload = create_payload(request)
607 + response = ThreatIntelRegisterResponse(
608 + **requests.post(
609 + request.registration_url,
610 + headers=headers,
611 + json=payload,
612 + ).json(),
613 + )
614 + await update_connector(response, session)
615 + return ThreatIntelRegisterResponse(
616 + api_key=response.api_key,
617 + success=response.success,
618 + message=response.message,
619 + )
620 + except Exception as e:
621 + logger.error(e)
622 + raise HTTPException(status_code=500, detail="Failed to register to the SOCFortress Threat Intel Feed")
docker-compose.yml
+3 -3
@@ -3,9 +3,9 @@ version: "2"
3 services:
4 copilot-backend:
5 image: ghcr.io/socfortress/copilot-backend:latest
6 - # Only expose if you want to access the docs
7 - #ports:
8 - # - "5000:5000"
6 + # Expose the Ports for Graylog Alerting and Docs
7 + ports:
8 + - "5000:5000"
9 volumes:
10 - ./data/copilot-backend-data/logs:/opt/logs
11 # Mount the copilot.db file to persist the database
frontend/package-lock.json
+317 -645
@@ -16,7 +16,7 @@
16 "@popperjs/core": "^2.11.8",
17 "@vueuse/components": "^10.9.0",
18 "@vueuse/core": "^10.9.0",
19 - "apexcharts": "^3.47.0",
19 + "apexcharts": "^3.48.0",
20 "bytes": "^3.1.2",
21 "colord": "^2.9.3",
22 "crypto-js": "^4.2.0",
@@ -38,7 +38,7 @@
38 "vue": "^3.4.21",
39 "vue-advanced-cropper": "^2.8.8",
40 "vue-highlight-words": "^3.0.1",
41 - "vue-i18n": "^9.10.1",
41 + "vue-i18n": "^9.10.2",
42 "vue-router": "^4.3.0",
43 "vue-sjv": "^0.0.6",
44 "vue3-apexcharts": "^1.5.2",
@@ -48,8 +48,8 @@
48 "devDependencies": {
49 "@clack/prompts": "^0.7.0",
50 "@iconify/vue": "^4.1.1",
51 - "@rushstack/eslint-patch": "^1.7.2",
52 - "@tsconfig/node18": "^18.2.2",
51 + "@rushstack/eslint-patch": "^1.9.0",
52 + "@tsconfig/node18": "^18.2.4",
53 "@types/bytes": "^3.1.4",
54 "@types/file-saver": "^2.0.7",
55 "@types/fs-extra": "^11.0.4",
@@ -60,7 +60,7 @@
60 "@types/lodash": "^4.17.0",
61 "@types/markdown-it": "^13.0.7",
62 "@types/markdown-it-highlightjs": "^3.3.4",
63 - "@types/node": "^20.11.27",
63 + "@types/node": "^20.11.30",
64 "@types/validator": "^13.11.9",
65 "@vitejs/plugin-vue": "^5.0.4",
66 "@vitejs/plugin-vue-jsx": "^3.1.0",
@@ -68,31 +68,31 @@
68 "@vue/eslint-config-typescript": "^13.0.0",
69 "@vue/test-utils": "^2.4.5",
70 "@vue/tsconfig": "^0.5.1",
71 - "autoprefixer": "^10.4.18",
72 - "cypress": "^13.7.0",
71 + "autoprefixer": "^10.4.19",
72 + "cypress": "^13.7.1",
73 "eslint": "^8.57.0",
74 "eslint-plugin-cypress": "^2.15.1",
75 - "eslint-plugin-vue": "^9.23.0",
75 + "eslint-plugin-vue": "^9.24.0",
76 "fs-extra": "^11.2.0",
77 "ip": "^2.0.1",
78 "jsdom": "^24.0.0",
79 "json5": "^2.2.3",
80 "npm-run-all": "^4.1.5",
81 "picocolors": "^1.0.0",
82 - "postcss": "^8.4.35",
82 + "postcss": "^8.4.38",
83 "prettier": "^3.2.5",
84 "sass": "^1.72.0",
85 "start-server-and-test": "^2.0.3",
86 "tailwind-config-viewer": "^1.7.3",
87 - "tailwindcss": "^3.4.1",
87 + "tailwindcss": "^3.4.3",
88 "taze": "^0.13.3",
89 "unplugin-vue-components": "^0.26.0",
90 - "vite": "^5.1.6",
91 - "vite-bundle-analyzer": "^0.8.3",
90 + "vite": "^5.2.6",
91 + "vite-bundle-analyzer": "^0.9.2",
92 "vite-bundle-visualizer": "^1.1.0",
93 "vite-svg-loader": "^5.1.0",
94 - "vitest": "^1.3.1",
95 - "vue-tsc": "^2.0.6"
94 + "vitest": "^1.4.0",
95 + "vue-tsc": "^2.0.7"
96 },
97 "engines": {
98 "node": ">=18.0.0"
@@ -757,9 +757,9 @@
757 "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
758 },
759 "node_modules/@esbuild/aix-ppc64": {
760 - "version": "0.19.12",
761 - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
762 - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
760 + "version": "0.20.2",
761 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
762 + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
763 "cpu": [
764 "ppc64"
765 ],
@@ -773,9 +773,9 @@
773 }
774 },
775 "node_modules/@esbuild/android-arm": {
776 - "version": "0.19.12",
777 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
778 - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
776 + "version": "0.20.2",
777 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
778 + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
779 "cpu": [
780 "arm"
781 ],
@@ -789,9 +789,9 @@
789 }
790 },
791 "node_modules/@esbuild/android-arm64": {
792 - "version": "0.19.12",
793 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
794 - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
792 + "version": "0.20.2",
793 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
794 + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
795 "cpu": [
796 "arm64"
797 ],
@@ -805,9 +805,9 @@
805 }
806 },
807 "node_modules/@esbuild/android-x64": {
808 - "version": "0.19.12",
809 - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
810 - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
808 + "version": "0.20.2",
809 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
810 + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
811 "cpu": [
812 "x64"
813 ],
@@ -821,9 +821,9 @@
821 }
822 },
823 "node_modules/@esbuild/darwin-arm64": {
824 - "version": "0.19.12",
825 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
826 - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
824 + "version": "0.20.2",
825 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
826 + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
827 "cpu": [
828 "arm64"
829 ],
@@ -837,9 +837,9 @@
837 }
838 },
839 "node_modules/@esbuild/darwin-x64": {
840 - "version": "0.19.12",
841 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
842 - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
840 + "version": "0.20.2",
841 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
842 + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
843 "cpu": [
844 "x64"
845 ],
@@ -853,9 +853,9 @@
853 }
854 },
855 "node_modules/@esbuild/freebsd-arm64": {
856 - "version": "0.19.12",
857 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
858 - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
856 + "version": "0.20.2",
857 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
858 + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
859 "cpu": [
860 "arm64"
861 ],
@@ -869,9 +869,9 @@
869 }
870 },
871 "node_modules/@esbuild/freebsd-x64": {
872 - "version": "0.19.12",
873 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
874 - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
872 + "version": "0.20.2",
873 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
874 + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
875 "cpu": [
876 "x64"
877 ],
@@ -885,9 +885,9 @@
885 }
886 },
887 "node_modules/@esbuild/linux-arm": {
888 - "version": "0.19.12",
889 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
890 - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
888 + "version": "0.20.2",
889 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
890 + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
891 "cpu": [
892 "arm"
893 ],
@@ -901,9 +901,9 @@
901 }
902 },
903 "node_modules/@esbuild/linux-arm64": {
904 - "version": "0.19.12",
905 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
906 - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
904 + "version": "0.20.2",
905 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
906 + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
907 "cpu": [
908 "arm64"
909 ],
@@ -917,9 +917,9 @@
917 }
918 },
919 "node_modules/@esbuild/linux-ia32": {
920 - "version": "0.19.12",
921 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
922 - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
920 + "version": "0.20.2",
921 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
922 + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
923 "cpu": [
924 "ia32"
925 ],
@@ -933,9 +933,9 @@
933 }
934 },
935 "node_modules/@esbuild/linux-loong64": {
936 - "version": "0.19.12",
937 - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
938 - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
936 + "version": "0.20.2",
937 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
938 + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
939 "cpu": [
940 "loong64"
941 ],
@@ -949,9 +949,9 @@
949 }
950 },
951 "node_modules/@esbuild/linux-mips64el": {
952 - "version": "0.19.12",
953 - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
954 - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
952 + "version": "0.20.2",
953 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
954 + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
955 "cpu": [
956 "mips64el"
957 ],
@@ -965,9 +965,9 @@
965 }
966 },
967 "node_modules/@esbuild/linux-ppc64": {
968 - "version": "0.19.12",
969 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
970 - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
968 + "version": "0.20.2",
969 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
970 + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
971 "cpu": [
972 "ppc64"
973 ],
@@ -981,9 +981,9 @@
981 }
982 },
983 "node_modules/@esbuild/linux-riscv64": {
984 - "version": "0.19.12",
985 - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
986 - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
984 + "version": "0.20.2",
985 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
986 + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
987 "cpu": [
988 "riscv64"
989 ],
@@ -997,9 +997,9 @@
997 }
998 },
999 "node_modules/@esbuild/linux-s390x": {
1000 - "version": "0.19.12",
1001 - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
1002 - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
1000 + "version": "0.20.2",
1001 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
1002 + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
1003 "cpu": [
1004 "s390x"
1005 ],
@@ -1013,9 +1013,9 @@
1013 }
1014 },
1015 "node_modules/@esbuild/linux-x64": {
1016 - "version": "0.19.12",
1017 - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
1018 - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
1016 + "version": "0.20.2",
1017 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
1018 + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
1019 "cpu": [
1020 "x64"
1021 ],
@@ -1029,9 +1029,9 @@
1029 }
1030 },
1031 "node_modules/@esbuild/netbsd-x64": {
1032 - "version": "0.19.12",
1033 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
1034 - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
1032 + "version": "0.20.2",
1033 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
1034 + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
1035 "cpu": [
1036 "x64"
1037 ],
@@ -1045,9 +1045,9 @@
1045 }
1046 },
1047 "node_modules/@esbuild/openbsd-x64": {
1048 - "version": "0.19.12",
1049 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
1050 - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
1048 + "version": "0.20.2",
1049 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
1050 + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
1051 "cpu": [
1052 "x64"
1053 ],
@@ -1061,9 +1061,9 @@
1061 }
1062 },
1063 "node_modules/@esbuild/sunos-x64": {
1064 - "version": "0.19.12",
1065 - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
1066 - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
1064 + "version": "0.20.2",
1065 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
1066 + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
1067 "cpu": [
1068 "x64"
1069 ],
@@ -1077,9 +1077,9 @@
1077 }
1078 },
1079 "node_modules/@esbuild/win32-arm64": {
1080 - "version": "0.19.12",
1081 - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
1082 - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
1080 + "version": "0.20.2",
1081 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
1082 + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
1083 "cpu": [
1084 "arm64"
1085 ],
@@ -1093,9 +1093,9 @@
1093 }
1094 },
1095 "node_modules/@esbuild/win32-ia32": {
1096 - "version": "0.19.12",
1097 - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
1098 - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
1096 + "version": "0.20.2",
1097 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
1098 + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
1099 "cpu": [
1100 "ia32"
1101 ],
@@ -1109,9 +1109,9 @@
1109 }
1110 },
1111 "node_modules/@esbuild/win32-x64": {
1112 - "version": "0.19.12",
1113 - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
1114 - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
1112 + "version": "0.20.2",
1113 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
1114 + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
1115 "cpu": [
1116 "x64"
1117 ],
@@ -1340,12 +1340,12 @@
1340 }
1341 },
1342 "node_modules/@intlify/core-base": {
1343 - "version": "9.10.1",
1344 - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.10.1.tgz",
1345 - "integrity": "sha512-0+Wtjj04GIyglh5KKiNjRwgjpHrhqqGZhaKY/QVjjogWKZq5WHROrTi84pNVsRN18QynyPmjtsVUWqFKPQ45xQ==",
1343 + "version": "9.10.2",
1344 + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.10.2.tgz",
1345 + "integrity": "sha512-HGStVnKobsJL0DoYIyRCGXBH63DMQqEZxDUGrkNI05FuTcruYUtOAxyL3zoAZu/uDGO6mcUvm3VXBaHG2GdZCg==",
1346 "dependencies": {
1347 - "@intlify/message-compiler": "9.10.1",
1348 - "@intlify/shared": "9.10.1"
1347 + "@intlify/message-compiler": "9.10.2",
1348 + "@intlify/shared": "9.10.2"
1349 },
1350 "engines": {
1351 "node": ">= 16"
@@ -1355,11 +1355,11 @@
1355 }
1356 },
1357 "node_modules/@intlify/message-compiler": {
1358 - "version": "9.10.1",
1359 - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.10.1.tgz",
1360 - "integrity": "sha512-b68UTmRhgZfswJZI7VAgW6BXZK5JOpoi5swMLGr4j6ss2XbFY13kiw+Hu+xYAfulMPSapcHzdWHnq21VGnMCnA==",
1358 + "version": "9.10.2",
1359 + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.10.2.tgz",
1360 + "integrity": "sha512-ntY/kfBwQRtX5Zh6wL8cSATujPzWW2ZQd1QwKyWwAy5fMqJyyixHMeovN4fmEyCqSu+hFfYOE63nU94evsy4YA==",
1361 "dependencies": {
1362 - "@intlify/shared": "9.10.1",
1362 + "@intlify/shared": "9.10.2",
1363 "source-map-js": "^1.0.2"
1364 },
1365 "engines": {
@@ -1370,9 +1370,9 @@
1370 }
1371 },
1372 "node_modules/@intlify/shared": {
1373 - "version": "9.10.1",
1374 - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.10.1.tgz",
1375 - "integrity": "sha512-liyH3UMoglHBUn70iCYcy9CQlInx/lp50W2aeSxqqrvmG+LDj/Jj7tBJhBoQL4fECkldGhbmW0g2ommHfL6Wmw==",
1373 + "version": "9.10.2",
1374 + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.10.2.tgz",
1375 + "integrity": "sha512-ttHCAJkRy7R5W2S9RVnN9KYQYPIpV2+GiS79T4EE37nrPyH6/1SrOh3bmdCRC1T3ocL8qCDx7x2lBJ0xaITU7Q==",
1376 "engines": {
1377 "node": ">= 16"
1378 },
@@ -1897,12 +1897,6 @@
1897 "url": "https://opencollective.com/unts"
1898 }
1899 },
1900 - "node_modules/@polka/url": {
1901 - "version": "1.0.0-next.24",
1902 - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz",
1903 - "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==",
1904 - "dev": true
1905 - },
1900 "node_modules/@popperjs/core": {
1901 "version": "2.11.8",
1902 "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -1935,9 +1929,9 @@
1929 }
1930 },
1931 "node_modules/@rollup/rollup-android-arm-eabi": {
1938 - "version": "4.12.0",
1939 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
1940 - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
1932 + "version": "4.13.2",
1933 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz",
1934 + "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==",
1935 "cpu": [
1936 "arm"
1937 ],
@@ -1948,9 +1942,9 @@
1942 ]
1943 },
1944 "node_modules/@rollup/rollup-android-arm64": {
1951 - "version": "4.12.0",
1952 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
1953 - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
1945 + "version": "4.13.2",
1946 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz",
1947 + "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==",
1948 "cpu": [
1949 "arm64"
1950 ],
@@ -1961,9 +1955,9 @@
1955 ]
1956 },
1957 "node_modules/@rollup/rollup-darwin-arm64": {
1964 - "version": "4.12.0",
1965 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
1966 - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
1958 + "version": "4.13.2",
1959 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz",
1960 + "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==",
1961 "cpu": [
1962 "arm64"
1963 ],
@@ -1974,9 +1968,9 @@
1968 ]
1969 },
1970 "node_modules/@rollup/rollup-darwin-x64": {
1977 - "version": "4.12.0",
1978 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
1979 - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
1971 + "version": "4.13.2",
1972 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz",
1973 + "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==",
1974 "cpu": [
1975 "x64"
1976 ],
@@ -1987,9 +1981,9 @@
1981 ]
1982 },
1983 "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
1990 - "version": "4.12.0",
1991 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
1992 - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
1984 + "version": "4.13.2",
1985 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz",
1986 + "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==",
1987 "cpu": [
1988 "arm"
1989 ],
@@ -2000,9 +1994,9 @@
1994 ]
1995 },
1996 "node_modules/@rollup/rollup-linux-arm64-gnu": {
2003 - "version": "4.12.0",
2004 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
2005 - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
1997 + "version": "4.13.2",
1998 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz",
1999 + "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==",
2000 "cpu": [
2001 "arm64"
2002 ],
@@ -2013,9 +2007,9 @@
2007 ]
2008 },
2009 "node_modules/@rollup/rollup-linux-arm64-musl": {
2016 - "version": "4.12.0",
2017 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
2018 - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
2010 + "version": "4.13.2",
2011 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz",
2012 + "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==",
2013 "cpu": [
2014 "arm64"
2015 ],
@@ -2025,10 +2019,23 @@
2019 "linux"
2020 ]
2021 },
2022 + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
2023 + "version": "4.13.2",
2024 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz",
2025 + "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==",
2026 + "cpu": [
2027 + "ppc64le"
2028 + ],
2029 + "dev": true,
2030 + "optional": true,
2031 + "os": [
2032 + "linux"
2033 + ]
2034 + },
2035 "node_modules/@rollup/rollup-linux-riscv64-gnu": {
2029 - "version": "4.12.0",
2030 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
2031 - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
2036 + "version": "4.13.2",
2037 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz",
2038 + "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==",
2039 "cpu": [
2040 "riscv64"
2041 ],
@@ -2038,10 +2045,23 @@
2045 "linux"
2046 ]
2047 },
2048 + "node_modules/@rollup/rollup-linux-s390x-gnu": {
2049 + "version": "4.13.2",
2050 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz",
2051 + "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==",
2052 + "cpu": [
2053 + "s390x"
2054 + ],
2055 + "dev": true,
2056 + "optional": true,
2057 + "os": [
2058 + "linux"
2059 + ]
2060 + },
2061 "node_modules/@rollup/rollup-linux-x64-gnu": {
2042 - "version": "4.12.0",
2043 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
2044 - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
2062 + "version": "4.13.2",
2063 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz",
2064 + "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==",
2065 "cpu": [
2066 "x64"
2067 ],
@@ -2052,9 +2072,9 @@
2072 ]
2073 },
2074 "node_modules/@rollup/rollup-linux-x64-musl": {
2055 - "version": "4.12.0",
2056 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
2057 - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
2075 + "version": "4.13.2",
2076 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz",
2077 + "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==",
2078 "cpu": [
2079 "x64"
2080 ],
@@ -2065,9 +2085,9 @@
2085 ]
2086 },
2087 "node_modules/@rollup/rollup-win32-arm64-msvc": {
2068 - "version": "4.12.0",
2069 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
2070 - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
2088 + "version": "4.13.2",
2089 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz",
2090 + "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==",
2091 "cpu": [
2092 "arm64"
2093 ],
@@ -2078,9 +2098,9 @@
2098 ]
2099 },
2100 "node_modules/@rollup/rollup-win32-ia32-msvc": {
2081 - "version": "4.12.0",
2082 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
2083 - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
2101 + "version": "4.13.2",
2102 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz",
2103 + "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==",
2104 "cpu": [
2105 "ia32"
2106 ],
@@ -2091,9 +2111,9 @@
2111 ]
2112 },
2113 "node_modules/@rollup/rollup-win32-x64-msvc": {
2094 - "version": "4.12.0",
2095 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
2096 - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
2114 + "version": "4.13.2",
2115 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz",
2116 + "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==",
2117 "cpu": [
2118 "x64"
2119 ],
@@ -2104,9 +2124,9 @@
2124 ]
2125 },
2126 "node_modules/@rushstack/eslint-patch": {
2107 - "version": "1.7.2",
2108 - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz",
2109 - "integrity": "sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==",
2127 + "version": "1.9.0",
2128 + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.9.0.tgz",
2129 + "integrity": "sha512-AAWymnpvHbGty1BmgbdfbqQDboXs6xN6h2yAacO4yKVyyUUBnpYkp+P9jjPrV9zrAGw7JVVriRtGOHPInnfjZQ==",
2130 "dev": true
2131 },
2132 "node_modules/@sideway/address": {
@@ -2218,9 +2238,9 @@
2238 }
2239 },
2240 "node_modules/@tsconfig/node18": {
2221 - "version": "18.2.2",
2222 - "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.2.tgz",
2223 - "integrity": "sha512-d6McJeGsuoRlwWZmVIeE8CUA27lu6jLjvv1JzqmpsytOYYbVi1tHZEnwCNVOXnj4pyLvneZlFlpXUK+X9wBWyw==",
2241 + "version": "18.2.4",
2242 + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz",
2243 + "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==",
2244 "dev": true
2245 },
2246 "node_modules/@tufjs/canonical-json": {
@@ -2388,9 +2408,9 @@
2408 "dev": true
2409 },
2410 "node_modules/@types/node": {
2391 - "version": "20.11.28",
2392 - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz",
2393 - "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==",
2411 + "version": "20.11.30",
2412 + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
2413 + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
2414 "dev": true,
2415 "dependencies": {
2416 "undici-types": "~5.26.4"
@@ -2723,13 +2743,13 @@
2743 }
2744 },
2745 "node_modules/@vitest/expect": {
2726 - "version": "1.3.1",
2727 - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz",
2728 - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==",
2746 + "version": "1.4.0",
2747 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz",
2748 + "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==",
2749 "dev": true,
2750 "dependencies": {
2731 - "@vitest/spy": "1.3.1",
2732 - "@vitest/utils": "1.3.1",
2751 + "@vitest/spy": "1.4.0",
2752 + "@vitest/utils": "1.4.0",
2753 "chai": "^4.3.10"
2754 },
2755 "funding": {
@@ -2737,12 +2757,12 @@
2757 }
2758 },
2759 "node_modules/@vitest/runner": {
2740 - "version": "1.3.1",
2741 - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz",
2742 - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==",
2760 + "version": "1.4.0",
2761 + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz",
2762 + "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==",
2763 "dev": true,
2764 "dependencies": {
2745 - "@vitest/utils": "1.3.1",
2765 + "@vitest/utils": "1.4.0",
2766 "p-limit": "^5.0.0",
2767 "pathe": "^1.1.1"
2768 },
@@ -2778,9 +2798,9 @@
2798 }
2799 },
2800 "node_modules/@vitest/snapshot": {
2781 - "version": "1.3.1",
2782 - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz",
2783 - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==",
2801 + "version": "1.4.0",
2802 + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz",
2803 + "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==",
2804 "dev": true,
2805 "dependencies": {
2806 "magic-string": "^0.30.5",
@@ -2792,9 +2812,9 @@
2812 }
2813 },
2814 "node_modules/@vitest/spy": {
2795 - "version": "1.3.1",
2796 - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz",
2797 - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==",
2815 + "version": "1.4.0",
2816 + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz",
2817 + "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==",
2818 "dev": true,
2819 "dependencies": {
2820 "tinyspy": "^2.2.0"
@@ -2804,9 +2824,9 @@
2824 }
2825 },
2826 "node_modules/@vitest/utils": {
2807 - "version": "1.3.1",
2808 - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz",
2809 - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==",
2827 + "version": "1.4.0",
2828 + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz",
2829 + "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==",
2830 "dev": true,
2831 "dependencies": {
2832 "diff-sequences": "^29.6.3",
@@ -2828,30 +2848,30 @@
2848 }
2849 },
2850 "node_modules/@volar/language-core": {
2831 - "version": "2.1.2",
2832 - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.1.2.tgz",
2833 - "integrity": "sha512-5qsDp0Gf6fE09UWCeK7bkVn6NxMwC9OqFWQkMMkeej8h8XjyABPdRygC2RCrqDrfVdGijqlMQeXs6yRS+vfZYA==",
2851 + "version": "2.1.6",
2852 + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.1.6.tgz",
2853 + "integrity": "sha512-pAlMCGX/HatBSiDFMdMyqUshkbwWbLxpN/RL7HCQDOo2gYBE+uS+nanosLc1qR6pTQ/U8q00xt8bdrrAFPSC0A==",
2854 "dev": true,
2855 "dependencies": {
2836 - "@volar/source-map": "2.1.2"
2856 + "@volar/source-map": "2.1.6"
2857 }
2858 },
2859 "node_modules/@volar/source-map": {
2840 - "version": "2.1.2",
2841 - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.1.2.tgz",
2842 - "integrity": "sha512-yFJqsuLm1OaWrsz9E3yd3bJcYIlHqdZ8MbmIoZLrAzMYQDcoF26/INIhgziEXSdyHc8xd7rd/tJdSnUyh0gH4Q==",
2860 + "version": "2.1.6",
2861 + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.1.6.tgz",
2862 + "integrity": "sha512-TeyH8pHHonRCHYI91J7fWUoxi0zWV8whZTVRlsWHSYfjm58Blalkf9LrZ+pj6OiverPTmrHRkBsG17ScQyWECw==",
2863 "dev": true,
2864 "dependencies": {
2865 "muggle-string": "^0.4.0"
2866 }
2867 },
2868 "node_modules/@volar/typescript": {
2849 - "version": "2.1.2",
2850 - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.1.2.tgz",
2851 - "integrity": "sha512-lhTancZqamvaLvoz0u/uth8dpudENNt2LFZOWCw9JZiX14xRFhdhfzmphiCRb7am9E6qAJSbdS/gMt1utXAoHQ==",
2869 + "version": "2.1.6",
2870 + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.1.6.tgz",
2871 + "integrity": "sha512-JgPGhORHqXuyC3r6skPmPHIZj4LoMmGlYErFTuPNBq9Nhc9VTv7ctHY7A3jMN3ngKEfRrfnUcwXHztvdSQqNfw==",
2872 "dev": true,
2873 "dependencies": {
2854 - "@volar/language-core": "2.1.2",
2874 + "@volar/language-core": "2.1.6",
2875 "path-browserify": "^1.0.1"
2876 }
2877 },
@@ -3184,12 +3204,12 @@
3204 }
3205 },
3206 "node_modules/@vue/language-core": {
3187 - "version": "2.0.6",
3188 - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.6.tgz",
3189 - "integrity": "sha512-UzqU12tzf9XLqRO3TiWPwRNpP4fyUzE6MAfOQWQNZ4jy6a30ARRUpmODDKq6O8C4goMc2AlPqTmjOHPjHkilSg==",
3207 + "version": "2.0.7",
3208 + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.7.tgz",
3209 + "integrity": "sha512-Vh1yZX3XmYjn9yYLkjU8DN6L0ceBtEcapqiyclHne8guG84IaTzqtvizZB1Yfxm3h6m7EIvjerLO5fvOZO6IIQ==",
3210 "dev": true,
3211 "dependencies": {
3192 - "@volar/language-core": "~2.1.2",
3212 + "@volar/language-core": "~2.1.3",
3213 "@vue/compiler-dom": "^3.4.0",
3214 "@vue/shared": "^3.4.0",
3215 "computeds": "^0.0.1",
@@ -3555,9 +3575,9 @@
3575 }
3576 },
3577 "node_modules/apexcharts": {
3558 - "version": "3.47.0",
3559 - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.47.0.tgz",
3560 - "integrity": "sha512-s/fgNCA69b8lJdhI3R7Z+/Df47RPplLyHwuvttecR+aaZ3/Pm6wHYPiAGjqDNbVsMGXhuA9mcOpIYU5ZWeSdeg==",
3578 + "version": "3.48.0",
3579 + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.48.0.tgz",
3580 + "integrity": "sha512-Lhpj1Ij6lKlrUke8gf+P+SE6uGUn+Pe1TnCJ+zqrY0YMvbqM3LMb1lY+eybbTczUyk0RmMZomlTa2NgX2EUs4Q==",
3581 "dependencies": {
3582 "@yr/monotone-cubic-spline": "^1.0.3",
3583 "svg.draggable.js": "^2.2.2",
@@ -3708,9 +3728,9 @@
3728 }
3729 },
3730 "node_modules/autoprefixer": {
3711 - "version": "10.4.18",
3712 - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
3713 - "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==",
3731 + "version": "10.4.19",
3732 + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
3733 + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
3734 "dev": true,
3735 "funding": [
3736 {
@@ -3728,7 +3748,7 @@
3748 ],
3749 "dependencies": {
3750 "browserslist": "^4.23.0",
3731 - "caniuse-lite": "^1.0.30001591",
3751 + "caniuse-lite": "^1.0.30001599",
3752 "fraction.js": "^4.3.7",
3753 "normalize-range": "^0.1.2",
3754 "picocolors": "^1.0.0",
@@ -3848,15 +3868,6 @@
3868 "tweetnacl": "^0.14.3"
3869 }
3870 },
3851 - "node_modules/big-integer": {
3852 - "version": "1.6.52",
3853 - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
3854 - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
3855 - "dev": true,
3856 - "engines": {
3857 - "node": ">=0.6"
3858 - }
3859 - },
3871 "node_modules/binary-extensions": {
3872 "version": "2.2.0",
3873 "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -3883,18 +3894,6 @@
3894 "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
3895 "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
3896 },
3886 - "node_modules/bplist-parser": {
3887 - "version": "0.2.0",
3888 - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
3889 - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
3890 - "dev": true,
3891 - "dependencies": {
3892 - "big-integer": "^1.6.44"
3893 - },
3894 - "engines": {
3895 - "node": ">= 5.10.0"
3896 - }
3897 - },
3897 "node_modules/brace-expansion": {
3898 "version": "2.0.1",
3899 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -3988,21 +3987,6 @@
3987 "semver": "^7.0.0"
3988 }
3989 },
3991 - "node_modules/bundle-name": {
3992 - "version": "3.0.0",
3993 - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
3994 - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
3995 - "dev": true,
3996 - "dependencies": {
3997 - "run-applescript": "^5.0.0"
3998 - },
3999 - "engines": {
4000 - "node": ">=12"
4001 - },
4002 - "funding": {
4003 - "url": "https://github.com/sponsors/sindresorhus"
4004 - }
4005 - },
3990 "node_modules/bytes": {
3991 "version": "3.1.2",
3992 "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -4123,9 +4107,9 @@
4107 }
4108 },
4109 "node_modules/caniuse-lite": {
4126 - "version": "1.0.30001593",
4127 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz",
4128 - "integrity": "sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==",
4110 + "version": "1.0.30001600",
4111 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
4112 + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
4113 "dev": true,
4114 "funding": [
4115 {
@@ -4677,9 +4661,9 @@
4661 "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
4662 },
4663 "node_modules/cypress": {
4680 - "version": "13.7.0",
4681 - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.0.tgz",
4682 - "integrity": "sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==",
4664 + "version": "13.7.1",
4665 + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.1.tgz",
4666 + "integrity": "sha512-4u/rpFNxOFCoFX/Z5h+uwlkBO4mWzAjveURi3vqdSu56HPvVdyGTxGw4XKGWt399Y1JwIn9E1L9uMXQpc0o55w==",
4667 "dev": true,
4668 "hasInstallScript": true,
4669 "dependencies": {
@@ -4951,162 +4935,6 @@
4935 "node": ">=0.10.0"
4936 }
4937 },
4954 - "node_modules/default-browser": {
4955 - "version": "4.0.0",
4956 - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
4957 - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
4958 - "dev": true,
4959 - "dependencies": {
4960 - "bundle-name": "^3.0.0",
4961 - "default-browser-id": "^3.0.0",
4962 - "execa": "^7.1.1",
4963 - "titleize": "^3.0.0"
4964 - },
4965 - "engines": {
4966 - "node": ">=14.16"
4967 - },
4968 - "funding": {
4969 - "url": "https://github.com/sponsors/sindresorhus"
4970 - }
4971 - },
4972 - "node_modules/default-browser-id": {
4973 - "version": "3.0.0",
4974 - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
4975 - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
4976 - "dev": true,
4977 - "dependencies": {
4978 - "bplist-parser": "^0.2.0",
4979 - "untildify": "^4.0.0"
4980 - },
4981 - "engines": {
4982 - "node": ">=12"
4983 - },
4984 - "funding": {
4985 - "url": "https://github.com/sponsors/sindresorhus"
4986 - }
4987 - },
4988 - "node_modules/default-browser/node_modules/execa": {
4989 - "version": "7.2.0",
4990 - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
4991 - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
4992 - "dev": true,
4993 - "dependencies": {
4994 - "cross-spawn": "^7.0.3",
4995 - "get-stream": "^6.0.1",
4996 - "human-signals": "^4.3.0",
4997 - "is-stream": "^3.0.0",
4998 - "merge-stream": "^2.0.0",
4999 - "npm-run-path": "^5.1.0",
5000 - "onetime": "^6.0.0",
5001 - "signal-exit": "^3.0.7",
5002 - "strip-final-newline": "^3.0.0"
5003 - },
5004 - "engines": {
5005 - "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
5006 - },
5007 - "funding": {
5008 - "url": "https://github.com/sindresorhus/execa?sponsor=1"
5009 - }
5010 - },
5011 - "node_modules/default-browser/node_modules/get-stream": {
5012 - "version": "6.0.1",
5013 - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
5014 - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
5015 - "dev": true,
5016 - "engines": {
5017 - "node": ">=10"
5018 - },
5019 - "funding": {
5020 - "url": "https://github.com/sponsors/sindresorhus"
5021 - }
5022 - },
5023 - "node_modules/default-browser/node_modules/human-signals": {
5024 - "version": "4.3.1",
5025 - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
5026 - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
5027 - "dev": true,
5028 - "engines": {
5029 - "node": ">=14.18.0"
5030 - }
5031 - },
5032 - "node_modules/default-browser/node_modules/is-stream": {
5033 - "version": "3.0.0",
5034 - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
5035 - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
5036 - "dev": true,
5037 - "engines": {
5038 - "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
5039 - },
5040 - "funding": {
5041 - "url": "https://github.com/sponsors/sindresorhus"
5042 - }
5043 - },
5044 - "node_modules/default-browser/node_modules/mimic-fn": {
5045 - "version": "4.0.0",
5046 - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
5047 - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
5048 - "dev": true,
5049 - "engines": {
5050 - "node": ">=12"
5051 - },
5052 - "funding": {
5053 - "url": "https://github.com/sponsors/sindresorhus"
5054 - }
5055 - },
5056 - "node_modules/default-browser/node_modules/npm-run-path": {
5057 - "version": "5.3.0",
5058 - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
5059 - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
5060 - "dev": true,
5061 - "dependencies": {
5062 - "path-key": "^4.0.0"
5063 - },
5064 - "engines": {
5065 - "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
5066 - },
5067 - "funding": {
5068 - "url": "https://github.com/sponsors/sindresorhus"
5069 - }
5070 - },
5071 - "node_modules/default-browser/node_modules/onetime": {
5072 - "version": "6.0.0",
5073 - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
5074 - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
5075 - "dev": true,
5076 - "dependencies": {
5077 - "mimic-fn": "^4.0.0"
5078 - },
5079 - "engines": {
5080 - "node": ">=12"
5081 - },
5082 - "funding": {
5083 - "url": "https://github.com/sponsors/sindresorhus"
5084 - }
5085 - },
5086 - "node_modules/default-browser/node_modules/path-key": {
5087 - "version": "4.0.0",
5088 - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
5089 - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
5090 - "dev": true,
5091 - "engines": {
5092 - "node": ">=12"
5093 - },
5094 - "funding": {
5095 - "url": "https://github.com/sponsors/sindresorhus"
5096 - }
5097 - },
5098 - "node_modules/default-browser/node_modules/strip-final-newline": {
5099 - "version": "3.0.0",
5100 - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
5101 - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
5102 - "dev": true,
5103 - "engines": {
5104 - "node": ">=12"
5105 - },
5106 - "funding": {
5107 - "url": "https://github.com/sponsors/sindresorhus"
5108 - }
5109 - },
4938 "node_modules/define-data-property": {
4939 "version": "1.1.4",
4940 "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -5581,9 +5409,9 @@
5409 }
5410 },
5411 "node_modules/esbuild": {
5584 - "version": "0.19.12",
5585 - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
5586 - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
5412 + "version": "0.20.2",
5413 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
5414 + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
5415 "dev": true,
5416 "hasInstallScript": true,
5417 "bin": {
@@ -5593,29 +5421,29 @@
5421 "node": ">=12"
5422 },
5423 "optionalDependencies": {
5596 - "@esbuild/aix-ppc64": "0.19.12",
5597 - "@esbuild/android-arm": "0.19.12",
5598 - "@esbuild/android-arm64": "0.19.12",
5599 - "@esbuild/android-x64": "0.19.12",
5600 - "@esbuild/darwin-arm64": "0.19.12",
5601 - "@esbuild/darwin-x64": "0.19.12",
5602 - "@esbuild/freebsd-arm64": "0.19.12",
5603 - "@esbuild/freebsd-x64": "0.19.12",
5604 - "@esbuild/linux-arm": "0.19.12",
5605 - "@esbuild/linux-arm64": "0.19.12",
5606 - "@esbuild/linux-ia32": "0.19.12",
5607 - "@esbuild/linux-loong64": "0.19.12",
5608 - "@esbuild/linux-mips64el": "0.19.12",
5609 - "@esbuild/linux-ppc64": "0.19.12",
5610 - "@esbuild/linux-riscv64": "0.19.12",
5611 - "@esbuild/linux-s390x": "0.19.12",
5612 - "@esbuild/linux-x64": "0.19.12",
5613 - "@esbuild/netbsd-x64": "0.19.12",
5614 - "@esbuild/openbsd-x64": "0.19.12",
5615 - "@esbuild/sunos-x64": "0.19.12",
5616 - "@esbuild/win32-arm64": "0.19.12",
5617 - "@esbuild/win32-ia32": "0.19.12",
5618 - "@esbuild/win32-x64": "0.19.12"
5424 + "@esbuild/aix-ppc64": "0.20.2",
5425 + "@esbuild/android-arm": "0.20.2",
5426 + "@esbuild/android-arm64": "0.20.2",
5427 + "@esbuild/android-x64": "0.20.2",
5428 + "@esbuild/darwin-arm64": "0.20.2",
5429 + "@esbuild/darwin-x64": "0.20.2",
5430 + "@esbuild/freebsd-arm64": "0.20.2",
5431 + "@esbuild/freebsd-x64": "0.20.2",
5432 + "@esbuild/linux-arm": "0.20.2",
5433 + "@esbuild/linux-arm64": "0.20.2",
5434 + "@esbuild/linux-ia32": "0.20.2",
5435 + "@esbuild/linux-loong64": "0.20.2",
5436 + "@esbuild/linux-mips64el": "0.20.2",
5437 + "@esbuild/linux-ppc64": "0.20.2",
5438 + "@esbuild/linux-riscv64": "0.20.2",
5439 + "@esbuild/linux-s390x": "0.20.2",
5440 + "@esbuild/linux-x64": "0.20.2",
5441 + "@esbuild/netbsd-x64": "0.20.2",
5442 + "@esbuild/openbsd-x64": "0.20.2",
5443 + "@esbuild/sunos-x64": "0.20.2",
5444 + "@esbuild/win32-arm64": "0.20.2",
5445 + "@esbuild/win32-ia32": "0.20.2",
5446 + "@esbuild/win32-x64": "0.20.2"
5447 }
5448 },
5449 "node_modules/escalade": {
@@ -5769,11 +5597,12 @@
5597 }
5598 },
5599 "node_modules/eslint-plugin-vue": {
5772 - "version": "9.23.0",
5773 - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.23.0.tgz",
5774 - "integrity": "sha512-Bqd/b7hGYGrlV+wP/g77tjyFmp81lh5TMw0be9093X02SyelxRRfCI6/IsGq/J7Um0YwB9s0Ry0wlFyjPdmtUw==",
5600 + "version": "9.24.0",
5601 + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz",
5602 + "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==",
5603 "dependencies": {
5604 "@eslint-community/eslint-utils": "^4.4.0",
5605 + "globals": "^13.24.0",
5606 "natural-compare": "^1.4.0",
5607 "nth-check": "^2.1.1",
5608 "postcss-selector-parser": "^6.0.15",
@@ -7222,39 +7051,6 @@
7051 "node": ">=0.10.0"
7052 }
7053 },
7225 - "node_modules/is-inside-container": {
7226 - "version": "1.0.0",
7227 - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
7228 - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
7229 - "dev": true,
7230 - "dependencies": {
7231 - "is-docker": "^3.0.0"
7232 - },
7233 - "bin": {
7234 - "is-inside-container": "cli.js"
7235 - },
7236 - "engines": {
7237 - "node": ">=14.16"
7238 - },
7239 - "funding": {
7240 - "url": "https://github.com/sponsors/sindresorhus"
7241 - }
7242 - },
7243 - "node_modules/is-inside-container/node_modules/is-docker": {
7244 - "version": "3.0.0",
7245 - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
7246 - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
7247 - "dev": true,
7248 - "bin": {
7249 - "is-docker": "cli.js"
7250 - },
7251 - "engines": {
7252 - "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
7253 - },
7254 - "funding": {
7255 - "url": "https://github.com/sponsors/sindresorhus"
7256 - }
7257 - },
7054 "node_modules/is-installed-globally": {
7055 "version": "0.4.0",
7056 "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
@@ -8725,15 +8521,6 @@
8521 "ufo": "^1.3.2"
8522 }
8523 },
8728 - "node_modules/mrmime": {
8729 - "version": "2.0.0",
8730 - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
8731 - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
8732 - "dev": true,
8733 - "engines": {
8734 - "node": ">=10"
8735 - }
8736 - },
8524 "node_modules/ms": {
8525 "version": "2.1.2",
8526 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -9298,6 +9085,15 @@
9085 "url": "https://github.com/sponsors/sindresorhus"
9086 }
9087 },
9088 + "node_modules/opener": {
9089 + "version": "1.5.2",
9090 + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
9091 + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
9092 + "dev": true,
9093 + "bin": {
9094 + "opener": "bin/opener-bin.js"
9095 + }
9096 + },
9097 "node_modules/optionator": {
9098 "version": "0.9.3",
9099 "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -9714,9 +9510,9 @@
9510 }
9511 },
9512 "node_modules/postcss": {
9717 - "version": "8.4.35",
9718 - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
9719 - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
9513 + "version": "8.4.38",
9514 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
9515 + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
9516 "funding": [
9517 {
9518 "type": "opencollective",
@@ -9734,7 +9530,7 @@
9530 "dependencies": {
9531 "nanoid": "^3.3.7",
9532 "picocolors": "^1.0.0",
9737 - "source-map-js": "^1.0.2"
9533 + "source-map-js": "^1.2.0"
9534 },
9535 "engines": {
9536 "node": "^10 || ^12 || >=14"
@@ -10548,9 +10344,9 @@
10344 }
10345 },
10346 "node_modules/rollup": {
10551 - "version": "4.12.0",
10552 - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
10553 - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
10347 + "version": "4.13.2",
10348 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz",
10349 + "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==",
10350 "dev": true,
10351 "dependencies": {
10352 "@types/estree": "1.0.5"
@@ -10563,19 +10359,21 @@
10359 "npm": ">=8.0.0"
10360 },
10361 "optionalDependencies": {
10566 - "@rollup/rollup-android-arm-eabi": "4.12.0",
10567 - "@rollup/rollup-android-arm64": "4.12.0",
10568 - "@rollup/rollup-darwin-arm64": "4.12.0",
10569 - "@rollup/rollup-darwin-x64": "4.12.0",
10570 - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
10571 - "@rollup/rollup-linux-arm64-gnu": "4.12.0",
10572 - "@rollup/rollup-linux-arm64-musl": "4.12.0",
10573 - "@rollup/rollup-linux-riscv64-gnu": "4.12.0",
10574 - "@rollup/rollup-linux-x64-gnu": "4.12.0",
10575 - "@rollup/rollup-linux-x64-musl": "4.12.0",
10576 - "@rollup/rollup-win32-arm64-msvc": "4.12.0",
10577 - "@rollup/rollup-win32-ia32-msvc": "4.12.0",
10578 - "@rollup/rollup-win32-x64-msvc": "4.12.0",
10362 + "@rollup/rollup-android-arm-eabi": "4.13.2",
10363 + "@rollup/rollup-android-arm64": "4.13.2",
10364 + "@rollup/rollup-darwin-arm64": "4.13.2",
10365 + "@rollup/rollup-darwin-x64": "4.13.2",
10366 + "@rollup/rollup-linux-arm-gnueabihf": "4.13.2",
10367 + "@rollup/rollup-linux-arm64-gnu": "4.13.2",
10368 + "@rollup/rollup-linux-arm64-musl": "4.13.2",
10369 + "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2",
10370 + "@rollup/rollup-linux-riscv64-gnu": "4.13.2",
10371 + "@rollup/rollup-linux-s390x-gnu": "4.13.2",
10372 + "@rollup/rollup-linux-x64-gnu": "4.13.2",
10373 + "@rollup/rollup-linux-x64-musl": "4.13.2",
10374 + "@rollup/rollup-win32-arm64-msvc": "4.13.2",
10375 + "@rollup/rollup-win32-ia32-msvc": "4.13.2",
10376 + "@rollup/rollup-win32-x64-msvc": "4.13.2",
10377 "fsevents": "~2.3.2"
10378 }
10379 },
@@ -10628,65 +10426,6 @@
10426 "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==",
10427 "dev": true
10428 },
10631 - "node_modules/run-applescript": {
10632 - "version": "5.0.0",
10633 - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
10634 - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
10635 - "dev": true,
10636 - "dependencies": {
10637 - "execa": "^5.0.0"
10638 - },
10639 - "engines": {
10640 - "node": ">=12"
10641 - },
10642 - "funding": {
10643 - "url": "https://github.com/sponsors/sindresorhus"
10644 - }
10645 - },
10646 - "node_modules/run-applescript/node_modules/execa": {
10647 - "version": "5.1.1",
10648 - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
10649 - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
10650 - "dev": true,
10651 - "dependencies": {
10652 - "cross-spawn": "^7.0.3",
10653 - "get-stream": "^6.0.0",
10654 - "human-signals": "^2.1.0",
10655 - "is-stream": "^2.0.0",
10656 - "merge-stream": "^2.0.0",
10657 - "npm-run-path": "^4.0.1",
10658 - "onetime": "^5.1.2",
10659 - "signal-exit": "^3.0.3",
10660 - "strip-final-newline": "^2.0.0"
10661 - },
10662 - "engines": {
10663 - "node": ">=10"
10664 - },
10665 - "funding": {
10666 - "url": "https://github.com/sindresorhus/execa?sponsor=1"
10667 - }
10668 - },
10669 - "node_modules/run-applescript/node_modules/get-stream": {
10670 - "version": "6.0.1",
10671 - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
10672 - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
10673 - "dev": true,
10674 - "engines": {
10675 - "node": ">=10"
10676 - },
10677 - "funding": {
10678 - "url": "https://github.com/sponsors/sindresorhus"
10679 - }
10680 - },
10681 - "node_modules/run-applescript/node_modules/human-signals": {
10682 - "version": "2.1.0",
10683 - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
10684 - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
10685 - "dev": true,
10686 - "engines": {
10687 - "node": ">=10.17.0"
10688 - }
10689 - },
10429 "node_modules/run-parallel": {
10430 "version": "1.2.0",
10431 "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -10965,20 +10704,6 @@
10704 "node": "^16.14.0 || >=18.0.0"
10705 }
10706 },
10968 - "node_modules/sirv": {
10969 - "version": "2.0.4",
10970 - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
10971 - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
10972 - "dev": true,
10973 - "dependencies": {
10974 - "@polka/url": "^1.0.0-next.24",
10975 - "mrmime": "^2.0.0",
10976 - "totalist": "^3.0.0"
10977 - },
10978 - "engines": {
10979 - "node": ">= 10"
10980 - }
10981 - },
10707 "node_modules/sisteransi": {
10708 "version": "1.0.5",
10709 "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -11093,9 +10818,9 @@
10818 }
10819 },
10820 "node_modules/source-map-js": {
11096 - "version": "1.0.2",
11097 - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
11098 - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
10821 + "version": "1.2.0",
10822 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
10823 + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
10824 "engines": {
10825 "node": ">=0.10.0"
10826 }
@@ -11693,9 +11418,9 @@
11418 }
11419 },
11420 "node_modules/tailwindcss": {
11696 - "version": "3.4.1",
11697 - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
11698 - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
11421 + "version": "3.4.3",
11422 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
11423 + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
11424 "dev": true,
11425 "dependencies": {
11426 "@alloc/quick-lru": "^5.2.0",
@@ -11706,7 +11431,7 @@
11431 "fast-glob": "^3.3.0",
11432 "glob-parent": "^6.0.2",
11433 "is-glob": "^4.0.3",
11709 - "jiti": "^1.19.1",
11434 + "jiti": "^1.21.0",
11435 "lilconfig": "^2.1.0",
11436 "micromatch": "^4.0.5",
11437 "normalize-path": "^3.0.0",
@@ -12031,18 +11756,6 @@
11756 "node": ">=14.0.0"
11757 }
11758 },
12034 - "node_modules/titleize": {
12035 - "version": "3.0.0",
12036 - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
12037 - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
12038 - "dev": true,
12039 - "engines": {
12040 - "node": ">=12"
12041 - },
12042 - "funding": {
12043 - "url": "https://github.com/sponsors/sindresorhus"
12044 - }
12045 - },
11759 "node_modules/tmp": {
11760 "version": "0.2.3",
11761 "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -12081,15 +11794,6 @@
11794 "node": ">=0.6"
11795 }
11796 },
12084 - "node_modules/totalist": {
12085 - "version": "3.0.1",
12086 - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
12087 - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
12088 - "dev": true,
12089 - "engines": {
12090 - "node": ">=6"
12091 - }
12092 - },
11797 "node_modules/tough-cookie": {
11798 "version": "4.1.3",
11799 "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
@@ -12596,14 +12300,14 @@
12300 }
12301 },
12302 "node_modules/vite": {
12599 - "version": "5.1.6",
12600 - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz",
12601 - "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==",
12303 + "version": "5.2.6",
12304 + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz",
12305 + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==",
12306 "dev": true,
12307 "dependencies": {
12604 - "esbuild": "^0.19.3",
12605 - "postcss": "^8.4.35",
12606 - "rollup": "^4.2.0"
12308 + "esbuild": "^0.20.1",
12309 + "postcss": "^8.4.36",
12310 + "rollup": "^4.13.0"
12311 },
12312 "bin": {
12313 "vite": "bin/vite.js"
@@ -12651,48 +12355,16 @@
12355 }
12356 },
12357 "node_modules/vite-bundle-analyzer": {
12654 - "version": "0.8.3",
12655 - "resolved": "https://registry.npmjs.org/vite-bundle-analyzer/-/vite-bundle-analyzer-0.8.3.tgz",
12656 - "integrity": "sha512-R4QRR9lruBooroMm9sxxIGC5UxTzux7MFS5MZYu2LS2BWdd/mj4bnPnB7hSGgOEHYZHXNc+yCOPpmxauKyN5KA==",
12358 + "version": "0.9.2",
12359 + "resolved": "https://registry.npmjs.org/vite-bundle-analyzer/-/vite-bundle-analyzer-0.9.2.tgz",
12360 + "integrity": "sha512-BVnGn1JyqNSN2Tz4cPeM1Ks0w207ESvnxzBp5yhk6Z7utSkZdXfZqZolX6NlkiV6EaIRA1ha9vfC32AhxOg8kw==",
12361 "dev": true,
12362 "dependencies": {
12659 - "fast-glob": "^3.3.1",
12660 - "open": "^9.1.0",
12363 + "opener": "^1.5.2",
12364 "picocolors": "^1.0.0",
12662 - "sirv": "^2.0.3",
12365 "source-map": "^0.7.4"
12366 }
12367 },
12666 - "node_modules/vite-bundle-analyzer/node_modules/define-lazy-prop": {
12667 - "version": "3.0.0",
12668 - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
12669 - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
12670 - "dev": true,
12671 - "engines": {
12672 - "node": ">=12"
12673 - },
12674 - "funding": {
12675 - "url": "https://github.com/sponsors/sindresorhus"
12676 - }
12677 - },
12678 - "node_modules/vite-bundle-analyzer/node_modules/open": {
12679 - "version": "9.1.0",
12680 - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
12681 - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
12682 - "dev": true,
12683 - "dependencies": {
12684 - "default-browser": "^4.0.0",
12685 - "define-lazy-prop": "^3.0.0",
12686 - "is-inside-container": "^1.0.0",
12687 - "is-wsl": "^2.2.0"
12688 - },
12689 - "engines": {
12690 - "node": ">=14.16"
12691 - },
12692 - "funding": {
12693 - "url": "https://github.com/sponsors/sindresorhus"
12694 - }
12695 - },
12368 "node_modules/vite-bundle-visualizer": {
12369 "version": "1.1.0",
12370 "resolved": "https://registry.npmjs.org/vite-bundle-visualizer/-/vite-bundle-visualizer-1.1.0.tgz",
@@ -12712,9 +12384,9 @@
12384 }
12385 },
12386 "node_modules/vite-node": {
12715 - "version": "1.3.1",
12716 - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz",
12717 - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==",
12387 + "version": "1.4.0",
12388 + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz",
12389 + "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==",
12390 "dev": true,
12391 "dependencies": {
12392 "cac": "^6.7.14",
@@ -12746,16 +12418,16 @@
12418 }
12419 },
12420 "node_modules/vitest": {
12749 - "version": "1.3.1",
12750 - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz",
12751 - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==",
12421 + "version": "1.4.0",
12422 + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz",
12423 + "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==",
12424 "dev": true,
12425 "dependencies": {
12754 - "@vitest/expect": "1.3.1",
12755 - "@vitest/runner": "1.3.1",
12756 - "@vitest/snapshot": "1.3.1",
12757 - "@vitest/spy": "1.3.1",
12758 - "@vitest/utils": "1.3.1",
12426 + "@vitest/expect": "1.4.0",
12427 + "@vitest/runner": "1.4.0",
12428 + "@vitest/snapshot": "1.4.0",
12429 + "@vitest/spy": "1.4.0",
12430 + "@vitest/utils": "1.4.0",
12431 "acorn-walk": "^8.3.2",
12432 "chai": "^4.3.10",
12433 "debug": "^4.3.4",
@@ -12769,7 +12441,7 @@
12441 "tinybench": "^2.5.1",
12442 "tinypool": "^0.8.2",
12443 "vite": "^5.0.0",
12772 - "vite-node": "1.3.1",
12444 + "vite-node": "1.4.0",
12445 "why-is-node-running": "^2.2.2"
12446 },
12447 "bin": {
@@ -12784,8 +12456,8 @@
12456 "peerDependencies": {
12457 "@edge-runtime/vm": "*",
12458 "@types/node": "^18.0.0 || >=20.0.0",
12787 - "@vitest/browser": "1.3.1",
12788 - "@vitest/ui": "1.3.1",
12459 + "@vitest/browser": "1.4.0",
12460 + "@vitest/ui": "1.4.0",
12461 "happy-dom": "*",
12462 "jsdom": "*"
12463 },
@@ -13049,12 +12721,12 @@
12721 }
12722 },
12723 "node_modules/vue-i18n": {
13052 - "version": "9.10.1",
13053 - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.10.1.tgz",
13054 - "integrity": "sha512-37HVJQZ/pZaRXGzFmmMomM1u1k7kndv3xCBPYHKEVfv5W3UVK67U/TpBug71ILYLNmjHLHdvTUPRF81pFT5fFg==",
12724 + "version": "9.10.2",
12725 + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.10.2.tgz",
12726 + "integrity": "sha512-ECJ8RIFd+3c1d3m1pctQ6ywG5Yj8Efy1oYoAKQ9neRdkLbuKLVeW4gaY5HPkD/9ssf1pOnUrmIFjx2/gkGxmEw==",
12727 "dependencies": {
13056 - "@intlify/core-base": "9.10.1",
13057 - "@intlify/shared": "9.10.1",
12728 + "@intlify/core-base": "9.10.2",
12729 + "@intlify/shared": "9.10.2",
12730 "@vue/devtools-api": "^6.5.0"
12731 },
12732 "engines": {
@@ -13100,13 +12772,13 @@
12772 }
12773 },
12774 "node_modules/vue-tsc": {
13103 - "version": "2.0.6",
13104 - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.6.tgz",
13105 - "integrity": "sha512-kK50W4XqQL34vHRkxlRWLicrT6+F9xfgCgJ4KSmCHcytKzc1u3c94XXgI+CjmhOSxyw0krpExF7Obo7y4+0dVQ==",
12775 + "version": "2.0.7",
12776 + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.7.tgz",
12777 + "integrity": "sha512-LYa0nInkfcDBB7y8jQ9FQ4riJTRNTdh98zK/hzt4gEpBZQmf30dPhP+odzCa+cedGz6B/guvJEd0BavZaRptjg==",
12778 "dev": true,
12779 "dependencies": {
13108 - "@volar/typescript": "~2.1.2",
13109 - "@vue/language-core": "2.0.6",
12780 + "@volar/typescript": "~2.1.3",
12781 + "@vue/language-core": "2.0.7",
12782 "semver": "^7.5.4"
12783 },
12784 "bin": {
frontend/package.json
+14 -14
@@ -41,7 +41,7 @@
41 "@popperjs/core": "^2.11.8",
42 "@vueuse/components": "^10.9.0",
43 "@vueuse/core": "^10.9.0",
44 - "apexcharts": "^3.47.0",
44 + "apexcharts": "^3.48.0",
45 "bytes": "^3.1.2",
46 "colord": "^2.9.3",
47 "crypto-js": "^4.2.0",
@@ -63,7 +63,7 @@
63 "vue": "^3.4.21",
64 "vue-advanced-cropper": "^2.8.8",
65 "vue-highlight-words": "^3.0.1",
66 - "vue-i18n": "^9.10.1",
66 + "vue-i18n": "^9.10.2",
67 "vue-router": "^4.3.0",
68 "vue-sjv": "^0.0.6",
69 "vue3-apexcharts": "^1.5.2",
@@ -73,8 +73,8 @@
73 "devDependencies": {
74 "@clack/prompts": "^0.7.0",
75 "@iconify/vue": "^4.1.1",
76 - "@rushstack/eslint-patch": "^1.7.2",
77 - "@tsconfig/node18": "^18.2.2",
76 + "@rushstack/eslint-patch": "^1.9.0",
77 + "@tsconfig/node18": "^18.2.4",
78 "@types/bytes": "^3.1.4",
79 "@types/file-saver": "^2.0.7",
80 "@types/fs-extra": "^11.0.4",
@@ -85,7 +85,7 @@
85 "@types/lodash": "^4.17.0",
86 "@types/markdown-it": "^13.0.7",
87 "@types/markdown-it-highlightjs": "^3.3.4",
88 - "@types/node": "^20.11.27",
88 + "@types/node": "^20.11.30",
89 "@types/validator": "^13.11.9",
90 "@vitejs/plugin-vue": "^5.0.4",
91 "@vitejs/plugin-vue-jsx": "^3.1.0",
@@ -93,31 +93,31 @@
93 "@vue/eslint-config-typescript": "^13.0.0",
94 "@vue/test-utils": "^2.4.5",
95 "@vue/tsconfig": "^0.5.1",
96 - "autoprefixer": "^10.4.18",
97 - "cypress": "^13.7.0",
96 + "autoprefixer": "^10.4.19",
97 + "cypress": "^13.7.1",
98 "eslint": "^8.57.0",
99 "eslint-plugin-cypress": "^2.15.1",
100 - "eslint-plugin-vue": "^9.23.0",
100 + "eslint-plugin-vue": "^9.24.0",
101 "fs-extra": "^11.2.0",
102 "ip": "^2.0.1",
103 "jsdom": "^24.0.0",
104 "json5": "^2.2.3",
105 "npm-run-all": "^4.1.5",
106 "picocolors": "^1.0.0",
107 - "postcss": "^8.4.35",
107 + "postcss": "^8.4.38",
108 "prettier": "^3.2.5",
109 "sass": "^1.72.0",
110 "start-server-and-test": "^2.0.3",
111 "tailwind-config-viewer": "^1.7.3",
112 - "tailwindcss": "^3.4.1",
112 + "tailwindcss": "^3.4.3",
113 "taze": "^0.13.3",
114 "unplugin-vue-components": "^0.26.0",
115 - "vite": "^5.1.6",
116 - "vite-bundle-analyzer": "^0.8.3",
115 + "vite": "^5.2.6",
116 + "vite-bundle-analyzer": "^0.9.2",
117 "vite-bundle-visualizer": "^1.1.0",
118 "vite-svg-loader": "^5.1.0",
119 - "vitest": "^1.3.1",
120 - "vue-tsc": "^2.0.6"
119 + "vitest": "^1.4.0",
120 + "vue-tsc": "^2.0.7"
121 },
122 "engines": {
123 "node": ">=18.0.0"
frontend/src/api/activeResponse.ts
+1 -1
@@ -1,6 +1,6 @@
1 import { type FlaskBaseResponse } from "@/types/flask.d"
2 import { HttpClient } from "./httpClient"
3 -import type { ActiveResponseDetails, SupportedActiveResponse } from "@/types/activeResponse"
3 +import type { ActiveResponseDetails, SupportedActiveResponse } from "@/types/activeResponse.d"
4
5 export type InvokeRequestAction = "block" | "unblock"
6
frontend/src/api/index.ts
+3 -1
@@ -17,6 +17,7 @@ import monitoringAlerts from "./monitoringAlerts"
17 import activeResponse from "./activeResponse"
18 import stackProvisioning from "./stackProvisioning"
19 import reporting from "./reporting"
20 +import license from "./license"
21
22 export default {
23 agents,
@@ -37,5 +38,6 @@ export default {
38 monitoringAlerts,
39 activeResponse,
40 stackProvisioning,
40 - reporting
41 + reporting,
42 + license
43 }
frontend/src/api/integrations.ts
+1 -1
@@ -1,6 +1,6 @@
1 import { type FlaskBaseResponse } from "@/types/flask.d"
2 import { HttpClient } from "./httpClient"
3 -import type { AvailableIntegration, CustomerIntegration } from "@/types/integrations"
3 +import type { AvailableIntegration, CustomerIntegration } from "@/types/integrations.d"
4
5 export interface NewIntegration {
6 customer_code: string
frontend/src/api/license.ts new
+45
@@ -0,0 +1,45 @@
1 +import { type FlaskBaseResponse } from "@/types/flask.d"
2 +import { HttpClient } from "./httpClient"
3 +import type { License, LicenseFeatures, LicenseKey } from "@/types/license.d"
4 +
5 +export interface NewLicensePayload {
6 + name: string
7 + email: string
8 + companyName: string
9 +}
10 +
11 +export default {
12 + getLicense() {
13 + return HttpClient.get<FlaskBaseResponse & { license_key: LicenseKey }>(`/license/get_license`)
14 + },
15 + verifyLicense() {
16 + return HttpClient.get<FlaskBaseResponse & { license: License }>(`/license/verify_license`)
17 + },
18 + getLicenseFeatures() {
19 + return HttpClient.get<FlaskBaseResponse & { features: LicenseFeatures[] }>(`/license/get_license_features`)
20 + },
21 + replaceLicense(license_key: LicenseKey) {
22 + return HttpClient.post<FlaskBaseResponse>(`/license/replace_license_in_db`, {
23 + license_key
24 + })
25 + },
26 + extendLicense(period: number) {
27 + return HttpClient.post<FlaskBaseResponse>(
28 + `/license/extend_license`,
29 + {},
30 + {
31 + params: { period }
32 + }
33 + )
34 + },
35 + createLicense({ name, email, companyName }: NewLicensePayload) {
36 + return HttpClient.post<FlaskBaseResponse>(`/license/create_new_key`, {
37 + product_id: 24355,
38 + notes: "Test Key",
39 + new_customer: true,
40 + name,
41 + email,
42 + company_name: companyName
43 + })
44 + }
45 +}
frontend/src/api/monitoringAlerts.ts
+1 -1
@@ -1,6 +1,6 @@
1 import { type FlaskBaseResponse } from "@/types/flask.d"
2 import { HttpClient } from "./httpClient"
3 -import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts"
3 +import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
4
5 export interface ProvisionsMonitoringAlertParams {
6 searchWithinLast: number
frontend/src/api/reporting.ts
+1 -1
@@ -1,4 +1,4 @@
1 -import type { Dashboard, Org, Panel, PanelLink } from "@/types/reporting"
1 +import type { Dashboard, Org, Panel, PanelLink } from "@/types/reporting.d"
2 import { HttpClient } from "./httpClient"
3 import type { FlaskBaseResponse } from "@/types/flask.d"
4
frontend/src/api/stackProvisioning.ts
+1 -1
@@ -1,6 +1,6 @@
1 import { type FlaskBaseResponse } from "@/types/flask.d"
2 import { HttpClient } from "./httpClient"
3 -import type { AvailableContentPack } from "@/types/stackProvisioning"
3 +import type { AvailableContentPack } from "@/types/stackProvisioning.d"
4
5 export default {
6 getAvailableContentPacks() {
frontend/src/components/activeResponse/ActiveResponseActions.vue
+1 -1
@@ -36,7 +36,7 @@ import { NButton, NModal } from "naive-ui"
36 import Icon from "@/components/common/Icon.vue"
37 import { computed, ref } from "vue"
38 import { watch } from "vue"
39 -import type { SupportedActiveResponse } from "@/types/activeResponse"
39 +import type { SupportedActiveResponse } from "@/types/activeResponse.d"
40 import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
41
42 const emit = defineEmits<{
frontend/src/components/activeResponse/ActiveResponseAgent.vue
+2 -2
@@ -25,8 +25,8 @@ import { ref, onBeforeMount } from "vue"
25 import { useMessage, NSpin, NEmpty } from "naive-ui"
26 import Api from "@/api"
27 import ActiveResponseItem from "./ActiveResponseItem.vue"
28 -import type { Agent } from "@/types/agents"
29 -import type { SupportedActiveResponse } from "@/types/activeResponse"
28 +import type { Agent } from "@/types/agents.d"
29 +import type { SupportedActiveResponse } from "@/types/activeResponse.d"
30
31 const { embedded, agent } = defineProps<{
32 embedded?: boolean
frontend/src/components/activeResponse/ActiveResponseDetails.vue
+1 -1
@@ -13,7 +13,7 @@
13 import { ref, onBeforeMount, defineAsyncComponent } from "vue"
14 import { useMessage, NSpin, NEmpty } from "naive-ui"
15 import Api from "@/api"
16 -import type { ActiveResponseDetails, SupportedActiveResponse } from "@/types/activeResponse"
16 +import type { ActiveResponseDetails, SupportedActiveResponse } from "@/types/activeResponse.d"
17 const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
18
19 const { activeResponse } = defineProps<{
frontend/src/components/activeResponse/ActiveResponseInvokeForm.vue
+1 -1
@@ -43,7 +43,7 @@ import {
43 } from "naive-ui"
44 import { computed, onMounted, ref } from "vue"
45 import { watch } from "vue"
46 -import type { SupportedActiveResponse } from "@/types/activeResponse"
46 +import type { SupportedActiveResponse } from "@/types/activeResponse.d"
47 import isIP from "validator/es/lib/isIP"
48 import type { InvokeRequest, InvokeRequestAction } from "@/api/activeResponse"
49 import Api from "@/api"
frontend/src/components/activeResponse/ActiveResponseItem.vue
+1 -1
@@ -50,7 +50,7 @@
50
51 <script setup lang="ts">
52 import { ref, toRefs } from "vue"
53 -import type { SupportedActiveResponse } from "@/types/activeResponse"
53 +import type { SupportedActiveResponse } from "@/types/activeResponse.d"
54 import ActiveResponseActions from "./ActiveResponseActions.vue"
55 import ActiveResponseDetails from "./ActiveResponseDetails.vue"
56 import { NButton, NModal } from "naive-ui"
frontend/src/components/activeResponse/ActiveResponseWizard.vue
+1 -1
@@ -99,7 +99,7 @@ import { NSteps, NStep, useMessage, NScrollbar, NButton, NEmpty, NSpin, type Ste
99 import Icon from "@/components/common/Icon.vue"
100 import Api from "@/api"
101 import { onBeforeMount } from "vue"
102 -import type { SupportedActiveResponse } from "@/types/activeResponse"
102 +import type { SupportedActiveResponse } from "@/types/activeResponse.d"
103 import ActiveResponseItem from "./ActiveResponseItem.vue"
104 import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
105 import { iconFromOs } from "@/utils"
frontend/src/components/agents/OverviewSection.vue
+2 -9
@@ -26,7 +26,7 @@
26
27 <script setup lang="ts">
28 import { computed, toRefs } from "vue"
29 -import dayjs from "@/utils/dayjs"
29 +import { formatDate } from "@/utils"
30 import { type Agent } from "@/types/agents.d"
31 import { useSettingsStore } from "@/stores/settings"
32 import KVCard from "@/components/common/KVCard.vue"
@@ -47,7 +47,7 @@ const propsSanitized = computed(() => {
47 for (const key in agent.value) {
48 if (["wazuh_last_seen", "velociraptor_last_seen"].includes(key)) {
49 // @ts-ignore
50 - obj.push({ key, val: formatDate(agent.value[key]) || "-" })
50 + obj.push({ key, val: formatDate(agent.value[key], dFormats.datetime) || "-" })
51 } else {
52 // @ts-ignore
53 obj.push({ key, val: agent.value[key] || "-" })
@@ -57,13 +57,6 @@ const propsSanitized = computed(() => {
57 return obj
58 })
59
60 -const formatDate = (date: string) => {
61 - const datejs = dayjs(date)
62 - if (!datejs.isValid()) return date
63 -
64 - return datejs.format(dFormats.datetime)
65 -}
66 -
60 function gotoCustomer(code: string | number) {
61 router.push({ name: "Customers", query: { code } })
62 }
frontend/src/components/agents/agentFlow/AgentFlowItem.vue
+3 -6
@@ -12,7 +12,7 @@
12 <template #trigger>
13 <div class="flex items-center gap-2 cursor-help">
14 <span>
15 - {{ formatDate(flow.start_time) }}
15 + {{ formatDate(flow.start_time, dFormats.datetimesec) }}
16 </span>
17 <Icon :name="TimeIcon" :size="16"></Icon>
18 </div>
@@ -57,7 +57,7 @@
57 </div>
58 </div>
59 <div class="footer-box">
60 - <div class="time">{{ formatDate(flow.start_time) }}</div>
60 + <div class="time">{{ formatDate(flow.start_time, dFormats.datetimesec) }}</div>
61 </div>
62
63 <n-modal
@@ -152,6 +152,7 @@
152 import { NPopover, NModal, NTabs, NTabPane, NEmpty, NScrollbar, NInput } from "naive-ui"
153 import { useSettingsStore } from "@/stores/settings"
154 import dayjs from "@/utils/dayjs"
155 +import { formatDate } from "@/utils"
156 import type { FlowResult } from "@/types/flow.d"
157 import Icon from "@/components/common/Icon.vue"
158 import AgentFlowTimeline from "./AgentFlowTimeline.vue"
@@ -191,10 +192,6 @@ const properties = computed(() => {
192 "user_notified"
193 ])
194 })
194 -
195 -function formatDate(timestamp: number): string {
196 - return dayjs(timestamp / 1000).format(dFormats.datetimesec)
197 -}
195 </script>
196
197 <style lang="scss" scoped>
frontend/src/components/agents/agentFlow/AgentFlowQueryStat.vue
+3 -6
@@ -3,11 +3,11 @@
3 <div class="header-box flex justify-between items-center gap-3">
4 <div class="id grow flex flex-wrap gap-2">
5 <span>
6 - {{ formatDate(stat.first_active) }}
6 + {{ formatDate(stat.first_active, dFormats.datetimesecmill) }}
7 </span>
8 <span>•</span>
9 <span>
10 - {{ formatDate(stat.last_active) }}
10 + {{ formatDate(stat.last_active, dFormats.datetimesecmill) }}
11 </span>
12 </div>
13 <div class="actions whitespace-nowrap">
@@ -91,6 +91,7 @@
91 import { NModal, NTabs, NTabPane, NInput, NButton } from "naive-ui"
92 import { useSettingsStore } from "@/stores/settings"
93 import dayjs from "@/utils/dayjs"
94 +import { formatDate } from "@/utils"
95 import type { FlowQueryStat } from "@/types/flow.d"
96 import Icon from "@/components/common/Icon.vue"
97 import KVCard from "@/components/common/KVCard.vue"
@@ -119,10 +120,6 @@ const properties = computed(() => {
120 "total_queries"
121 ])
122 })
122 -
123 -function formatDate(timestamp: number): string {
124 - return dayjs(timestamp / 1000).format(dFormats.datetimesecmill)
125 -}
123 </script>
124
125 <style lang="scss" scoped>
frontend/src/components/agents/agentFlow/AgentFlowTimeline.vue
+6 -6
@@ -1,19 +1,19 @@
1 <template>
2 <n-timeline>
3 - <n-timeline-item type="success" title="Start" :time="formatDate(flow.start_time)" />
3 + <n-timeline-item type="success" title="Start" :time="formatDateTime(flow.start_time)" />
4 <n-timeline-item
5 v-if="flow.create_time"
6 title="Create"
7 - :time="formatDate(flow.create_time)"
7 + :time="formatDateTime(flow.create_time)"
8 line-type="dashed"
9 />
10 - <n-timeline-item v-if="flow.active_time" title="Active" :time="formatDate(flow.active_time)" />
10 + <n-timeline-item v-if="flow.active_time" title="Active" :time="formatDateTime(flow.active_time)" />
11 </n-timeline>
12 </template>
13
14 <script setup lang="ts">
15 import { useSettingsStore } from "@/stores/settings"
16 -import dayjs from "@/utils/dayjs"
16 +import { formatDate } from "@/utils"
17 import { NTimeline, NTimelineItem } from "naive-ui"
18 import type { FlowResult } from "@/types/flow.d"
19
@@ -21,7 +21,7 @@ const { flow } = defineProps<{ flow: FlowResult }>()
21
22 const dFormats = useSettingsStore().dateFormat
23
24 -function formatDate(timestamp: number): string {
25 - return dayjs(timestamp / 1000).format(dFormats.datetimesec)
24 +function formatDateTime(timestamp: number): string {
25 + return formatDate(timestamp, dFormats.datetimesec).toString()
26 }
27 </script>
frontend/src/components/alerts/Alert.vue
+3 -7
@@ -6,7 +6,7 @@
6 <Icon :name="InfoIcon" :size="16"></Icon>
7 </div>
8 <div class="time">
9 - {{ formatDate(alert._source.timestamp_utc) }}
9 + {{ formatDate(alert._source.timestamp_utc, dFormats.datetimesec) }}
10 </div>
11 </div>
12 <div class="main-box flex justify-between gap-4">
@@ -112,7 +112,7 @@
112 @updated-url="alert._source.alert_url = $event"
113 @updated-ask-message="alert._source.ask_socfortress_message = $event"
114 />
115 - <div class="time">{{ formatDate(alert._source.timestamp_utc) }}</div>
115 + <div class="time">{{ formatDate(alert._source.timestamp_utc, dFormats.datetimesec) }}</div>
116 </div>
117
118 <n-modal
@@ -216,7 +216,7 @@
216 import { computed, defineAsyncComponent, ref, toRefs } from "vue"
217 import { NPopover, NModal, NTabs, NTabPane, NInput } from "naive-ui"
218 import { useSettingsStore } from "@/stores/settings"
219 -import dayjs from "@/utils/dayjs"
219 +import { formatDate } from "@/utils"
220 import Icon from "@/components/common/Icon.vue"
221 import Badge from "@/components/common/Badge.vue"
222 const AlertActions = defineAsyncComponent(() => import("./AlertActions.vue"))
@@ -255,10 +255,6 @@ const agentProperties = computed(() => {
255 ])
256 })
257
258 -function formatDate(timestamp: string): string {
259 - return dayjs(timestamp).format(dFormats.datetimesec)
260 -}
261 -
258 function gotoAgentPage(agentId: string) {
259 router.push({ name: "Agent", params: { id: agentId } })
260 }
frontend/src/components/alerts/ThreatIntelForm.vue
+4 -10
@@ -35,7 +35,9 @@
35 </div>
36 <div class="item">
37 <div class="key">timestamp</div>
38 - <div class="value">{{ response?.timestamp ? formatDate(response.timestamp) : "-" }}</div>
38 + <div class="value">
39 + {{ response?.timestamp ? formatDate(response.timestamp, dFormats.datetime) : "-" }}
40 + </div>
41 </div>
42 <div class="item">
43 <div class="key">report_url</div>
@@ -66,10 +68,9 @@ import { ref, computed, onMounted } from "vue"
68 import { useMessage, NSpin, NButton, NInput } from "naive-ui"
69 import Api from "@/api"
70 import _trim from "lodash/trim"
69 -import _toNumber from "lodash/toNumber"
71 import type { ThreatIntelResponse } from "@/types/threatIntel.d"
72 import { useSettingsStore } from "@/stores/settings"
72 -import dayjs from "@/utils/dayjs"
73 +import { formatDate } from "@/utils"
74
75 const emit = defineEmits<{
76 (
@@ -103,13 +104,6 @@ function restore() {
104 error.value = ""
105 }
106
106 -const formatDate = (date: string) => {
107 - const datejs = dayjs(_toNumber(date) * 1000)
108 - if (!datejs.isValid()) return date
109 -
110 - return datejs.format(dFormats.datetime)
111 -}
112 -
107 function create() {
108 loading.value = true
109
frontend/src/components/artifacts/ArtifactsCommand.vue
+3 -6
@@ -60,11 +60,11 @@
60 </template>
61 <template #value>
62 <span class="flex">
63 - {{ formatDate(commandTime) }}
63 + {{ formatDate(commandTime, dFormats.timesec) }}
64
65 <n-spin :size="12" v-if="loading" class="ml-2" />
66
67 - {{ responseTime ? " / " + formatDate(responseTime) : "" }}
67 + {{ responseTime ? " / " + formatDate(responseTime, dFormats.timesec) : "" }}
68 </span>
69 </template>
70 </Badge>
@@ -124,6 +124,7 @@ import type { Artifact, CommandResult } from "@/types/artifacts.d"
124 import dayjs from "@/utils/dayjs"
125 import Icon from "@/components/common/Icon.vue"
126 import { useSettingsStore } from "@/stores/settings"
127 +import { formatDate } from "@/utils"
128 // import { commandResult } from "./mock"
129
130 const emit = defineEmits<{
@@ -177,10 +178,6 @@ const artifactsOptions = computed(() => {
178 return (artifactsList.value || []).map(o => ({ value: o.name, label: o.name }))
179 })
180
180 -function formatDate(timestamp: string | Date): string {
181 - return dayjs(timestamp).format(dFormats.timesec)
182 -}
183 -
181 function getData() {
182 if (areFiltersValid.value) {
183 loading.value = true
frontend/src/components/artifacts/CollectItem.vue
+3 -6
@@ -32,6 +32,7 @@ import KVCard from "@/components/common/KVCard.vue"
32 import { onBeforeMount, ref } from "vue"
33 import _isString from "lodash/isString"
34 import _isNumber from "lodash/isNumber"
35 +import { formatDate } from "@/utils"
36
37 interface Prop {
38 key: string
@@ -47,10 +48,6 @@ const displayData = ref<Prop[]>([])
48 const showDetails = ref(false)
49 const dFormats = useSettingsStore().dateFormat
50
50 -function formatDate(timestamp: string | number): string {
51 - return dayjs(timestamp).format(dFormats.datetimesec)
52 -}
53 -
51 onBeforeMount(() => {
52 for (const key in collect) {
53 const value = collect[key]
@@ -68,7 +65,7 @@ onBeforeMount(() => {
65 }
66
67 if (prop.value && typeof prop.value === "string") {
71 - prop.value = dayjs(value).isValid() ? formatDate(value) : value.toString()
68 + prop.value = dayjs(value).isValid() ? formatDate(value, dFormats.datetimesec) : value.toString()
69 }
70
71 if (prop.value && typeof prop.value === "number") {
@@ -76,7 +73,7 @@ onBeforeMount(() => {
73
74 if (numText.length === 10 || numText.length === 13) {
75 if (dayjs(value).isValid()) {
79 - prop.value = formatDate(value)
76 + prop.value = formatDate(value, dFormats.datetimesec).toString()
77 }
78 }
79 }
frontend/src/components/artifacts/QuarantineItem.vue
+2 -6
@@ -1,7 +1,7 @@
1 <template>
2 <div class="quarantine-item flex flex-col gap-1 px-5 py-3">
3 <div class="time text-secondary-color">
4 - {{ formatDate(quarantine.Time) }}
4 + {{ formatDate(quarantine.Time, dFormats.datetimesec) }}
5 </div>
6 <div class="result">{{ quarantine.Result }}</div>
7 </div>
@@ -10,15 +10,11 @@
10 <script setup lang="ts">
11 import { useSettingsStore } from "@/stores/settings"
12 import type { QuarantineResult } from "@/types/artifacts.d"
13 -import dayjs from "@/utils/dayjs"
13 +import { formatDate } from "@/utils"
14
15 const { quarantine } = defineProps<{ quarantine: QuarantineResult }>()
16
17 const dFormats = useSettingsStore().dateFormat
18 -
19 -function formatDate(timestamp: string): string {
20 - return dayjs(timestamp).format(dFormats.datetimesec)
21 -}
18 </script>
19
20 <style lang="scss" scoped>
frontend/src/components/connectors/ConnectorItem.vue
+1 -1
@@ -107,7 +107,7 @@ import Badge from "@/components/common/Badge.vue"
107 import { computed, ref, toRefs } from "vue"
108 import Api from "@/api"
109 import { NAvatar, useMessage, NModal, NSpin, NButton, NCard } from "naive-ui"
110 -import type { Connector } from "@/types/connectors"
110 +import type { Connector } from "@/types/connectors.d"
111 import ConfigForm from "./ConfigForm"
112
113 const emit = defineEmits<{
frontend/src/components/connectors/ConnectorsList.vue
+1 -1
@@ -32,7 +32,7 @@ import { ref, onBeforeMount, computed } from "vue"
32 import { useMessage, NSpin, NEmpty } from "naive-ui"
33 import Api from "@/api"
34 import ConnectorItem from "./ConnectorItem.vue"
35 -import type { Connector } from "@/types/connectors"
35 +import type { Connector } from "@/types/connectors.d"
36
37 const message = useMessage()
38 const loadingConnectors = ref(false)
frontend/src/components/customers/CustomerItem.vue
+1 -1
@@ -98,7 +98,7 @@
98 v-model:show="showDetails"
99 preset="card"
100 content-style="padding:0px"
101 - :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(600px, 90vh)', overflow: 'hidden' }"
101 + :style="{ maxWidth: 'min(900px, 90vw)', minHeight: 'min(600px, 90vh)', overflow: 'hidden' }"
102 :title="customerInfo?.customer_name"
103 :bordered="false"
104 segmented
frontend/src/components/customers/integrations/CustomerIntegrationActions.vue
+1 -1
@@ -46,7 +46,7 @@ import Icon from "@/components/common/Icon.vue"
46 import Api from "@/api"
47 import { computed, h, ref } from "vue"
48 import { watch } from "vue"
49 -import type { CustomerIntegration } from "@/types/integrations"
49 +import type { CustomerIntegration } from "@/types/integrations.d"
50
51 const emit = defineEmits<{
52 (e: "startLoading"): void
frontend/src/components/customers/integrations/CustomerIntegrationForm.vue
+1 -1
@@ -85,7 +85,7 @@ import { useMessage, NFormItem, NInput, NSelect, NButton, NScrollbar, NSteps, NS
85 import Icon from "@/components/common/Icon.vue"
86 import Api from "@/api"
87 import IntegrationsList from "@/components/integrations/IntegrationsList.vue"
88 -import type { AvailableIntegration } from "@/types/integrations"
88 +import type { AvailableIntegration } from "@/types/integrations.d"
89 import type { NewIntegration } from "@/api/integrations"
90
91 interface AuthKeysInput {
frontend/src/components/customers/integrations/CustomerIntegrationItem.vue
+1 -1
@@ -64,7 +64,7 @@ import Icon from "@/components/common/Icon.vue"
64 import Badge from "@/components/common/Badge.vue"
65 import { computed, ref, toRefs } from "vue"
66 import { NModal, NButton } from "naive-ui"
67 -import type { CustomerIntegration } from "@/types/integrations"
67 +import type { CustomerIntegration } from "@/types/integrations.d"
68 import CustomerIntegrationActions from "./CustomerIntegrationActions.vue"
69 import KVCard from "@/components/common/KVCard.vue"
70 import _uniqBy from "lodash/uniqBy"
frontend/src/components/customers/integrations/CustomerIntegrations.vue
+1 -1
@@ -54,7 +54,7 @@ import Icon from "@/components/common/Icon.vue"
54 import Api from "@/api"
55 import CustomerIntegrationForm from "./CustomerIntegrationForm.vue"
56 import CustomerIntegrationItem from "./CustomerIntegrationItem.vue"
57 -import type { CustomerIntegration } from "@/types/integrations"
57 +import type { CustomerIntegration } from "@/types/integrations.d"
58
59 const { customerCode, customerName } = defineProps<{
60 customerCode: string
frontend/src/components/customers/provision/CustomerProvisionWizard.vue
+38 -5
@@ -8,6 +8,11 @@
8 <n-step title="Provisioning" />
9 <n-step title="Graylog" />
10 <n-step title="Subscription" />
11 + <n-step title="Infrastructure">
12 + <template #icon>
13 + <Icon :name="SkipIcon" v-if="!isInfrastructureEnabled"></Icon>
14 + </template>
15 + </n-step>
16 <n-step title="Wazuh Worker">
17 <template #icon>
18 <Icon :name="SkipIcon" v-if="!isWazuhStepEnabled"></Icon>
@@ -131,7 +136,20 @@
136 </n-form-item>
137 </div>
138
134 - <div v-else-if="current === 4" class="px-7 flex flex-wrap gap-3">
139 + <div v-else-if="current === 4" class="px-7 flex gap-3">
140 + <n-card class="grow">
141 + <n-form-item label="Deploy HA Proxy" path="provision_ha_proxy">
142 + <n-switch v-model:value="form.provision_ha_proxy" clearable />
143 + </n-form-item>
144 + </n-card>
145 + <n-card class="grow">
146 + <n-form-item label="Deploy Wazuh Worker" path="provision_wazuh_worker">
147 + <n-switch v-model:value="form.provision_wazuh_worker" clearable />
148 + </n-form-item>
149 + </n-card>
150 + </div>
151 +
152 + <div v-else-if="current === 5" class="px-7 flex flex-wrap gap-3">
153 <n-form-item label="Auth Password" path="wazuh_auth_password" class="grow">
154 <n-input
155 v-model:value="form.wazuh_auth_password"
@@ -229,6 +247,8 @@ import {
247 NSelect,
248 NInputNumber,
249 NSpin,
250 + NSwitch,
251 + NCard,
252 type StepsProps,
253 type FormRules,
254 type FormInst,
@@ -277,11 +297,22 @@ const allDashboardsSelected = computed(
297 () => form.value.dashboards_to_include.dashboards.length === dashboardOptions.value.length
298 )
299
280 -const isWazuhStepEnabled = computed(() => form.value.customer_subscription.map(o => o.toLowerCase()).includes("wazuh"))
281 -const isNextStepEnabled = computed(() => current.value < 3 || (current.value === 3 && isWazuhStepEnabled.value))
300 +const isInfrastructureEnabled = computed(() =>
301 + form.value.customer_subscription.map(o => o.toLowerCase()).includes("wazuh")
302 +)
303 +const isWazuhStepEnabled = computed(() => isInfrastructureEnabled.value && form.value.provision_wazuh_worker)
304 +const isNextStepEnabled = computed(
305 + () =>
306 + current.value < 3 ||
307 + (current.value === 3 && isInfrastructureEnabled.value) ||
308 + (current.value === 4 && isWazuhStepEnabled.value)
309 +)
310 const isPrevStepEnabled = computed(() => current.value > 1)
311 const isSubmitEnabled = computed(
284 - () => (current.value === 3 && !isWazuhStepEnabled.value) || (current.value === 4 && isWazuhStepEnabled.value)
312 + () =>
313 + (current.value === 3 && !isInfrastructureEnabled.value) ||
314 + (current.value === 4 && !isWazuhStepEnabled.value) ||
315 + (current.value === 5 && isWazuhStepEnabled.value)
316 )
317 const slideFormDirection = ref<"right" | "left">("right")
318
@@ -430,7 +461,9 @@ function getClearForm(settings?: CustomerProvisioningDefaultSettings): CustomerP
461 wazuh_cluster_name: settings?.cluster_name || "",
462 wazuh_cluster_key: settings?.cluster_key || "",
463 wazuh_master_ip: settings?.master_ip || "",
433 - grafana_url: settings?.grafana_url || ""
464 + grafana_url: settings?.grafana_url || "",
465 + provision_wazuh_worker: false,
466 + provision_ha_proxy: false
467 }
468 }
469
frontend/src/components/graylog/Alerts/Item.vue
+9 -9
@@ -44,11 +44,11 @@
44 </div>
45 <div class="box">
46 timestamp:
47 - <code>{{ formatDate(alertsEvent.event.timestamp) }}</code>
47 + <code>{{ formatDateTime(alertsEvent.event.timestamp) }}</code>
48 </div>
49 <div class="box">
50 timestamp processing:
51 - <code>{{ formatDate(alertsEvent.event.timestamp_processing) }}</code>
51 + <code>{{ formatDateTime(alertsEvent.event.timestamp_processing) }}</code>
52 </div>
53 </div>
54 </n-popover>
@@ -58,7 +58,7 @@
58 <template #trigger>
59 <div class="flex items-center gap-2 cursor-help">
60 <span>
61 - {{ formatDate(alertsEvent.event.timestamp) }}
61 + {{ formatDateTime(alertsEvent.event.timestamp) }}
62 </span>
63 <Icon :name="TimeIcon" :size="16"></Icon>
64 </div>
@@ -68,12 +68,12 @@
68 <n-timeline-item
69 type="success"
70 title="Timestamp"
71 - :time="formatDate(alertsEvent.event.timestamp)"
71 + :time="formatDateTime(alertsEvent.event.timestamp)"
72 />
73 <n-timeline-item
74 v-if="alertsEvent.event.timestamp_processing"
75 title="Processing"
76 - :time="formatDate(alertsEvent.event.timestamp_processing)"
76 + :time="formatDateTime(alertsEvent.event.timestamp_processing)"
77 />
78 </n-timeline>
79 </div>
@@ -84,7 +84,7 @@
84 <div class="content">{{ alertsEvent.event.message }}</div>
85 </div>
86 <div class="footer-box flex justify-end items-center gap-3">
87 - <div class="time">{{ formatDate(alertsEvent.event.timestamp) }}</div>
87 + <div class="time">{{ formatDateTime(alertsEvent.event.timestamp) }}</div>
88 </div>
89 </div>
90 </template>
@@ -93,7 +93,7 @@
93 import { type AlertsEventElement } from "@/types/graylog/alerts.d"
94 import { NPopover, NTimeline, NTimelineItem } from "naive-ui"
95 import { useSettingsStore } from "@/stores/settings"
96 -import dayjs from "@/utils/dayjs"
96 +import { formatDate } from "@/utils"
97 import Icon from "@/components/common/Icon.vue"
98 import { useRouter } from "vue-router"
99
@@ -110,8 +110,8 @@ const LinkIcon = "carbon:launch"
110 const router = useRouter()
111 const dFormats = useSettingsStore().dateFormat
112
113 -function formatDate(timestamp: string): string {
114 - return dayjs(timestamp).format(dFormats.datetimesec)
113 +function formatDateTime(timestamp: string): string {
114 + return formatDate(timestamp, dFormats.datetimesec).toString()
115 }
116
117 function gotoIndicesPage(index: string) {
frontend/src/components/graylog/Inputs/Item.vue
+6 -6
@@ -7,7 +7,7 @@
7 {{ input.creator_user_id }}
8 </div>
9 </div>
10 - <div class="time">{{ formatDate(input.created_at) }}</div>
10 + <div class="time">{{ formatDateTime(input.created_at) }}</div>
11 <n-button size="small" @click.stop="showDetails = true">
12 <template #icon>
13 <Icon :name="InfoIcon"></Icon>
@@ -34,7 +34,7 @@
34 <template #label>Running</template>
35 </Badge>
36 </template>
37 - {{ formatDate(input.started_at) }}
37 + {{ formatDateTime(input.started_at) }}
38 </n-tooltip>
39 </div>
40 </div>
@@ -62,7 +62,7 @@
62 </n-button>
63 </div>
64
65 - <div class="time">{{ formatDate(input.created_at) }}</div>
65 + <div class="time">{{ formatDateTime(input.created_at) }}</div>
66 </div>
67
68 <n-modal
@@ -119,13 +119,13 @@
119 import { useSettingsStore } from "@/stores/settings"
120 import Icon from "@/components/common/Icon.vue"
121 import Badge from "@/components/common/Badge.vue"
122 -import dayjs from "@/utils/dayjs"
122 import { NModal, NButton, useMessage, NTooltip, NTabs, NTabPane } from "naive-ui"
123 import { computed, ref } from "vue"
124 import { SimpleJsonViewer } from "vue-sjv"
125 import "@/assets/scss/vuesjv-override.scss"
126 import Api from "@/api"
127 import type { InputExtended } from "@/types/graylog/inputs.d"
128 +import { formatDate } from "@/utils"
129
130 const emit = defineEmits<{
131 (e: "updated"): void
@@ -147,8 +147,8 @@ const showDetails = ref(false)
147 const isRunning = computed(() => input?.state === "RUNNING")
148 const dFormats = useSettingsStore().dateFormat
149
150 -function formatDate(timestamp: string): string {
151 - return dayjs(timestamp).format(dFormats.datetimesec)
150 +function formatDateTime(timestamp: string): string {
151 + return formatDate(timestamp, dFormats.datetimesec).toString()
152 }
153
154 function stop() {
frontend/src/components/graylog/Messages/Item.vue
+3 -7
@@ -2,13 +2,13 @@
2 <div class="item flex flex-col gap-2 px-5 py-3">
3 <div class="header-box flex justify-between">
4 <div class="caller">{{ message.caller }}</div>
5 - <div class="time">{{ formatDate(message.timestamp) }}</div>
5 + <div class="time">{{ formatDate(message.timestamp, dFormats.datetimesec) }}</div>
6 </div>
7 <div class="main-box">
8 <div class="content">{{ message.content }}</div>
9 </div>
10 <div class="footer-box">
11 - <div class="time">{{ formatDate(message.timestamp) }}</div>
11 + <div class="time">{{ formatDate(message.timestamp, dFormats.datetimesec) }}</div>
12 </div>
13 </div>
14 </template>
@@ -16,15 +16,11 @@
16 <script setup lang="ts">
17 import { type Message } from "@/types/graylog/index.d"
18 import { useSettingsStore } from "@/stores/settings"
19 -import dayjs from "@/utils/dayjs"
19 +import { formatDate } from "@/utils"
20
21 const { message } = defineProps<{ message: Message }>()
22
23 const dFormats = useSettingsStore().dateFormat
24 -
25 -function formatDate(timestamp: string): string {
26 - return dayjs(timestamp).format(dFormats.datetimesec)
27 -}
24 </script>
25
26 <style lang="scss" scoped>
frontend/src/components/graylog/MonitoringAlerts/Item.vue
+1 -1
@@ -90,7 +90,7 @@
90 </template>
91
92 <script setup lang="ts">
93 -import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts"
93 +import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
94 import { ref } from "vue"
95 import Icon from "@/components/common/Icon.vue"
96 import Badge from "@/components/common/Badge.vue"
frontend/src/components/graylog/MonitoringAlerts/List.vue
+2 -2
@@ -70,9 +70,9 @@ import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination } from "naive
70 import Api from "@/api"
71 import MonitoringAlert from "./Item.vue"
72 import CustomAlertButton from "./CustomAlertButton.vue"
73 -import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts"
73 +import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
74 import Icon from "@/components/common/Icon.vue"
75 -import type { EventDefinition } from "@/types/graylog/event-definition"
75 +import type { EventDefinition } from "@/types/graylog/event-definition.d"
76
77 const { eventsList } = defineProps<{ eventsList: EventDefinition[] }>()
78
frontend/src/components/graylog/Pipelines/PipeDetails.vue
+2 -6
@@ -7,7 +7,7 @@
7 </div>
8
9 <div class="time">
10 - {{ formatDate(pipeline.modified_at) }}
10 + {{ formatDate(pipeline.modified_at, dFormats.datetimesec) }}
11 </div>
12 </div>
13
@@ -46,7 +46,7 @@ import { computed, toRefs } from "vue"
46 import type { PipelineFull, PipelineFullStage } from "@/types/graylog/pipelines.d"
47 import Icon from "@/components/common/Icon.vue"
48 import RulesSmallList, { type RuleExtended } from "./RulesSmallList.vue"
49 -import dayjs from "@/utils/dayjs"
49 +import { formatDate } from "@/utils"
50 import { useSettingsStore } from "@/stores/settings"
51
52 interface PipelineFullStageExt extends Omit<PipelineFullStage, "rules" | "rule_ids"> {
@@ -92,10 +92,6 @@ const stages = computed<PipelineFullStageExt[]>(() => {
92
93 return stages
94 })
95 -
96 -function formatDate(timestamp: string): string {
97 - return dayjs(timestamp).format(dFormats.datetimesec)
98 -}
95 </script>
96
97 <style lang="scss" scoped>
frontend/src/components/graylog/Pipelines/PipeInfo.vue
+7 -7
@@ -8,11 +8,15 @@
8 </div>
9 <div class="mb-2">
10 Created:
11 - <code>{{ pipeline?.created_at ? formatDate(pipeline.created_at) : "-" }}</code>
11 + <code>
12 + {{ pipeline?.created_at ? formatDate(pipeline.created_at, dFormats.datetimesec) : "-" }}
13 + </code>
14 </div>
15 <div class="mb-2">
16 Modified:
15 - <code>{{ pipeline?.modified_at ? formatDate(pipeline.modified_at) : "-" }}</code>
17 + <code>
18 + {{ pipeline?.modified_at ? formatDate(pipeline.modified_at, dFormats.datetimesec) : "-" }}
19 + </code>
20 </div>
21 <div class="mb-2">
22 Errors :
@@ -42,13 +46,9 @@ import { NTabs, NTabPane, NInput } from "naive-ui"
46 import { toRefs } from "vue"
47 import type { Pipeline } from "@/types/graylog/pipelines.d"
48 import { useSettingsStore } from "@/stores/settings"
45 -import dayjs from "@/utils/dayjs"
49 +import { formatDate } from "@/utils"
50
51 const props = defineProps<{ pipeline?: Pipeline }>()
52 const { pipeline } = toRefs(props)
53 const dFormats = useSettingsStore().dateFormat
50 -
51 -function formatDate(timestamp: string): string {
52 - return dayjs(timestamp).format(dFormats.datetimesec)
53 -}
54 </script>
frontend/src/components/graylog/Pipelines/Rule.vue
+9 -9
@@ -14,18 +14,18 @@
14 <template #trigger>
15 <div class="flex items-center gap-2 cursor-help">
16 <span>
17 - {{ formatDate(rule.modified_at) }}
17 + {{ formatDateTime(rule.modified_at) }}
18 </span>
19 <Icon :name="TimeIcon" :size="16"></Icon>
20 </div>
21 </template>
22 <div class="flex flex-col py-2 px-1">
23 <n-timeline>
24 - <n-timeline-item type="success" title="Created" :time="formatDate(rule.created_at)" />
24 + <n-timeline-item type="success" title="Created" :time="formatDateTime(rule.created_at)" />
25 <n-timeline-item
26 v-if="rule.modified_at"
27 title="Modified"
28 - :time="formatDate(rule.modified_at)"
28 + :time="formatDateTime(rule.modified_at)"
29 />
30 </n-timeline>
31 </div>
@@ -38,7 +38,7 @@
38 </div>
39 <div class="footer-box flex justify-end items-center gap-3">
40 <div class="time">
41 - {{ formatDate(rule.modified_at) }}
41 + {{ formatDateTime(rule.modified_at) }}
42 </div>
43 </div>
44
@@ -54,11 +54,11 @@
54 <div class="p-7 pt-4">
55 <div class="mb-2">
56 Created:
57 - <code>{{ formatDate(rule.created_at) }}</code>
57 + <code>{{ formatDateTime(rule.created_at) }}</code>
58 </div>
59 <div class="mb-2">
60 Modified:
61 - <code>{{ formatDate(rule.modified_at) }}</code>
61 + <code>{{ formatDateTime(rule.modified_at) }}</code>
62 </div>
63 <div class="mb-2">
64 Errors :
@@ -86,7 +86,7 @@ import { NModal, NInput, NPopover, NTimeline, NTimelineItem } from "naive-ui"
86 import Icon from "@/components/common/Icon.vue"
87 import type { PipelineRule } from "@/types/graylog/pipelines.d"
88 import { useSettingsStore } from "@/stores/settings"
89 -import dayjs from "@/utils/dayjs"
89 +import { formatDate } from "@/utils"
90
91 const props = defineProps<{ rule: PipelineRule; highlight: boolean | null | undefined }>()
92 const { rule, highlight } = toRefs(props)
@@ -97,8 +97,8 @@ const InfoIcon = "carbon:information"
97 const showDetails = ref(false)
98 const dFormats = useSettingsStore().dateFormat
99
100 -function formatDate(timestamp: string): string {
101 - return dayjs(timestamp).format(dFormats.datetimesec)
100 +function formatDateTime(timestamp: string): string {
101 + return formatDate(timestamp, dFormats.datetimesec).toString()
102 }
103 </script>
104
frontend/src/components/graylog/Streams/Item.vue
+3 -7
@@ -7,7 +7,7 @@
7 {{ stream.creator_user_id }}
8 </div>
9 </div>
10 - <div class="time">{{ formatDate(stream.created_at) }}</div>
10 + <div class="time">{{ formatDate(stream.created_at, dFormats.datetimesec) }}</div>
11 <n-button size="small" @click.stop="showDetails = true">
12 <template #icon>
13 <Icon :name="InfoIcon"></Icon>
@@ -61,7 +61,7 @@
61 Start
62 </n-button>
63 </div>
64 - <div class="time">{{ formatDate(stream.created_at) }}</div>
64 + <div class="time">{{ formatDate(stream.created_at, dFormats.datetimesec) }}</div>
65 </div>
66
67 <n-modal
@@ -91,7 +91,7 @@ import { type Stream } from "@/types/graylog/stream.d"
91 import { useSettingsStore } from "@/stores/settings"
92 import Icon from "@/components/common/Icon.vue"
93 import Badge from "@/components/common/Badge.vue"
94 -import dayjs from "@/utils/dayjs"
94 +import { formatDate } from "@/utils"
95 import { NModal, NButton, useMessage } from "naive-ui"
96 import { ref, toRefs } from "vue"
97 import { SimpleJsonViewer } from "vue-sjv"
@@ -113,10 +113,6 @@ const loading = ref(false)
113 const showDetails = ref(false)
114 const dFormats = useSettingsStore().dateFormat
115
116 -function formatDate(timestamp: string): string {
117 - return dayjs(timestamp).format(dFormats.datetimesec)
118 -}
119 -
116 function stop() {
117 loading.value = true
118
frontend/src/components/integrations/IntegrationItem.vue
+1 -1
@@ -51,7 +51,7 @@ import Icon from "@/components/common/Icon.vue"
51 import Badge from "@/components/common/Badge.vue"
52 import { defineAsyncComponent, ref, toRefs } from "vue"
53 import { NModal, NRadio, NButton } from "naive-ui"
54 -import type { AvailableIntegration } from "@/types/integrations"
54 +import type { AvailableIntegration } from "@/types/integrations.d"
55 const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
56
57 const props = defineProps<{
frontend/src/components/integrations/IntegrationsList.vue
+1 -1
@@ -34,7 +34,7 @@ import { ref, onBeforeMount, computed } from "vue"
34 import { useMessage, NSpin, NEmpty } from "naive-ui"
35 import Api from "@/api"
36 import IntegrationItem from "./IntegrationItem.vue"
37 -import type { AvailableIntegration } from "@/types/integrations"
37 +import type { AvailableIntegration } from "@/types/integrations.d"
38
39 const { embedded, hideTotals, selectable, disabledIdsList } = defineProps<{
40 embedded?: boolean
frontend/src/components/license/LicenseEditor.vue new
+361
@@ -0,0 +1,361 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <div class="license-box" :class="{ loading: loadingLicense && !licenseKey }">
4 + <p class="flex gap-4 items-center" v-if="loadingLicense || licenseKey">
5 + <span>your license:</span>
6 + <Icon :name="LoadingIcon" v-if="loadingLicense"></Icon>
7 + </p>
8 + <div v-if="!loadingLicense && !licenseKey">
9 + <p class="flex gap-4 items-center" v-if="!creationEnabled">no license found</p>
10 + <div class="flex items-center gap-4 mt-2">
11 + <div class="actions-box flex gap-2">
12 + <n-button
13 + type="primary"
14 + :loading="loadingCreation"
15 + @click="enableCreation()"
16 + v-if="!creationEnabled"
17 + size="small"
18 + >
19 + <template #icon>
20 + <Icon :name="LicenseIcon"></Icon>
21 + </template>
22 + Create new license
23 + </n-button>
24 + </div>
25 + </div>
26 + </div>
27 + <div class="flex items-center gap-4 mt-1" v-if="!replaceEnabled && licenseKey">
28 + <h3>{{ licenseKey }}</h3>
29 + <div class="actions-box flex gap-2">
30 + <n-button secondary :disabled="replaceEnabled" @click="enableReplace()" size="small">
31 + <template #icon>
32 + <Icon :name="EditIcon"></Icon>
33 + </template>
34 + Edit
35 + </n-button>
36 + <n-button
37 + type="primary"
38 + :loading="loadingExtend"
39 + @click="enableExtend()"
40 + v-if="!extendEnabled"
41 + size="small"
42 + >
43 + <template #icon>
44 + <Icon :name="ExtendIcon"></Icon>
45 + </template>
46 + Extend
47 + </n-button>
48 + </div>
49 + </div>
50 + </div>
51 +
52 + <div class="replace-box mt-2 flex gap-2" v-if="replaceEnabled">
53 + <n-input v-model:value="licenseKeyModel" class="grow !max-w-72" clearable />
54 + <n-button secondary :disabled="loadingReplace" @click="resetLicense()">Reset</n-button>
55 + <n-button type="success" :loading="loadingReplace" :disabled="!licenseKeyModel" @click="replaceLicense()">
56 + <template #icon>
57 + <Icon :name="EditIcon"></Icon>
58 + </template>
59 + Replace
60 + </n-button>
61 + </div>
62 +
63 + <div class="extend-box mt-5 flex gap-2" v-if="extendEnabled">
64 + <n-input-number v-model:value="period" class="grow !max-w-44" :min="1">
65 + <template #prefix>
66 + <div class="min-w-12">Day{{ period === 1 ? "" : "s" }}</div>
67 + </template>
68 + </n-input-number>
69 + <n-button secondary :disabled="loadingExtend" @click="resetPeriod()">Reset</n-button>
70 + <n-button type="success" :loading="loadingExtend" :disabled="!period" @click="extendLicense()">
71 + <template #icon>
72 + <Icon :name="ExtendIcon"></Icon>
73 + </template>
74 + Extend
75 + </n-button>
76 + </div>
77 +
78 + <div class="create-box flex flex-col gap-2" v-if="creationEnabled">
79 + <n-form :label-width="80" :model="creationForm" :rules="rules" ref="formRef">
80 + <div class="grid gap-2 grid-auto-flow-200">
81 + <n-form-item label="Name" path="name">
82 + <n-input v-model:value.trim="creationForm.name" placeholder="Input name..." clearable />
83 + </n-form-item>
84 + <n-form-item label="Email" path="email">
85 + <n-input v-model:value.trim="creationForm.email" placeholder="Input email..." clearable />
86 + </n-form-item>
87 + <n-form-item label="Company Name" path="companyName">
88 + <n-input
89 + v-model:value.trim="creationForm.companyName"
90 + placeholder="Input Company Name..."
91 + clearable
92 + />
93 + </n-form-item>
94 + </div>
95 + </n-form>
96 + <div class="flex gap-2 justify-end">
97 + <n-button secondary :disabled="loadingCreation" @click="resetCreation()">Reset</n-button>
98 + <n-button
99 + type="success"
100 + :loading="loadingCreation"
101 + :disabled="!isCreationFormValid"
102 + @click="validateCreation()"
103 + >
104 + <template #icon>
105 + <Icon :name="LicenseIcon"></Icon>
106 + </template>
107 + Create License
108 + </n-button>
109 + </div>
110 + </div>
111 + </n-spin>
112 +</template>
113 +
114 +<script setup lang="ts">
115 +import {
116 + NInput,
117 + NInputNumber,
118 + NButton,
119 + NSpin,
120 + NFormItem,
121 + NForm,
122 + useMessage,
123 + type FormRules,
124 + type FormItemRule,
125 + type FormInst,
126 + type FormValidationError
127 +} from "naive-ui"
128 +import Icon from "@/components/common/Icon.vue"
129 +import Api from "@/api"
130 +import { onBeforeMount, ref } from "vue"
131 +import isEmail from "validator/es/lib/isEmail"
132 +import { computed } from "vue"
133 +import type { LicenseKey } from "@/types/license.d"
134 +import type { NewLicensePayload } from "@/api/license"
135 +
136 +const emit = defineEmits<{
137 + (e: "updated"): void
138 +}>()
139 +
140 +const LoadingIcon = "eos-icons:loading"
141 +const EditIcon = "uil:edit-alt"
142 +const LicenseIcon = "carbon:license"
143 +const ExtendIcon = "majesticons:clock-plus-line"
144 +
145 +const formRef = ref<FormInst>()
146 +const message = useMessage()
147 +const loadingLicense = ref(false)
148 +const loadingReplace = ref(false)
149 +const loadingExtend = ref(false)
150 +const loadingCreation = ref(false)
151 +
152 +const licenseKey = ref<LicenseKey | "">("")
153 +const licenseKeyModel = ref<LicenseKey | "">("")
154 +const period = ref<number>(15)
155 +const creationForm = ref<NewLicensePayload>(getCreationForm())
156 +const replaceEnabled = ref(false)
157 +const extendEnabled = ref(false)
158 +const creationEnabled = ref(false)
159 +
160 +const isCreationFormValid = computed(() => {
161 + if (!creationForm.value.name || !creationForm.value.email || !creationForm.value.companyName) {
162 + return false
163 + }
164 + return true
165 +})
166 +
167 +const loading = computed(
168 + () => loadingLicense.value || loadingReplace.value || loadingExtend.value || loadingCreation.value
169 +)
170 +
171 +const rules: FormRules = {
172 + name: {
173 + required: true,
174 + message: "Please input name",
175 + trigger: ["input", "blur"]
176 + },
177 + companyName: {
178 + required: true,
179 + message: "Please input company name",
180 + trigger: ["input", "blur"]
181 + },
182 + email: {
183 + required: true,
184 + trigger: ["input", "blur"],
185 + validator: (rule: FormItemRule, value: string) => {
186 + if (!value) {
187 + return new Error("Email is required")
188 + }
189 + if (!isEmail(value)) {
190 + return new Error("The email is not formatted correctly")
191 + }
192 + }
193 + }
194 +}
195 +
196 +function getLicense() {
197 + loadingLicense.value = true
198 +
199 + Api.license
200 + .getLicense()
201 + .then(res => {
202 + if (res.data.success) {
203 + licenseKey.value = res.data?.license_key || ""
204 + licenseKeyModel.value = res.data?.license_key || ""
205 + } else {
206 + message.warning(res.data?.message || "An error occurred. Please try again later.")
207 + }
208 + })
209 + .catch(err => {
210 + if (err.response.status !== 404) {
211 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
212 + }
213 + })
214 + .finally(() => {
215 + loadingLicense.value = false
216 + })
217 +}
218 +
219 +function replaceLicense() {
220 + if (licenseKeyModel.value) {
221 + loadingReplace.value = true
222 +
223 + Api.license
224 + .replaceLicense(licenseKeyModel.value)
225 + .then(res => {
226 + if (res.data.success) {
227 + disableReplace()
228 + message.success(res.data?.message || "License replaced successfully")
229 + emit("updated")
230 + getLicense()
231 + } else {
232 + message.warning(res.data?.message || "An error occurred. Please try again later.")
233 + }
234 + })
235 + .catch(err => {
236 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
237 + })
238 + .finally(() => {
239 + loadingReplace.value = false
240 + })
241 + }
242 +}
243 +
244 +function extendLicense() {
245 + if (period.value) {
246 + loadingExtend.value = true
247 +
248 + Api.license
249 + .extendLicense(period.value)
250 + .then(res => {
251 + if (res.data.success) {
252 + resetPeriod()
253 + message.success(res.data?.message || "License extended successfully")
254 + emit("updated")
255 + } else {
256 + message.warning(res.data?.message || "An error occurred. Please try again later.")
257 + }
258 + })
259 + .catch(err => {
260 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
261 + })
262 + .finally(() => {
263 + loadingExtend.value = false
264 + })
265 + }
266 +}
267 +
268 +function createLicense() {
269 + loadingCreation.value = true
270 +
271 + Api.license
272 + .createLicense(creationForm.value)
273 + .then(res => {
274 + if (res.data.success) {
275 + resetCreation()
276 + message.success(res.data?.message || "License created successfully")
277 + emit("updated")
278 + getLicense()
279 + } else {
280 + message.warning(res.data?.message || "An error occurred. Please try again later.")
281 + }
282 + })
283 + .catch(err => {
284 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
285 + })
286 + .finally(() => {
287 + loadingCreation.value = false
288 + })
289 +}
290 +
291 +function resetLicense() {
292 + licenseKeyModel.value = licenseKey.value
293 + disableReplace()
294 +}
295 +
296 +function disableReplace() {
297 + replaceEnabled.value = false
298 +}
299 +
300 +function enableReplace() {
301 + resetPeriod()
302 + replaceEnabled.value = true
303 +}
304 +
305 +function resetPeriod() {
306 + period.value = 15
307 + disableExtend()
308 +}
309 +
310 +function disableExtend() {
311 + extendEnabled.value = false
312 +}
313 +
314 +function enableExtend() {
315 + extendEnabled.value = true
316 +}
317 +
318 +function resetCreation() {
319 + creationForm.value = getCreationForm()
320 + disableCreation()
321 +}
322 +
323 +function disableCreation() {
324 + creationEnabled.value = false
325 +}
326 +
327 +function enableCreation() {
328 + creationEnabled.value = true
329 +}
330 +
331 +function getCreationForm(): NewLicensePayload {
332 + return {
333 + name: "",
334 + email: "",
335 + companyName: ""
336 + }
337 +}
338 +
339 +function validateCreation() {
340 + if (!formRef.value) return
341 +
342 + formRef.value.validate((errors?: Array<FormValidationError>) => {
343 + if (!errors) {
344 + createLicense()
345 + } else {
346 + message.warning("You must fill in the required fields correctly.")
347 + return false
348 + }
349 + })
350 +}
351 +
352 +onBeforeMount(() => {
353 + getLicense()
354 +})
355 +</script>
356 +
357 +<style lang="scss" scoped>
358 +.license-box.loading {
359 + min-height: 100px;
360 +}
361 +</style>
frontend/src/components/license/LicenseViewer.vue new
+181
@@ -0,0 +1,181 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <p class="mb-2" v-if="license">license details:</p>
4 + <div class="license-box flex flex-col gap-7" v-if="license">
5 + <div class="section" v-if="!hideKey">
6 + <div class="label">
7 + <Icon :name="KeyIcon" :size="14"></Icon>
8 + Key:
9 + </div>
10 + <div class="value">{{ license.key }}</div>
11 + </div>
12 + <div class="section">
13 + <div class="label">
14 + <Icon :name="ExpiresIcon" :size="14"></Icon>
15 + Expires:
16 + </div>
17 + <div class="value">{{ expiresText }}</div>
18 + </div>
19 + <div class="section">
20 + <div class="label">
21 + <Icon :name="PeriodIcon" :size="14"></Icon>
22 + Period:
23 + </div>
24 + <div class="value">{{ periodText }}</div>
25 + </div>
26 + <div class="section">
27 + <div class="label">
28 + <Icon :name="CustomerIcon" :size="14"></Icon>
29 + Customer:
30 + </div>
31 + <div class="value grid gap-2 grid-auto-flow-200">
32 + <KVCard v-for="(value, key) of license.customer" :key="key">
33 + <template #key>{{ key }}</template>
34 + <template #value>
35 + <template v-if="key === 'Created'">
36 + {{ formatDate(value, dFormats.datetime) }}
37 + </template>
38 + <template v-else>{{ value ?? "-" }}</template>
39 + </template>
40 + </KVCard>
41 + </div>
42 + </div>
43 + <div class="section">
44 + <div class="label">
45 + <Icon :name="FeaturesIcon" :size="14"></Icon>
46 + Features:
47 + </div>
48 + <div class="value">{{ featuresText || "No feature enabled" }}</div>
49 + </div>
50 + </div>
51 + </n-spin>
52 +</template>
53 +
54 +<script setup lang="ts">
55 +import { NSpin, useMessage } from "naive-ui"
56 +import Icon from "@/components/common/Icon.vue"
57 +import Api from "@/api"
58 +import { onBeforeMount, onMounted, ref } from "vue"
59 +import { computed } from "vue"
60 +import { LicenseFeatures, type License } from "@/types/license.d"
61 +import { formatDate } from "@/utils"
62 +import { useSettingsStore } from "@/stores/settings"
63 +import KVCard from "@/components/common/KVCard.vue"
64 +
65 +const emit = defineEmits<{
66 + (
67 + e: "mounted",
68 + value: {
69 + reload: () => void
70 + }
71 + ): void
72 +}>()
73 +
74 +const { hideKey } = defineProps<{ hideKey?: boolean }>()
75 +
76 +const KeyIcon = "ph:key"
77 +const ExpiresIcon = "ph:calendar-blank"
78 +const PeriodIcon = "majesticons:clock-line"
79 +const CustomerIcon = "carbon:user"
80 +const FeaturesIcon = "material-symbols:checklist"
81 +
82 +const message = useMessage()
83 +const loadingLicense = ref(false)
84 +const loadingFeatures = ref(false)
85 +const dFormats = useSettingsStore().dateFormat
86 +
87 +const license = ref<License | null>(null)
88 +const features = ref<LicenseFeatures[]>([])
89 +const expiresText = computed(() => (license.value ? formatDate(license.value.expires, dFormats.datetime) : ""))
90 +const periodText = computed(() =>
91 + license.value ? `${license.value.period} Day${license.value.period === 1 ? "" : "s"}` : ""
92 +)
93 +const featuresText = computed(() => features.value.join(", "))
94 +
95 +const loading = computed(() => loadingLicense.value || loadingFeatures.value)
96 +
97 +function getLicense() {
98 + loadingLicense.value = true
99 +
100 + Api.license
101 + .verifyLicense()
102 + .then(res => {
103 + if (res.data.success) {
104 + license.value = res.data?.license
105 + } else {
106 + message.warning(res.data?.message || "An error occurred. Please try again later.")
107 + }
108 + })
109 + .catch(err => {
110 + if (err.response.status !== 404) {
111 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
112 + }
113 + })
114 + .finally(() => {
115 + loadingLicense.value = false
116 + })
117 +}
118 +
119 +function getLicenseFeatures() {
120 + loadingFeatures.value = true
121 +
122 + Api.license
123 + .getLicenseFeatures()
124 + .then(res => {
125 + if (res.data.success) {
126 + features.value = res.data?.features
127 + } else {
128 + message.warning(res.data?.message || "An error occurred. Please try again later.")
129 + }
130 + })
131 + .catch(err => {
132 + if (err.response.status !== 404) {
133 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
134 + }
135 + })
136 + .finally(() => {
137 + loadingFeatures.value = false
138 + })
139 +}
140 +
141 +function load() {
142 + getLicense()
143 + getLicenseFeatures()
144 +}
145 +
146 +onBeforeMount(() => {
147 + load()
148 +})
149 +
150 +onMounted(() => {
151 + emit("mounted", {
152 + reload: load
153 + })
154 +})
155 +</script>
156 +
157 +<style lang="scss" scoped>
158 +.license-box {
159 + background-color: var(--bg-color);
160 + border-radius: var(--border-radius);
161 + padding: 14px 18px;
162 + .section {
163 + display: flex;
164 + flex-direction: column;
165 + gap: 6px;
166 + .label {
167 + display: flex;
168 + align-items: center;
169 + gap: 10px;
170 + color: var(--fg-secondary-color);
171 + font-family: var(--font-family-mono);
172 + font-size: 14px;
173 + }
174 +
175 + .value {
176 + font-size: 16px;
177 + font-weight: bold;
178 + }
179 + }
180 +}
181 +</style>
frontend/src/components/stackProvisioning/StackProvisioningItem.vue
+1 -1
@@ -34,7 +34,7 @@ import { ref } from "vue"
34 import Icon from "@/components/common/Icon.vue"
35 import { NButton, useMessage } from "naive-ui"
36 import Api from "@/api"
37 -import type { AvailableContentPack } from "@/types/stackProvisioning"
37 +import type { AvailableContentPack } from "@/types/stackProvisioning.d"
38
39 const emit = defineEmits<{
40 (e: "provisioned"): void
frontend/src/components/stackProvisioning/StackProvisioningList.vue
+1 -1
@@ -18,7 +18,7 @@ import { ref, onBeforeMount, computed } from "vue"
18 import { useMessage, NSpin, NEmpty } from "naive-ui"
19 import Api from "@/api"
20 import StackProvisioningItem from "./StackProvisioningItem.vue"
21 -import type { AvailableContentPack } from "@/types/stackProvisioning"
21 +import type { AvailableContentPack } from "@/types/stackProvisioning.d"
22
23 const message = useMessage()
24 const loadingList = ref(false)
frontend/src/layouts/common/Toolbar/Avatar.vue
+6
@@ -12,6 +12,7 @@ import { ref, h } from "vue"
12 import { useAuthStore } from "@/stores/auth"
13
14 const UserIcon = "ion:person-outline"
15 +const LicenseIcon = "carbon:license"
16 const LogoutIcon = "ion:log-out-outline"
17 const ContactIcon = "ic:outline-alternate-email"
18
@@ -29,6 +30,11 @@ const options = ref([
30 key: "route-Profile",
31 icon: renderIcon(UserIcon)
32 },
33 + {
34 + label: "License",
35 + key: "route-License",
36 + icon: renderIcon(LicenseIcon)
37 + },
38 {
39 label: () =>
40 h(
frontend/src/router/index.ts
+6
@@ -147,6 +147,12 @@ const router = createRouter({
147 component: () => import("@/views/ReportCreation.vue"),
148 meta: { title: "Report Creation", auth: true, roles: UserRole.All }
149 },
150 + {
151 + path: "/license",
152 + name: "License",
153 + component: () => import("@/views/License.vue"),
154 + meta: { title: "License", auth: true, roles: UserRole.All }
155 + },
156
157 {
158 path: "/profile",
frontend/src/types/customers.d.ts
+2
@@ -81,6 +81,8 @@ export interface CustomerProvision {
81 wazuh_cluster_key: string
82 wazuh_master_ip: string
83 grafana_url: string
84 + provision_wazuh_worker: boolean
85 + provision_ha_proxy: boolean
86 }
87
88 export interface CustomerDecomissionedData {
frontend/src/types/license.d.ts new
+48
@@ -0,0 +1,48 @@
1 +export interface License {
2 + product_id: number
3 + id: number
4 + key: string
5 + created: string
6 + expires: string
7 + period: number
8 + f1: boolean
9 + f2: boolean
10 + f3: boolean
11 + f4: boolean
12 + f5: boolean
13 + f6: boolean
14 + f7: boolean
15 + f8: boolean
16 + notes: string
17 + block: boolean
18 + global_id: number
19 + customer: LicenseCustomer
20 + activated_machines: { [key: string]: string }
21 + trial_activation: boolean
22 + max_no_of_machines: number
23 + allowed_machines: null | string
24 + data_objects: LicenseDataObject[]
25 + sign_date: string
26 + reseller: null | string
27 +}
28 +
29 +export interface LicenseCustomer {
30 + Id: number
31 + Name: string
32 + Email: string
33 + CompanyName: string
34 + Created: number
35 +}
36 +
37 +export interface LicenseDataObject {
38 + Id: number
39 + Name: string
40 + StringValue: string
41 + IntValue: number
42 +}
43 +
44 +export enum LicenseFeatures {
45 + "Reporting" = "REPORTING"
46 +}
47 +
48 +export type LicenseKey = `${string}-${string}-${string}-${string}`
frontend/src/utils/index.ts
+12
@@ -2,6 +2,7 @@ import Icon from "@/components/common/Icon.vue"
2 import { type Component, h } from "vue"
3 import { isMobile as detectMobile } from "detect-touch-device"
4 import { md5 } from "js-md5"
5 +import dayjs from "@/utils/dayjs"
6
7 export type OS = "Unknown" | "Windows" | "MacOS" | "UNIX" | "Linux"
8
@@ -79,3 +80,14 @@ export const delay = (t: number) => {
80 export const hashMD5 = (text: number | string) => {
81 return md5(text.toString())
82 }
83 +
84 +export function formatDate(date: Date | string | number, format: string) {
85 + let parsedDate = date
86 + if (typeof date === "number" && date.toString().length === 10) {
87 + parsedDate = date * 1000
88 + }
89 + const datejs = dayjs(parsedDate)
90 + if (!datejs.isValid()) return date
91 +
92 + return datejs.format(format)
93 +}
frontend/src/views/License.vue new
+20
@@ -0,0 +1,20 @@
1 +<template>
2 + <div class="page flex flex-col gap-8">
3 + <LicenseEditor @updated="reload()" />
4 + <LicenseViewer @mounted="licenseViewerCTX = $event" hide-key />
5 + </div>
6 +</template>
7 +
8 +<script setup lang="ts">
9 +import LicenseEditor from "@/components/license/LicenseEditor.vue"
10 +import LicenseViewer from "@/components/license/LicenseViewer.vue"
11 +import { ref } from "vue"
12 +
13 +const licenseViewerCTX = ref<{ reload: () => void } | null>(null)
14 +
15 +function reload() {
16 + if (licenseViewerCTX.value) {
17 + licenseViewerCTX.value.reload()
18 + }
19 +}
20 +</script>
frontend/src/views/ReportCreation.vue
+1 -1
@@ -21,7 +21,7 @@
21 import { ref } from "vue"
22 import ReportWizard from "@/components/reportCreation/Wizard.vue"
23 import ReportPanels from "@/components/reportCreation/Panels.vue"
24 -import type { Dashboard, Org, Panel } from "@/types/reporting"
24 +import type { Dashboard, Org, Panel } from "@/types/reporting.d"
25 import type { ReportTimeRange } from "@/api/reporting"
26 import Icon from "@/components/common/Icon.vue"
27
frontend/src/views/graylog/Management.vue
+1 -1
@@ -44,7 +44,7 @@ import Events from "@/components/graylog/Events/List.vue"
44 import Streams from "@/components/graylog/Streams/List.vue"
45 import MonitoringAlerts from "@/components/graylog/MonitoringAlerts/List.vue"
46 import Inputs from "@/components/graylog/Inputs/List.vue"
47 -import type { EventDefinition } from "@/types/graylog/event-definition"
47 +import type { EventDefinition } from "@/types/graylog/event-definition.d"
48 import { useRoute, useRouter } from "vue-router"
49 import { watch } from "vue"
50
frontend/src/views/graylog/Metrics.vue
+2 -6
@@ -6,7 +6,7 @@
6 <template #icon><Icon :name="UpdatedIcon" :size="15"></Icon></template>
7 </n-button>
8 <span>Last check:</span>
9 - <strong>{{ lastCheck ? formatDate(lastCheck) : "..." }}</strong>
9 + <strong>{{ lastCheck ? formatDate(lastCheck, dFormats.datetimesec) : "..." }}</strong>
10 </div>
11
12 <div class="toolbar flex items-center gap-3">
@@ -40,7 +40,7 @@ import type { ThroughputMetric } from "@/types/graylog/index.d"
40 import Icon from "@/components/common/Icon.vue"
41 import UncommittedEntries from "@/components/graylog/Metrics/UncommittedEntries.vue"
42 import MetricsList from "@/components/graylog/Metrics/List.vue"
43 -import dayjs from "@/utils/dayjs"
43 +import { formatDate } from "@/utils"
44 import { useSettingsStore } from "@/stores/settings"
45 import { useStorage } from "@vueuse/core"
46
@@ -116,10 +116,6 @@ function start() {
116 getDataTimer.value = setInterval(getData, intervalSelected.value)
117 }
118
119 -function formatDate(timestamp: string | Date): string {
120 - return dayjs(timestamp).format(dFormats.datetimesec)
121 -}
122 -
119 watch(intervalSelected, () => {
120 stop()
121 nextTick(() => {
report-template-test.html deleted
-113
@@ -1,113 +0,0 @@
1 -<html lang="en">
2 - <head>
3 - <meta charset="UTF-8" />
4 - <title>test</title>
5 - <style>
6 - html,
7 - body {
8 - padding: 0;
9 - margin: 0;
10 - }
11 -
12 - * {
13 - box-sizing: border-box;
14 - }
15 - </style>
16 -
17 - <script src="https://cdnjs.cloudflare.com/ajax/libs/nunjucks/2.4.2/nunjucks.min.js"></script>
18 - <script src="https://code.jquery.com/jquery-latest.js"></script>
19 - </head>
20 -
21 - <body>
22 - <span id="output"></span>
23 -
24 - <script>
25 - var content = `
26 -{% set panels = panels | we_parse %}
27 -
28 -<html>
29 - <head>
30 - <style>
31 - :root {
32 - --border-radius: 6px;
33 - --bg-secondary-color: red;
34 - --border-small-050: 1px solid green;
35 - }
36 -
37 - html,
38 - body {
39 - padding: 0;
40 - margin: 0;
41 - }
42 -
43 - * {
44 - box-sizing: border-box;
45 - }
46 -
47 - .panels-container {
48 - background-color: var(--bg-secondary-color);
49 - display: flex;
50 - flex-wrap: wrap;
51 - box-sizing: border-box;
52 - padding: 10px;
53 - }
54 -
55 - .panel {
56 - background-color: var(--bg-secondary-color);
57 - overflow: hidden;
58 - flex-grow: 1;
59 - min-width: 100px;
60 - box-sizing: border-box;
61 - padding: 10px;
62 - }
63 -
64 - .panel img {
65 - width: 100%;
66 - border-radius: var(--border-radius);
67 - border: var(--border-small-050);
68 - }
69 - </style>
70 - </head>
71 - <body>
72 - <div class="panels-container">
73 - {% for panel in panels %}
74 - <div class="panel" style="{{'flex-basis:'+panel.width+'%' if panel.width else ''}}">
75 - <img src="{{panel.image}}" />
76 - </div>
77 - {% endfor %}
78 - </div>
79 - </body>
80 -</html>
81 -`
82 -
83 - function _parse(string) {
84 - if (typeof string === "string") {
85 - try {
86 - return JSON.parse(string)
87 - } catch (e) {
88 - return {}
89 - }
90 - }
91 - return {}
92 - }
93 -
94 - var env = new nunjucks.Environment()
95 - env.addGlobal("we_parse", _parse)
96 - env.addFilter("we_parse", _parse)
97 - var t = nunjucks.compile(content, env)
98 -
99 - var ctx = {
100 - panels: JSON.stringify([
101 - { width: 50, image: "https://placehold.co/600x400" },
102 - { width: 50, image: "https://placehold.co/600x400" },
103 - { width: 0, image: "https://placehold.co/600x400" },
104 - { width: 20, image: "https://placehold.co/600x400" },
105 - { width: 80, image: "https://placehold.co/600x400" }
106 - ])
107 - }
108 -
109 - console.log(t.render(ctx))
110 - $("#output").html(t.render(ctx))
111 - </script>
112 - </body>
113 -</html>
report-template.html deleted
-56
@@ -1,56 +0,0 @@
1 -{% set panels = panels | we_parse %}
2 -
3 -<html>
4 - <head>
5 - <style>
6 - :root {
7 - --border-radius: 6px;
8 - --bg-secondary-color: red;
9 - --border-small-050: 1px solid green;
10 - }
11 -
12 - html,
13 - body {
14 - padding: 0;
15 - margin: 0;
16 - }
17 -
18 - * {
19 - box-sizing: border-box;
20 - }
21 -
22 - .panels-container {
23 - background-color: var(--bg-secondary-color);
24 - display: flex;
25 - flex-wrap: wrap;
26 - box-sizing: border-box;
27 - padding: 10px;
28 - }
29 -
30 - .panel {
31 - background-color: var(--bg-secondary-color);
32 - overflow: hidden;
33 - flex-grow: 1;
34 - min-width: 100px;
35 - box-sizing: border-box;
36 - padding: 10px;
37 - page-break-inside: avoid;
38 - }
39 -
40 - .panel img {
41 - width: 100%;
42 - border-radius: var(--border-radius);
43 - border: var(--border-small-050);
44 - }
45 - </style>
46 - </head>
47 - <body>
48 - <div class="panels-container">
49 - {% for panel in panels %}
50 - <div class="panel" style="{{'flex-basis:'+panel.width+'%' if panel.width else ''}}">
51 - <img src="{{panel.image}}" />
52 - </div>
53 - {% endfor %}
54 - </div>
55 - </body>
56 -</html>