@cryptotaxi247 / CoPilot / commits / 2392a6aa

Datastore (#301)

* refactor: Add CaseDataStore model and relationship to Case model * refactor: Add CaseDataStore model and relationship to Case model * chore: Add MINIO_SECURE environment variable to .env.example and use it in data_store_session.py * feat: Add endpoint for uploading case data store...still testing * refactor: Add endpoint for uploading file to case * feat: Add delete endpoint for case data store file * refactor: Add ListCaseDataStoreResponse model and endpoints for listing case data store files * refactor: Add download endpoint for case data store file * refactor: Update README.md with YouTube channel subscribers badge * filter alerts by customer code * refactor: updated pinia setttings * chore: update vue dependencies * refactor: vue components and utils * new eslint settings * refactor: eslint fix * refactor: eslint fix * refactor: eslint fix * refactor: eslint fix * refactor: eslint fix * refactor: eslint fix * refactor: eslint fix * fix: vue dependencies conflicts * refactor: Add customer code field to Case model and update related endpoints * refactor: Add list_cases_by_customer_code endpoint and related functions * refactor: eslint fix * refactor: Improve search for index source in Wazuh-Indexer * refactor: Update Docker tags for backend and frontend images * Update miniopy-async dependency to version 1.21.1 * chore: Update default value for MINIO_ROOT_PASSWORD in data_store_session.py * refactor: Optimize index source retrieval in Wazuh-Indexer * refactor: Update post_to_copilot_mimecast_module signature to include optional license_key parameter * refactor: Add license_key parameter to post_to_copilot_huntress_module * refactor: Update post_to_copilot_carbonblack_module signature to include optional license_key parameter * refactor: Add list_alerts_by_source_endpoint and related functions * refactor: agent sca download * chore: update vue dependencies * feat: add CaseDataStore components * feat: add CaseDataStore api/types * refactor: Remove wazuh_index_fields_resize and rename * feat: Upload Data Store file * refactor: lint * feat: Enable bucket creation during initialization The code changes in `copilot.py` enable the creation of buckets during the initialization process. This was previously commented out but is now uncommented to ensure that the buckets are created. Commit message suggestion: `feat: Enable bucket creation during initialization` * export cases and alerts * chore: update dependencies in frontend/package.json * feat: Add multiple filters for listing alerts * chore: Update Docker tags for backend and frontend images * feat: add customerCode and source filter to incident management API * feat: add customer_code and source filter to AlertsList * feat: add link to customer code in cases list and overview * feat: autocomplete for customerCode and source filters * feat: Add CSV export functionality for cases and alerts * refactor: incidentManagement components * added customer_code to filename * refactor: feat: improve support for userId parameter in getLogs function * refactor: update baseInfo computation to return merged SocCaseExt values * refactor: update Docker workflow to use "lab" tags for backend and frontend images * refactor: Remove Discord notifications from Docker workflow * refactor: Update Docker workflow to use v0.1.2 of CoPilot * Update .env file to configure Copilot environment variables * chore: update dependencies in frontend * chore: update optional dependencies in frontend * feat: add exportCases api * refactor: ActiveResponseActions * refactor: download fileName * refactor: CaseCreationButton props * refactor: incidentManagement filters * feat: add cases export feature * some precommit fixes * chore: Update Docker tags for backend and frontend images * update precommit eslint * fix: eslint config * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Sep 22, 2024 at 09:19 UTC 2392a6aa927af14b794400da2c8d42c87af0d2b8
416 files changed +11339 -4898
.env.example
+5
@@ -6,6 +6,11 @@ MYSQL_ROOT_PASSWORD=REPLACE_WITH_PASSWORD
6 MYSQL_USER=copilot
7 MYSQL_PASSWORD=REPLACE_WITH_PASSWORD
8
9 +MINIO_URL=copilot-minio
10 +MINIO_ROOT_USER=admin
11 +MINIO_ROOT_PASSWORD=REPLACE_ME
12 +MINIO_SECURE=False
13 +
14 # ! ALERT FORWARDING IP
15 # Set this to the IP of the host running CoPilot. This is used by Graylog to forward alerts to CoPilot
16 # ! Not needed anymore since we are reading from the index now
.pre-commit-config.yaml
+2 -10
@@ -51,15 +51,7 @@ repos:
51 - id: prettier
52
53 - repo: https://github.com/pre-commit/mirrors-eslint
54 - rev: v9.5.0
54 + rev: v9.11.0
55 hooks:
56 - id: eslint
57 - files: \.([cjt]sx?|[cm]ts|[cm]js|cvue)$ # *.js, *.jsx, *.ts, *.tsx and *.vue
58 - args: ["--config", "frontend/eslint.config.js"]
59 - additional_dependencies:
60 - - eslint@9.5.0
61 - - "@vue/eslint-config-prettier@9.0.0"
62 - - "@vue/eslint-config-typescript@13.0.0"
63 - - eslint-plugin-cypress@3.3.0
64 - - eslint-plugin-vue@9.26.0
65 - - globals@15.6.0
57 + args: ["--config", "frontend/eslint.config.mjs"]
.vscode/settings.json
+2
@@ -1,6 +1,7 @@
1 {
2 "cSpell.words": [
3 "ajoelp",
4 + "antfu",
5 "apexchart",
6 "arcticons",
7 "artifactstring",
@@ -31,6 +32,7 @@
32 "iconoir",
33 "Indicies",
34 "lastupdate",
35 + "linebreak",
36 "Logsource",
37 "majesticons",
38 "mimecast",
README.md
+5 -2
@@ -5,10 +5,12 @@
5 SOCFortress CoPilot
6
7 [![Medium](https://img.shields.io/badge/Medium-12100E?style=for-the-badge&logo=medium&logoColor=white)](https://socfortress.medium.com/)
8 -[![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/@taylorwalton_socfortress/videos)
8 +[![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC4EUQtTxeC8wGrKRafI6pZg)](https://www.youtube.com/@taylorwalton_socfortress/videos)
9 [![Discord Shield](https://discordapp.com/api/guilds/871419379999469568/widget.png?style=shield)](https://discord.gg/UN3pNBzaEQ)
10 [![GitHub Sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/taylorwalton)
11
12 +[![Email Us](https://img.shields.io/badge/📧%20Email%20Us-We're%20Here%20to%20Help!-orange?style=for-the-badge)](https://www.socfortress.co/contact_form.html)
13 +
14 </h1><h4 align="center">
15
16 [SOCFortress CoPilot](https://www.socfortress.co) focuses on providing a single pane of glass for all your security operations needs. Simplify your open source security stack with a single platform focused on making open source security tools easier to use and more accessible.
@@ -83,7 +85,7 @@ systemctl restart docker
85
86 ```bash
87 # Clone the CoPilot repository
86 -wget https://raw.githubusercontent.com/socfortress/CoPilot/v0.1.1/docker-compose.yml
88 +wget https://raw.githubusercontent.com/socfortress/CoPilot/v0.1.2/docker-compose.yml
89
90 # Edit the docker-compose.yml file to set the server name and/or the services you want to use
91
@@ -91,6 +93,7 @@ wget https://raw.githubusercontent.com/socfortress/CoPilot/v0.1.1/docker-compose
93 mkdir data
94
95 # Create the .env file based on the .env.example
96 +nano .env
97
98 # Run Copilot
99 docker compose up -d
backend/alembic/alembic.ini
+1 -1
@@ -60,7 +60,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
60 # are written from script.py.mako
61 # output_encoding = utf-8
62
63 -sqlalchemy.url = mysql+pymysql://copilot:REPLACE_ME@copilot-mysql/copilot
63 +sqlalchemy.url = mysql+pymysql://copilot:H7U3AHsXWSGvE5L123B7$GQdLQz@10.255.254.2/copilot
64
65
66 [post_write_hooks]
backend/alembic/env.py
+1
@@ -31,6 +31,7 @@ from app.incidents.models import Asset
31 from app.incidents.models import AssetFieldName
32 from app.incidents.models import Case
33 from app.incidents.models import CaseAlertLink
34 +from app.incidents.models import CaseDataStore
35 from app.incidents.models import Comment
36 from app.incidents.models import CustomerCodeFieldName
37 from app.incidents.models import FieldName
backend/alembic/versions/068f32ae5984_add_incident_management_cases_data_.py new
+47
@@ -0,0 +1,47 @@
1 +"""Add incident management cases data store table
2 +
3 +Revision ID: 068f32ae5984
4 +Revises: e18dc3169b33
5 +Create Date: 2024-09-13 15:15:02.050101
6 +
7 +"""
8 +from typing import Sequence
9 +from typing import Union
10 +
11 +import sqlalchemy as sa
12 +
13 +from alembic import op
14 +
15 +# revision identifiers, used by Alembic.
16 +revision: str = "068f32ae5984"
17 +down_revision: Union[str, None] = "e18dc3169b33"
18 +branch_labels: Union[str, Sequence[str], None] = None
19 +depends_on: Union[str, Sequence[str], None] = None
20 +
21 +
22 +def upgrade() -> None:
23 + # ### commands auto generated by Alembic - please adjust! ###
24 + op.create_table(
25 + "incident_management_case_datastore",
26 + sa.Column("id", sa.Integer(), nullable=False),
27 + sa.Column("case_id", sa.Integer(), nullable=False),
28 + sa.Column("bucket_name", sa.String(length=255), nullable=False),
29 + sa.Column("object_key", sa.String(length=1024), nullable=False),
30 + sa.Column("file_name", sa.String(length=255), nullable=False),
31 + sa.Column("content_type", sa.String(length=100), nullable=True),
32 + sa.Column("file_size", sa.Integer(), nullable=True),
33 + sa.Column("upload_time", sa.DateTime(), nullable=False),
34 + sa.Column("file_hash", sa.String(length=128), nullable=False),
35 + sa.ForeignKeyConstraint(
36 + ["case_id"],
37 + ["incident_management_case.id"],
38 + ),
39 + sa.PrimaryKeyConstraint("id"),
40 + )
41 + # ### end Alembic commands ###
42 +
43 +
44 +def downgrade() -> None:
45 + # ### commands auto generated by Alembic - please adjust! ###
46 + op.drop_table("incident_management_case_datastore")
47 + # ### end Alembic commands ###
backend/alembic/versions/ba64d98dbdc5_add_customer_code_column_to_cases_table.py new
+31
@@ -0,0 +1,31 @@
1 +"""Add customer code column to cases table
2 +
3 +Revision ID: ba64d98dbdc5
4 +Revises: 068f32ae5984
5 +Create Date: 2024-09-18 08:25:27.546888
6 +
7 +"""
8 +from typing import Sequence
9 +from typing import Union
10 +
11 +import sqlalchemy as sa
12 +
13 +from alembic import op
14 +
15 +# revision identifiers, used by Alembic.
16 +revision: str = "ba64d98dbdc5"
17 +down_revision: Union[str, None] = "068f32ae5984"
18 +branch_labels: Union[str, Sequence[str], None] = None
19 +depends_on: Union[str, Sequence[str], None] = None
20 +
21 +
22 +def upgrade() -> None:
23 + # ### commands auto generated by Alembic - please adjust! ###
24 + op.add_column("incident_management_case", sa.Column("customer_code", sa.String(length=50), nullable=True))
25 + # ### end Alembic commands ###
26 +
27 +
28 +def downgrade() -> None:
29 + # ### commands auto generated by Alembic - please adjust! ###
30 + op.drop_column("incident_management_case", "customer_code")
31 + # ### end Alembic commands ###
backend/app/connectors/utils.py
+22
@@ -36,3 +36,25 @@ async def get_connector_info_from_db(
36 else:
37 logger.warning("No connector found.")
38 return None
39 +
40 +
41 +async def is_connector_verified(connector_name: str, db: AsyncSession) -> bool:
42 + """
43 + Checks if a connector is verified.
44 +
45 + Args:
46 + connector_name (str): The name of the connector to check.
47 + db (AsyncSession): The database session.
48 +
49 + Returns:
50 + bool: True if the connector is verified, otherwise False.
51 + """
52 + logger.info(f"Checking if connector {connector_name} is verified")
53 + query = select(Connectors).where(Connectors.connector_name == connector_name)
54 + result = await db.execute(query)
55 + connector = result.scalars().first()
56 + if connector:
57 + return connector.connector_verified
58 + else:
59 + logger.warning("No connector found.")
60 + return False
backend/app/connectors/wazuh_indexer/utils/universal.py
+27 -2
@@ -507,18 +507,43 @@ async def return_graylog_events_index_names():
507 return list(indices.keys())
508
509
510 +# async def get_index_source(index_name: str):
511 +# """
512 +# Get the 10 latest results from the index and search for where the source contains a field name of `syslog_type` or `integration`
513 +# """
514 +# es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
515 +# query = {"size": 10, "query": {"bool": {"must": [{"exists": {"field": "syslog_type"}}]}}}
516 +# response = es_client.search(index=index_name, body=query)
517 +# for hit in response["hits"]["hits"]: # Loop through each hit in the response
518 +# if "syslog_type" in hit["_source"]: # Check if 'syslog_type' exists in the source of the hit
519 +# if hit["_source"]["syslog_type"] == "integration" and "integration" in hit["_source"]:
520 +# return hit["_source"]["integration"] # Return the value of 'integration' if 'syslog_type' equals 'integration'
521 +# return hit["_source"]["syslog_type"] # Return the value of 'syslog_type' for other cases
522 +# raise HTTPException(status_code=404, detail=f"Source not found in index {index_name}")
523 +
524 +
525 async def get_index_source(index_name: str):
526 """
527 Get the 10 latest results from the index and search for where the source contains a field name of `syslog_type` or `integration`
528 """
529 es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
515 - query = {"size": 10, "query": {"bool": {"must": [{"exists": {"field": "syslog_type"}}]}}}
516 - response = es_client.search(index=index_name, body=query)
530 +
531 + # First search for 'syslog_type'
532 + query_syslog_type = {"size": 10, "query": {"bool": {"must": [{"exists": {"field": "syslog_type"}}]}}}
533 + response = es_client.search(index=index_name, body=query_syslog_type)
534 for hit in response["hits"]["hits"]: # Loop through each hit in the response
535 if "syslog_type" in hit["_source"]: # Check if 'syslog_type' exists in the source of the hit
536 if hit["_source"]["syslog_type"] == "integration" and "integration" in hit["_source"]:
537 return hit["_source"]["integration"] # Return the value of 'integration' if 'syslog_type' equals 'integration'
538 return hit["_source"]["syslog_type"] # Return the value of 'syslog_type' for other cases
539 +
540 + # If no 'syslog_type' found, search for 'integration'
541 + query_integration = {"size": 10, "query": {"bool": {"must": [{"exists": {"field": "integration"}}]}}}
542 + response = es_client.search(index=index_name, body=query_integration)
543 + for hit in response["hits"]["hits"]: # Loop through each hit in the response
544 + if "integration" in hit["_source"]: # Check if 'integration' exists in the source of the hit
545 + return hit["_source"]["integration"] # Return the value of 'integration'
546 +
547 raise HTTPException(status_code=404, detail=f"Source not found in index {index_name}")
548
549
backend/app/data_store/data_store_operations.py new
+95
@@ -0,0 +1,95 @@
1 +import os
2 +
3 +import aiofiles
4 +import aiohttp
5 +from fastapi import HTTPException
6 +from fastapi import UploadFile
7 +from loguru import logger
8 +
9 +from app.data_store.data_store_schema import CaseDataStoreCreation
10 +from app.data_store.data_store_session import create_session
11 +
12 +
13 +async def create_bucket_if_not_exists(bucket_name: str) -> None:
14 + client = await create_session()
15 + if not await client.bucket_exists(bucket_name):
16 + await client.make_bucket(bucket_name)
17 + logger.info(f"Created bucket {bucket_name}")
18 + else:
19 + logger.info(f"Bucket {bucket_name} already exists")
20 +
21 +
22 +async def upload_case_data_store(data: CaseDataStoreCreation, file: UploadFile) -> None:
23 + client = await create_session()
24 + logger.info(f"Uploading file {file.filename} to bucket {data.bucket_name}")
25 +
26 + # Define the temporary file path
27 + temp_file_path = os.path.join(os.getcwd(), file.filename)
28 +
29 + # Save the file to the temporary location
30 + async with aiofiles.open(temp_file_path, "wb") as out_file:
31 + content = await file.read()
32 + await out_file.write(content)
33 +
34 + # Upload the file to Minio
35 + await client.fput_object(
36 + bucket_name=data.bucket_name,
37 + object_name=f"{data.case_id}/{file.filename}",
38 + file_path=temp_file_path,
39 + content_type=data.content_type,
40 + )
41 +
42 + # Optionally, remove the temporary file after upload
43 + os.remove(temp_file_path)
44 +
45 +
46 +async def download_case_data_store(bucket_name: str, object_name: str) -> bytes:
47 + client = await create_session()
48 + logger.info(f"Downloading file {object_name} from bucket {bucket_name}")
49 + try:
50 + # Check if the file exists
51 + await client.stat_object(bucket_name, object_name)
52 +
53 + # If no exception is raised, the file exists, proceed to download
54 + async with aiohttp.ClientSession() as session:
55 + response = await client.get_object(bucket_name, object_name, session)
56 + if response is None:
57 + raise Exception("Received None response from get_object")
58 + if not isinstance(response, aiohttp.ClientResponse):
59 + raise Exception("Response is not an instance of aiohttp.ClientResponse")
60 + data = await response.read() # Ensure to read the data
61 + response.close() # Close the response to release resources
62 + logger.info(f"Downloaded file {object_name} from bucket {bucket_name} and returning data")
63 + return data
64 + except Exception as e:
65 + # If an exception is raised, the file does not exist
66 + logger.info(f"Error: {e}")
67 + # List all objects in the bucket
68 + objects = client.list_objects(bucket_name, recursive=True)
69 + objects_list = [obj.object_name async for obj in objects]
70 + logger.info(f"Objects in bucket {bucket_name}: {objects_list}")
71 + raise HTTPException(status_code=404, detail=f"File {object_name} not found in bucket {bucket_name}")
72 +
73 +
74 +async def list_case_data_store_files(bucket_name: str, case_id: int) -> list:
75 + client = await create_session()
76 + objects = await client.list_objects(bucket_name, prefix=f"{case_id}/")
77 + return objects
78 +
79 +
80 +async def create_buckets() -> None:
81 + await create_bucket_if_not_exists("copilot-cases")
82 +
83 +
84 +async def delete_file(bucket_name: str, object_name: str) -> None:
85 + client = await create_session()
86 + try:
87 + # Check if the file exists
88 + await client.stat_object(bucket_name, object_name)
89 + # If no exception is raised, the file exists, proceed to delete
90 + await client.remove_object(bucket_name, object_name)
91 + logger.info(f"Deleted file {object_name} from bucket {bucket_name}")
92 + except Exception as e:
93 + # If an exception is raised, the file does not exist
94 + logger.info(f"Error: {e}")
95 + raise HTTPException(status_code=404, detail=f"File {object_name} not found in bucket {bucket_name}")
backend/app/data_store/data_store_schema.py new
+16
@@ -0,0 +1,16 @@
1 +from datetime import datetime
2 +from typing import Optional
3 +
4 +from pydantic import BaseModel
5 +from pydantic import Field
6 +
7 +
8 +class CaseDataStoreCreation(BaseModel):
9 + case_id: int
10 + bucket_name: str = Field(max_length=255)
11 + object_key: str = Field(max_length=1024)
12 + file_name: str = Field(max_length=255)
13 + content_type: Optional[str] = Field(max_length=100, default=None)
14 + file_size: Optional[int] = None
15 + upload_time: datetime = Field(default_factory=datetime.utcnow)
16 + file_hash: str = Field(max_length=128)
backend/app/data_store/data_store_session.py new
+23
@@ -0,0 +1,23 @@
1 +from pathlib import Path
2 +
3 +from environs import Env
4 +from loguru import logger
5 +from miniopy_async import Minio
6 +
7 +env = Env()
8 +env.read_env(Path(__file__).parent.parent / ".env")
9 +# env.read_env(Path(__file__).parent.parent.parent / "docker-env" / ".env")
10 +logger.info(f"Loading environment from {Path(__file__).parent.parent.parent.parent / '.env'}")
11 +
12 +
13 +minio_root_user = env.str("MINIO_ROOT_USER", default="admin")
14 +minio_root_password = env.str("MINIO_ROOT_PASSWORD", default="password")
15 +minio_url = env.str("MINIO_URL", default="copilot-minio")
16 +minio_secure = env.bool("MINIO_SECURE", default=False)
17 +
18 +logger.info(f"Minio Root User: {minio_root_user} and password: {minio_root_password}")
19 +
20 +
21 +async def create_session() -> Minio:
22 + client = Minio(f"{minio_url}:9000", access_key=minio_root_user, secret_key=minio_root_password, secure=minio_secure)
23 + return client
backend/app/data_store/data_store_setup.py new
+16
@@ -0,0 +1,16 @@
1 +from loguru import logger
2 +
3 +from app.data_store.data_store_session import create_session
4 +
5 +
6 +async def create_bucket_if_not_exists(bucket_name: str) -> None:
7 + client = await create_session()
8 + if not await client.bucket_exists(bucket_name):
9 + await client.make_bucket(bucket_name)
10 + logger.info(f"Created bucket {bucket_name}")
11 + else:
12 + logger.info(f"Bucket {bucket_name} already exists")
13 +
14 +
15 +async def create_buckets() -> None:
16 + await create_bucket_if_not_exists("copilot-cases")
backend/app/db/db_session.py
-121
@@ -1,124 +1,3 @@
1 -# # ! Old Testing without Async
2 -# from sqlmodel import Session
3 -# from sqlmodel import create_engine
4 -
5 -# from settings import SQLALCHEMY_DATABASE_URI
6 -
7 -# engine = create_engine(
8 -# SQLALCHEMY_DATABASE_URI,
9 -# connect_args={"check_same_thread": False},
10 -# )
11 -# session = "placeholder"
12 -
13 -# from contextlib import asynccontextmanager
14 -# from contextlib import contextmanager
15 -
16 -# from loguru import logger
17 -# from sqlalchemy import create_engine
18 -# from sqlalchemy.ext.asyncio import AsyncSession
19 -# from sqlalchemy.ext.asyncio import create_async_engine
20 -# from sqlalchemy.orm import sessionmaker
21 -
22 -# from settings import SQLALCHEMY_DATABASE_URI
23 -
24 -# # create async engine for SQLite using aiosqlite
25 -# async_engine = create_async_engine(SQLALCHEMY_DATABASE_URI, echo=False)
26 -# sync_engine = create_engine(
27 -# SQLALCHEMY_DATABASE_URI.replace("+aiosqlite", ""),
28 -# echo=False,
29 -# )
30 -
31 -# # create a configured "AsyncSession" class
32 -# AsyncSessionLocal = sessionmaker(
33 -# bind=async_engine,
34 -# class_=AsyncSession,
35 -# expire_on_commit=False,
36 -# )
37 -# SyncSessionLocal = sessionmaker(
38 -# bind=sync_engine,
39 -# class_=Session,
40 -# expire_on_commit=False,
41 -# )
42 -
43 -
44 -# @asynccontextmanager
45 -# async def get_db_session():
46 -# """
47 -# Context manager that provides an asynchronous database session.
48 -
49 -# Yields:
50 -# session: An asynchronous database session.
51 -
52 -# Raises:
53 -# Exception: If an error occurs during the database session.
54 -
55 -# """
56 -# async with AsyncSessionLocal() as session:
57 -# logger.info("DB session created")
58 -# try:
59 -# yield session
60 -# except Exception as e:
61 -# logger.error(f"Error during DB session: {e}")
62 -# await session.rollback()
63 -# raise e
64 -# finally:
65 -# logger.info("Closing DB session")
66 -# await session.close()
67 -
68 -
69 -# # Synchronous context manager to get DB session for each request
70 -# @contextmanager
71 -# def get_sync_db_session():
72 -# """
73 -# Context manager that provides a synchronous database session.
74 -
75 -# Yields:
76 -# SyncSessionLocal: The synchronous database session.
77 -
78 -# Raises:
79 -# Exception: If an error occurs during the session.
80 -# """
81 -# session = SyncSessionLocal()
82 -# logger.info("Sync DB session created")
83 -# try:
84 -# yield session
85 -# except Exception as e:
86 -# logger.error(f"Error during sync DB session: {e}")
87 -# session.rollback()
88 -# raise e
89 -# finally:
90 -# logger.info("Closing sync DB session")
91 -# session.close()
92 -
93 -
94 -# @asynccontextmanager
95 -# async def get_session():
96 -# """
97 -# Context manager that provides an async session object.
98 -
99 -# Usage:
100 -# async with get_session() as session:
101 -# # Use the session object here
102 -# """
103 -# async with get_db_session() as session:
104 -# yield session
105 -
106 -
107 -# async def get_db():
108 -# """
109 -# A coroutine function that returns an asynchronous context manager for a database session.
110 -
111 -# Usage:
112 -# async with get_db() as session:
113 -# # Use the session object to interact with the database
114 -
115 -# Returns:
116 -# An asynchronous context manager that yields a database session object.
117 -# """
118 -# async with get_session() as session:
119 -# yield session
120 -
121 -
1 # ! NEW WITH MYSQL ! #
2
3 from contextlib import asynccontextmanager
backend/app/incidents/models.py
+18
@@ -126,8 +126,10 @@ class Case(SQLModel, table=True):
126 case_creation_time: datetime = Field(default_factory=datetime.utcnow)
127 case_status: str = Field(max_length=50, nullable=False)
128 assigned_to: Optional[str] = Field(max_length=50, nullable=True)
129 + customer_code: Optional[str] = Field(max_length=50, nullable=True)
130
131 alerts: List["CaseAlertLink"] = Relationship(back_populates="case")
132 + data_store: List["CaseDataStore"] = Relationship(back_populates="case")
133
134
135 class CaseAlertLink(SQLModel, table=True):
@@ -147,3 +149,19 @@ class Notification(SQLModel, table=True):
149 customer_code: str = Field(max_length=50, nullable=False)
150 shuffle_workflow_id: str = Field(max_length=1000, nullable=False)
151 enabled: bool = Field(default=True)
152 +
153 +
154 +class CaseDataStore(SQLModel, table=True):
155 + __tablename__ = "incident_management_case_datastore"
156 + id: Optional[int] = Field(default=None, primary_key=True)
157 +
158 + case_id: int = Field(foreign_key="incident_management_case.id", nullable=False)
159 + bucket_name: str = Field(max_length=255, nullable=False) # Name of the MinIO bucket
160 + object_key: str = Field(max_length=1024, nullable=False) # Path/key of the file in MinIO
161 + file_name: str = Field(max_length=255, nullable=False) # Original file name uploaded by the user
162 + content_type: Optional[str] = Field(max_length=100, nullable=True) # MIME type of the file
163 + file_size: Optional[int] = Field(nullable=True) # File size in bytes
164 + upload_time: datetime = Field(default_factory=datetime.utcnow) # Time of upload
165 + file_hash: str = Field(max_length=128, nullable=False) # Hash of the file (e.g., SHA-256)
166 +
167 + case: "Case" = Relationship(back_populates="data_store")
backend/app/incidents/routes/db_operations.py
+190 -5
@@ -1,7 +1,13 @@
1 +import io
2 +from typing import Optional
3 +
4 from fastapi import APIRouter
5 from fastapi import Depends
6 +from fastapi import File
7 from fastapi import HTTPException
8 from fastapi import Query
9 +from fastapi import UploadFile
10 +from fastapi.responses import StreamingResponse
11 from loguru import logger
12 from sqlalchemy import select
13 from sqlalchemy.ext.asyncio import AsyncSession
@@ -37,6 +43,7 @@ from app.incidents.schema.db_operations import CaseAlertLinkCreate
43 from app.incidents.schema.db_operations import CaseAlertLinkResponse
44 from app.incidents.schema.db_operations import CaseCreate
45 from app.incidents.schema.db_operations import CaseCreateFromAlert
46 +from app.incidents.schema.db_operations import CaseDataStoreResponse
47 from app.incidents.schema.db_operations import CaseOutResponse
48 from app.incidents.schema.db_operations import CaseResponse
49 from app.incidents.schema.db_operations import CommentCreate
@@ -44,6 +51,7 @@ from app.incidents.schema.db_operations import CommentResponse
51 from app.incidents.schema.db_operations import ConfiguredSourcesResponse
52 from app.incidents.schema.db_operations import FieldAndAssetNames
53 from app.incidents.schema.db_operations import FieldAndAssetNamesResponse
54 +from app.incidents.schema.db_operations import ListCaseDataStoreResponse
55 from app.incidents.schema.db_operations import MappingsResponse
56 from app.incidents.schema.db_operations import NotificationResponse
57 from app.incidents.schema.db_operations import PutNotification
@@ -65,19 +73,31 @@ from app.incidents.services.db_operations import alerts_closed
73 from app.incidents.services.db_operations import alerts_closed_by_alert_title
74 from app.incidents.services.db_operations import alerts_closed_by_asset_name
75 from app.incidents.services.db_operations import alerts_closed_by_assigned_to
76 +from app.incidents.services.db_operations import alerts_closed_by_customer_code
77 +from app.incidents.services.db_operations import alerts_closed_by_source
78 from app.incidents.services.db_operations import alerts_closed_by_tag
79 +from app.incidents.services.db_operations import alerts_closed_multiple_filters
80 from app.incidents.services.db_operations import alerts_in_progress
81 from app.incidents.services.db_operations import alerts_in_progress_by_alert_title
82 from app.incidents.services.db_operations import alerts_in_progress_by_assest_name
83 from app.incidents.services.db_operations import alerts_in_progress_by_assigned_to
84 +from app.incidents.services.db_operations import alerts_in_progress_by_customer_code
85 +from app.incidents.services.db_operations import alerts_in_progress_by_source
86 from app.incidents.services.db_operations import alerts_in_progress_by_tag
87 +from app.incidents.services.db_operations import alerts_in_progress_multiple_filters
88 from app.incidents.services.db_operations import alerts_open
89 from app.incidents.services.db_operations import alerts_open_by_alert_title
90 from app.incidents.services.db_operations import alerts_open_by_assest_name
91 from app.incidents.services.db_operations import alerts_open_by_assigned_to
92 +from app.incidents.services.db_operations import alerts_open_by_customer_code
93 +from app.incidents.services.db_operations import alerts_open_by_source
94 from app.incidents.services.db_operations import alerts_open_by_tag
95 +from app.incidents.services.db_operations import alerts_open_multiple_filters
96 from app.incidents.services.db_operations import alerts_total_by_assigned_to
97 +from app.incidents.services.db_operations import alerts_total_by_customer_code
98 +from app.incidents.services.db_operations import alerts_total_by_source
99 from app.incidents.services.db_operations import alerts_total_by_tag
100 +from app.incidents.services.db_operations import alerts_total_multiple_filters
101 from app.incidents.services.db_operations import create_alert
102 from app.incidents.services.db_operations import create_alert_context
103 from app.incidents.services.db_operations import create_alert_tag
@@ -92,7 +112,10 @@ from app.incidents.services.db_operations import delete_alert_title_name
112 from app.incidents.services.db_operations import delete_asset_name
113 from app.incidents.services.db_operations import delete_case
114 from app.incidents.services.db_operations import delete_field_name
115 +from app.incidents.services.db_operations import delete_file_from_case
116 from app.incidents.services.db_operations import delete_timefield_name
117 +from app.incidents.services.db_operations import download_file_from_case
118 +from app.incidents.services.db_operations import file_exists
119 from app.incidents.services.db_operations import get_alert_by_id
120 from app.incidents.services.db_operations import get_alert_context_by_id
121 from app.incidents.services.db_operations import get_alert_title_names
@@ -106,12 +129,18 @@ from app.incidents.services.db_operations import list_alert_by_assigned_to
129 from app.incidents.services.db_operations import list_alert_by_status
130 from app.incidents.services.db_operations import list_alerts
131 from app.incidents.services.db_operations import list_alerts_by_asset_name
132 +from app.incidents.services.db_operations import list_alerts_by_customer_code
133 +from app.incidents.services.db_operations import list_alerts_by_source
134 from app.incidents.services.db_operations import list_alerts_by_tag
135 from app.incidents.services.db_operations import list_alerts_by_title
136 +from app.incidents.services.db_operations import list_alerts_multiple_filters
137 +from app.incidents.services.db_operations import list_all_files
138 from app.incidents.services.db_operations import list_cases
139 from app.incidents.services.db_operations import list_cases_by_asset_name
140 from app.incidents.services.db_operations import list_cases_by_assigned_to
141 +from app.incidents.services.db_operations import list_cases_by_customer_code
142 from app.incidents.services.db_operations import list_cases_by_status
143 +from app.incidents.services.db_operations import list_files_by_case_id
144 from app.incidents.services.db_operations import put_customer_notification
145 from app.incidents.services.db_operations import replace_alert_title_name
146 from app.incidents.services.db_operations import replace_asset_name
@@ -120,7 +149,9 @@ from app.incidents.services.db_operations import replace_timefield_name
149 from app.incidents.services.db_operations import update_alert_assigned_to
150 from app.incidents.services.db_operations import update_alert_status
151 from app.incidents.services.db_operations import update_case_assigned_to
152 +from app.incidents.services.db_operations import update_case_customer_code
153 from app.incidents.services.db_operations import update_case_status
154 +from app.incidents.services.db_operations import upload_file_to_case
155 from app.incidents.services.db_operations import validate_source_exists
156
157 incidents_db_operations_router = APIRouter()
@@ -529,16 +560,104 @@ async def list_alerts_by_title_endpoint(
560 )
561
562
563 +@incidents_db_operations_router.get("/alerts/customer/{customer_code}", response_model=AlertOutResponse)
564 +async def list_alerts_by_customer_code_endpoint(
565 + customer_code: str,
566 + page: int = Query(1, ge=1),
567 + page_size: int = Query(25, ge=1),
568 + order: str = Query("desc", regex="^(asc|desc)$"),
569 + db: AsyncSession = Depends(get_db),
570 +):
571 + return AlertOutResponse(
572 + alerts=await list_alerts_by_customer_code(customer_code, db, page=page, page_size=page_size, order=order),
573 + total=await alerts_total_by_customer_code(db, customer_code),
574 + open=await alerts_open_by_customer_code(db, customer_code),
575 + in_progress=await alerts_in_progress_by_customer_code(db, customer_code),
576 + closed=await alerts_closed_by_customer_code(db, customer_code),
577 + success=True,
578 + message="Alerts retrieved successfully",
579 + )
580 +
581 +
582 +@incidents_db_operations_router.get("/alerts/source/{source}", response_model=AlertOutResponse)
583 +async def list_alerts_by_source_endpoint(
584 + source: str,
585 + page: int = Query(1, ge=1),
586 + page_size: int = Query(25, ge=1),
587 + order: str = Query("desc", regex="^(asc|desc)$"),
588 + db: AsyncSession = Depends(get_db),
589 +):
590 + return AlertOutResponse(
591 + alerts=await list_alerts_by_source(source, db, page=page, page_size=page_size, order=order),
592 + total=await alerts_total_by_source(db, source),
593 + open=await alerts_open_by_source(db, source),
594 + in_progress=await alerts_in_progress_by_source(db, source),
595 + closed=await alerts_closed_by_source(db, source),
596 + success=True,
597 + message="Alerts retrieved successfully",
598 + )
599 +
600 +
601 +@incidents_db_operations_router.get("/alerts/filter", response_model=AlertOutResponse)
602 +async def list_alerts_multiple_filters_endpoint(
603 + assigned_to: Optional[str] = Query(None),
604 + alert_title: Optional[str] = Query(None),
605 + customer_code: Optional[str] = Query(None),
606 + source: Optional[str] = Query(None),
607 + page: int = Query(1, ge=1),
608 + page_size: int = Query(25, ge=1),
609 + order: str = Query("desc", regex="^(asc|desc)$"),
610 + db: AsyncSession = Depends(get_db),
611 +):
612 + return AlertOutResponse(
613 + alerts=await list_alerts_multiple_filters(
614 + assigned_to=assigned_to,
615 + alert_title=alert_title,
616 + customer_code=customer_code,
617 + source=source,
618 + db=db,
619 + page=page,
620 + page_size=page_size,
621 + order=order,
622 + ),
623 + total=await alerts_total_multiple_filters(
624 + assigned_to=assigned_to,
625 + alert_title=alert_title,
626 + customer_code=customer_code,
627 + source=source,
628 + db=db,
629 + ),
630 + open=await alerts_open_multiple_filters(
631 + assigned_to=assigned_to,
632 + alert_title=alert_title,
633 + customer_code=customer_code,
634 + source=source,
635 + db=db,
636 + ),
637 + in_progress=await alerts_in_progress_multiple_filters(
638 + assigned_to=assigned_to,
639 + alert_title=alert_title,
640 + customer_code=customer_code,
641 + source=source,
642 + db=db,
643 + ),
644 + closed=await alerts_closed_multiple_filters(
645 + assigned_to=assigned_to,
646 + alert_title=alert_title,
647 + customer_code=customer_code,
648 + source=source,
649 + db=db,
650 + ),
651 + success=True,
652 + message="Alerts retrieved successfully",
653 + )
654 +
655 +
656 @incidents_db_operations_router.get("/cases", response_model=CaseOutResponse)
657 async def list_cases_endpoint(db: AsyncSession = Depends(get_db)):
658 return CaseOutResponse(cases=await list_cases(db), success=True, message="Cases retrieved successfully")
659
660
537 -@incidents_db_operations_router.get("/case/{case_id}", response_model=CaseOutResponse)
538 -async def get_case_by_id_endpoint(case_id: int, db: AsyncSession = Depends(get_db)):
539 - return CaseOutResponse(cases=[await get_case_by_id(case_id, db)], success=True, message="Case retrieved successfully")
540 -
541 -
661 @incidents_db_operations_router.put("/case/status", response_model=CaseOutResponse)
662 async def update_case_status_endpoint(case_status: UpdateCaseStatus, db: AsyncSession = Depends(get_db)):
663 return CaseOutResponse(cases=[await update_case_status(case_status, db)], success=True, message="Case status updated successfully")
@@ -557,6 +676,15 @@ async def update_case_assigned_to_endpoint(assigned_to: AssignedToCase, db: Asyn
676 )
677
678
679 +@incidents_db_operations_router.put("/case/customer-code", response_model=CaseOutResponse)
680 +async def update_case_customer_code_endpoint(case_id: int, customer_code: str, db: AsyncSession = Depends(get_db)):
681 + return CaseOutResponse(
682 + cases=[await update_case_customer_code(case_id, customer_code, db)],
683 + success=True,
684 + message="Case customer code updated successfully",
685 + )
686 +
687 +
688 @incidents_db_operations_router.delete("/case/{case_id}")
689 async def delete_case_endpoint(case_id: int, db: AsyncSession = Depends(get_db)):
690 await delete_case(case_id, db)
@@ -578,3 +706,60 @@ async def list_cases_by_assigned_to_endpoint(assigned_to: str, db: AsyncSession
706 @incidents_db_operations_router.get("/case/asset/{asset_name}", response_model=CaseOutResponse)
707 async def list_cases_by_asset_name_endpoint(asset_name: str, db: AsyncSession = Depends(get_db)):
708 return CaseOutResponse(cases=await list_cases_by_asset_name(asset_name, db), success=True, message="Cases retrieved successfully")
709 +
710 +
711 +@incidents_db_operations_router.get("/case/customer/{customer_code}", response_model=CaseOutResponse)
712 +async def list_cases_by_customer_code_endpoint(customer_code: str, db: AsyncSession = Depends(get_db)):
713 + return CaseOutResponse(cases=await list_cases_by_customer_code(customer_code, db), success=True, message="Cases retrieved successfully")
714 +
715 +
716 +@incidents_db_operations_router.get("/case/data-store", response_model=ListCaseDataStoreResponse)
717 +async def list_all_case_data_store_files_endpoint(db: AsyncSession = Depends(get_db)):
718 + logger.info("Listing all files in the data store")
719 + return ListCaseDataStoreResponse(case_data_store=await list_all_files(db), success=True, message="Files retrieved successfully")
720 +
721 +
722 +@incidents_db_operations_router.get("/case/data-store/{case_id}", response_model=ListCaseDataStoreResponse)
723 +async def list_case_data_store_files_endpoint(case_id: int, db: AsyncSession = Depends(get_db)):
724 + logger.info(f"Listing all files in the data store for case {case_id}")
725 + return ListCaseDataStoreResponse(
726 + case_data_store=await list_files_by_case_id(case_id, db),
727 + success=True,
728 + message="Files retrieved successfully",
729 + )
730 +
731 +
732 +@incidents_db_operations_router.get("/case/data-store/download/{case_id}/{file_name}")
733 +async def download_case_data_store_file_endpoint(case_id: int, file_name: str, db: AsyncSession = Depends(get_db)) -> StreamingResponse:
734 + file_bytes, file_content_type = await download_file_from_case(case_id, file_name, db)
735 + logger.info(f"Streaming file {file_name} from case {case_id}")
736 + output = io.BytesIO(file_bytes)
737 + output.seek(0)
738 +
739 + return StreamingResponse(output, media_type=file_content_type, headers={"Content-Disposition": f"attachment; filename={file_name}"})
740 +
741 +
742 +@incidents_db_operations_router.post("/case/data-store/upload", response_model=CaseDataStoreResponse)
743 +async def upload_case_data_store_endpoint(
744 + case_id: int,
745 + file: UploadFile = File(...),
746 + db: AsyncSession = Depends(get_db),
747 +):
748 + if await file_exists(case_id, file.filename, db):
749 + raise HTTPException(status_code=400, detail="File name already exists for this case")
750 + return CaseDataStoreResponse(
751 + case_data_store=await upload_file_to_case(case_id, file, db),
752 + success=True,
753 + message="File uploaded successfully",
754 + )
755 +
756 +
757 +@incidents_db_operations_router.delete("/case/data-store/{case_id}/{file_name}")
758 +async def delete_case_data_store_file_endpoint(case_id: int, file_name: str, db: AsyncSession = Depends(get_db)):
759 + await delete_file_from_case(case_id, file_name, db)
760 + return {"message": "File deleted successfully", "success": True}
761 +
762 +
763 +@incidents_db_operations_router.get("/case/{case_id}", response_model=CaseOutResponse)
764 +async def get_case_by_id_endpoint(case_id: int, db: AsyncSession = Depends(get_db)):
765 + return CaseOutResponse(cases=[await get_case_by_id(case_id, db)], success=True, message="Case retrieved successfully")
backend/app/incidents/routes/incident_report.py new
+146
@@ -0,0 +1,146 @@
1 +import csv
2 +from datetime import datetime
3 +from io import StringIO
4 +from typing import Any
5 +from typing import Dict
6 +from typing import List
7 +
8 +from fastapi import APIRouter
9 +from fastapi import Depends
10 +from fastapi.responses import StreamingResponse
11 +from sqlalchemy.ext.asyncio import AsyncSession
12 +from sqlalchemy.orm import selectinload
13 +from sqlmodel import select
14 +
15 +from app.customers.routes.customers import get_customer
16 +from app.db.db_session import get_db
17 +from app.incidents.models import Alert
18 +from app.incidents.models import AlertToTag
19 +from app.incidents.models import Case
20 +from app.incidents.models import CaseAlertLink
21 +
22 +incidents_report_router = APIRouter()
23 +
24 +# Constants
25 +FIELDNAMES = [
26 + "Case ID",
27 + "Case Name",
28 + "Case Description",
29 + "Case Creation Time",
30 + "Case Status",
31 + "Case Assigned To",
32 + "Case Customer Code",
33 + "Alert ID",
34 + "Alert Name",
35 + "Alert Description",
36 + "Alert Status",
37 + "Alert Creation Time",
38 + "Alert Time Closed",
39 + "Alert Source",
40 + "Alert Assigned To",
41 + "Assets",
42 + "Tags",
43 + "Comments",
44 + "Customer Code",
45 +]
46 +
47 +
48 +# Helper Functions
49 +async def fetch_cases_with_related_data(session: AsyncSession) -> List[Case]:
50 + """Fetch cases with related alerts, assets, tags, and comments."""
51 + result = await session.execute(
52 + select(Case).options(
53 + selectinload(Case.alerts)
54 + .selectinload(CaseAlertLink.alert)
55 + .options(selectinload(Alert.assets), selectinload(Alert.tags).selectinload(AlertToTag.tag), selectinload(Alert.comments)),
56 + ),
57 + )
58 + return result.scalars().all()
59 +
60 +
61 +async def fetch_cases_by_customer(session: AsyncSession, customer_code: str) -> List[Case]:
62 + """Fetch cases for a specific customer with related data."""
63 + result = await session.execute(
64 + select(Case)
65 + .where(Case.customer_code == customer_code)
66 + .options(
67 + selectinload(Case.alerts)
68 + .selectinload(CaseAlertLink.alert)
69 + .options(selectinload(Alert.assets), selectinload(Alert.tags).selectinload(AlertToTag.tag), selectinload(Alert.comments)),
70 + ),
71 + )
72 + return result.scalars().all()
73 +
74 +
75 +def serialize_case_alert_to_row(case: Case, alert: Alert) -> Dict[str, Any]:
76 + """Serialize a case and its alert into a CSV row."""
77 + assets = ";".join(asset.asset_name for asset in alert.assets or [])
78 + tags = ";".join(alert_tag.tag.tag for alert_tag in alert.tags or [])
79 + comments = ";".join(comment.comment for comment in alert.comments or [])
80 +
81 + return {
82 + "Case ID": case.id,
83 + "Case Name": case.case_name,
84 + "Case Description": case.case_description,
85 + "Case Creation Time": case.case_creation_time.strftime("%Y-%m-%d %H:%M:%S"),
86 + "Case Status": case.case_status,
87 + "Case Assigned To": case.assigned_to or "",
88 + "Case Customer Code": case.customer_code or "",
89 + "Alert ID": alert.id,
90 + "Alert Name": alert.alert_name,
91 + "Alert Description": alert.alert_description,
92 + "Alert Status": alert.status,
93 + "Alert Creation Time": alert.alert_creation_time.strftime("%Y-%m-%d %H:%M:%S"),
94 + "Alert Time Closed": alert.time_closed.strftime("%Y-%m-%d %H:%M:%S") if alert.time_closed else "",
95 + "Alert Source": alert.source,
96 + "Alert Assigned To": alert.assigned_to or "",
97 + "Assets": assets,
98 + "Tags": tags,
99 + "Comments": comments,
100 + "Customer Code": alert.customer_code,
101 + }
102 +
103 +
104 +def generate_csv_content(rows: List[Dict[str, Any]]) -> StringIO:
105 + """Generate CSV content from rows."""
106 + csv_stream = StringIO()
107 + writer = csv.DictWriter(csv_stream, fieldnames=FIELDNAMES, lineterminator="\n")
108 + writer.writeheader()
109 + writer.writerows(rows)
110 + csv_stream.seek(0)
111 + return csv_stream
112 +
113 +
114 +# Route Handler
115 +@incidents_report_router.post(
116 + "/generate-report",
117 + description="Generate a report for all cases.",
118 +)
119 +async def get_cases_export_all_route(
120 + session: AsyncSession = Depends(get_db),
121 +) -> StreamingResponse:
122 + cases = await fetch_cases_with_related_data(session)
123 + rows = [serialize_case_alert_to_row(case, alert_link.alert) for case in cases for alert_link in case.alerts]
124 + csv_stream = generate_csv_content(rows)
125 + filename = f"cases_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
126 + response = StreamingResponse(csv_stream, media_type="text/csv")
127 + response.headers["Content-Disposition"] = f"attachment; filename={filename}"
128 + return response
129 +
130 +
131 +@incidents_report_router.post(
132 + "/generate-report/{customer_code}",
133 + description="Generate a report for a customer",
134 +)
135 +async def get_cases_export_customer_route(
136 + customer_code: str,
137 + session: AsyncSession = Depends(get_db),
138 +) -> StreamingResponse:
139 + await get_customer(customer_code=customer_code, session=session)
140 + cases = await fetch_cases_by_customer(session, customer_code)
141 + rows = [serialize_case_alert_to_row(case, alert_link.alert) for case in cases for alert_link in case.alerts]
142 + csv_stream = generate_csv_content(rows)
143 + filename = f"cases_export_{customer_code}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
144 + response = StreamingResponse(csv_stream, media_type="text/csv")
145 + response.headers["Content-Disposition"] = f"attachment; filename={filename}"
146 + return response
backend/app/incidents/schema/db_operations.py
+15
@@ -12,6 +12,7 @@ from app.incidents.models import AlertTag
12 from app.incidents.models import Asset
13 from app.incidents.models import Case
14 from app.incidents.models import CaseAlertLink
15 +from app.incidents.models import CaseDataStore
16 from app.incidents.models import Comment
17
18
@@ -208,6 +209,7 @@ class CaseCreate(BaseModel):
209 case_creation_time: datetime
210 case_status: str
211 assigned_to: Optional[str] = None
212 + customer_code: Optional[str] = None
213
214
215 class LinkedCaseCreate(BaseModel):
@@ -308,6 +310,7 @@ class CaseOut(BaseModel):
310 alerts: Optional[List[AlertOut]] = []
311 case_status: Optional[str] = None
312 case_creation_time: Optional[datetime] = None
313 + customer_code: Optional[str] = None
314
315
316 class CaseOutResponse(BaseModel):
@@ -333,3 +336,15 @@ class PutNotification(BaseModel):
336 customer_code: str
337 shuffle_workflow_id: str
338 enabled: bool
339 +
340 +
341 +class CaseDataStoreResponse(BaseModel):
342 + case_data_store: CaseDataStore
343 + success: bool
344 + message: str
345 +
346 +
347 +class ListCaseDataStoreResponse(BaseModel):
348 + case_data_store: List[CaseDataStore]
349 + success: bool
350 + message: str
backend/app/incidents/services/db_operations.py
+479
@@ -1,15 +1,24 @@
1 +import hashlib
2 from typing import List
3 +from typing import Optional
4 +from typing import Tuple
5
6 from fastapi import HTTPException
7 +from fastapi import UploadFile
8 from loguru import logger
9 from sqlalchemy import asc
10 from sqlalchemy import delete
11 from sqlalchemy import desc
12 +from sqlalchemy import func
13 from sqlalchemy.exc import IntegrityError
14 from sqlalchemy.ext.asyncio import AsyncSession
15 from sqlalchemy.future import select
16 from sqlalchemy.orm import selectinload
17
18 +from app.data_store.data_store_operations import delete_file
19 +from app.data_store.data_store_operations import download_case_data_store
20 +from app.data_store.data_store_operations import upload_case_data_store
21 +from app.data_store.data_store_schema import CaseDataStoreCreation
22 from app.incidents.models import Alert
23 from app.incidents.models import AlertContext
24 from app.incidents.models import AlertTag
@@ -19,6 +28,7 @@ from app.incidents.models import Asset
28 from app.incidents.models import AssetFieldName
29 from app.incidents.models import Case
30 from app.incidents.models import CaseAlertLink
31 +from app.incidents.models import CaseDataStore
32 from app.incidents.models import Comment
33 from app.incidents.models import CustomerCodeFieldName
34 from app.incidents.models import FieldName
@@ -40,6 +50,16 @@ from app.incidents.schema.db_operations import LinkedCaseCreate
50 from app.incidents.schema.db_operations import PutNotification
51 from app.incidents.schema.db_operations import UpdateAlertStatus
52 from app.incidents.schema.db_operations import UpdateCaseStatus
53 +from app.integrations.alert_creation_settings.models.alert_creation_settings import (
54 + AlertCreationSettings,
55 +)
56 +
57 +
58 +async def customer_code_valid(customer_code: str, db: AsyncSession) -> bool:
59 + result = await db.execute(select(AlertCreationSettings).where(AlertCreationSettings.customer_code == customer_code))
60 + if result.scalars().first():
61 + return True
62 + raise HTTPException(status_code=404, detail="Customer code not found")
63
64
65 async def alert_total(db: AsyncSession) -> int:
@@ -128,6 +148,145 @@ async def alerts_open_by_assigned_to(db: AsyncSession, assigned_to: str) -> int:
148 return len(result.scalars().all())
149
150
151 +async def alerts_total_by_customer_code(db: AsyncSession, customer_code: str) -> int:
152 + result = await db.execute(select(Alert).where(Alert.customer_code == customer_code))
153 + return len(result.scalars().all())
154 +
155 +
156 +async def alerts_closed_by_customer_code(db: AsyncSession, customer_code: str) -> int:
157 + result = await db.execute(select(Alert).where((Alert.status == "CLOSED") & (Alert.customer_code == customer_code)))
158 + return len(result.scalars().all())
159 +
160 +
161 +async def alerts_in_progress_by_customer_code(db: AsyncSession, customer_code: str) -> int:
162 + result = await db.execute(select(Alert).where((Alert.status == "IN_PROGRESS") & (Alert.customer_code == customer_code)))
163 + return len(result.scalars().all())
164 +
165 +
166 +async def alerts_open_by_customer_code(db: AsyncSession, customer_code: str) -> int:
167 + result = await db.execute(select(Alert).where((Alert.status == "OPEN") & (Alert.customer_code == customer_code)))
168 + return len(result.scalars().all())
169 +
170 +
171 +async def alerts_total_by_source(db: AsyncSession, source: str) -> int:
172 + result = await db.execute(select(Alert).where(Alert.source == source))
173 + return len(result.scalars().all())
174 +
175 +
176 +async def alerts_closed_by_source(db: AsyncSession, source: str) -> int:
177 + result = await db.execute(select(Alert).where((Alert.status == "CLOSED") & (Alert.source == source)))
178 + return len(result.scalars().all())
179 +
180 +
181 +async def alerts_in_progress_by_source(db: AsyncSession, source: str) -> int:
182 + result = await db.execute(select(Alert).where((Alert.status == "IN_PROGRESS") & (Alert.source == source)))
183 + return len(result.scalars().all())
184 +
185 +
186 +async def alerts_open_by_source(db: AsyncSession, source: str) -> int:
187 + result = await db.execute(select(Alert).where((Alert.status == "OPEN") & (Alert.source == source)))
188 + return len(result.scalars().all())
189 +
190 +
191 +async def alerts_total_multiple_filters(
192 + db: AsyncSession,
193 + assigned_to: Optional[str] = None,
194 + alert_title: Optional[str] = None,
195 + customer_code: Optional[str] = None,
196 + source: Optional[str] = None,
197 +) -> int:
198 + # Build dynamic filters
199 + filters = []
200 + if assigned_to:
201 + filters.append(Alert.assigned_to == assigned_to)
202 + if alert_title:
203 + filters.append(Alert.alert_name.like(f"%{alert_title}%"))
204 + if customer_code:
205 + filters.append(Alert.customer_code == customer_code)
206 + if source:
207 + filters.append(Alert.source == source)
208 +
209 + # Build the query
210 + query = select(func.count()).select_from(Alert).where(*filters)
211 +
212 + result = await db.execute(query)
213 + total = result.scalar_one()
214 + return total
215 +
216 +
217 +async def alerts_closed_multiple_filters(
218 + db: AsyncSession,
219 + assigned_to: Optional[str] = None,
220 + alert_title: Optional[str] = None,
221 + customer_code: Optional[str] = None,
222 + source: Optional[str] = None,
223 +) -> int:
224 + # Include the status filter
225 + filters = [Alert.status == "CLOSED"]
226 + if assigned_to:
227 + filters.append(Alert.assigned_to == assigned_to)
228 + if alert_title:
229 + filters.append(Alert.alert_name.like(f"%{alert_title}%"))
230 + if customer_code:
231 + filters.append(Alert.customer_code == customer_code)
232 + if source:
233 + filters.append(Alert.source == source)
234 +
235 + query = select(func.count()).select_from(Alert).where(*filters)
236 +
237 + result = await db.execute(query)
238 + closed_count = result.scalar_one()
239 + return closed_count
240 +
241 +
242 +async def alerts_in_progress_multiple_filters(
243 + db: AsyncSession,
244 + assigned_to: Optional[str] = None,
245 + alert_title: Optional[str] = None,
246 + customer_code: Optional[str] = None,
247 + source: Optional[str] = None,
248 +) -> int:
249 + filters = [Alert.status == "IN_PROGRESS"]
250 + if assigned_to:
251 + filters.append(Alert.assigned_to == assigned_to)
252 + if alert_title:
253 + filters.append(Alert.alert_name.like(f"%{alert_title}%"))
254 + if customer_code:
255 + filters.append(Alert.customer_code == customer_code)
256 + if source:
257 + filters.append(Alert.source == source)
258 +
259 + query = select(func.count()).select_from(Alert).where(*filters)
260 +
261 + result = await db.execute(query)
262 + in_progress_count = result.scalar_one()
263 + return in_progress_count
264 +
265 +
266 +async def alerts_open_multiple_filters(
267 + db: AsyncSession,
268 + assigned_to: Optional[str] = None,
269 + alert_title: Optional[str] = None,
270 + customer_code: Optional[str] = None,
271 + source: Optional[str] = None,
272 +) -> int:
273 + filters = [Alert.status == "OPEN"]
274 + if assigned_to:
275 + filters.append(Alert.assigned_to == assigned_to)
276 + if alert_title:
277 + filters.append(Alert.alert_name.like(f"%{alert_title}%"))
278 + if customer_code:
279 + filters.append(Alert.customer_code == customer_code)
280 + if source:
281 + filters.append(Alert.source == source)
282 +
283 + query = select(func.count()).select_from(Alert).where(*filters)
284 +
285 + result = await db.execute(query)
286 + open_count = result.scalar_one()
287 + return open_count
288 +
289 +
290 async def alerts_total_by_tag(db: AsyncSession, tag: str) -> int:
291 result = await db.execute(
292 select(Alert)
@@ -440,6 +599,17 @@ async def update_case_assigned_to(case_id: int, assigned_to: str, db: AsyncSessi
599 return case
600
601
602 +async def update_case_customer_code(case_id: int, customer_code: str, db: AsyncSession) -> Case:
603 + await customer_code_valid(customer_code, db)
604 + result = await db.execute(select(Case).where(Case.id == case_id))
605 + case = result.scalars().first()
606 + if not case:
607 + raise HTTPException(status_code=404, detail="Case not found")
608 + case.customer_code = customer_code
609 + await db.commit()
610 + return case
611 +
612 +
613 async def update_alert_assigned_to(alert_id: int, assigned_to: str, db: AsyncSession) -> Alert:
614 result = await db.execute(select(Alert).where(Alert.id == alert_id))
615 alert = result.scalars().first()
@@ -650,6 +820,7 @@ async def create_case_from_alert(alert_id: int, db: AsyncSession) -> Case:
820 case_description=alert.alert_description,
821 case_status=alert.status,
822 assigned_to=alert.assigned_to,
823 + customer_code=alert.customer_code,
824 )
825 db.add(case)
826 try:
@@ -728,6 +899,7 @@ async def get_case_by_id(case_id: int, db: AsyncSession) -> CaseOut:
899 assigned_to=case.assigned_to,
900 alerts=alerts_out,
901 case_creation_time=case.case_creation_time,
902 + customer_code=case.customer_code,
903 )
904 return case_out
905
@@ -775,6 +947,7 @@ async def list_cases(db: AsyncSession) -> List[CaseOut]:
947 alerts=alerts_out,
948 case_creation_time=case.case_creation_time,
949 case_status=case.case_status,
950 + customer_code=case.customer_code,
951 )
952 cases_out.append(case_out)
953 return cases_out
@@ -820,6 +993,7 @@ async def list_cases_by_status(status: str, db: AsyncSession) -> List[CaseOut]:
993 case_description=case.case_description,
994 assigned_to=case.assigned_to,
995 alerts=alerts_out,
996 + customer_code=case.customer_code,
997 )
998 cases_out.append(case_out)
999 return cases_out
@@ -865,6 +1039,7 @@ async def list_cases_by_assigned_to(assigned_to: str, db: AsyncSession) -> List[
1039 case_description=case.case_description,
1040 assigned_to=case.assigned_to,
1041 alerts=alerts_out,
1042 + customer_code=case.customer_code,
1043 )
1044 cases_out.append(case_out)
1045 return cases_out
@@ -913,6 +1088,53 @@ async def list_cases_by_asset_name(asset_name: str, db: AsyncSession) -> List[Ca
1088 case_description=case.case_description,
1089 assigned_to=case.assigned_to,
1090 alerts=alerts_out,
1091 + customer_code=case.customer_code,
1092 + )
1093 + cases_out.append(case_out)
1094 + return cases_out
1095 +
1096 +
1097 +async def list_cases_by_customer_code(customer_code: str, db: AsyncSession) -> List[CaseOut]:
1098 + result = await db.execute(
1099 + select(Case)
1100 + .where(Case.customer_code == customer_code)
1101 + .options(
1102 + selectinload(Case.alerts).selectinload(CaseAlertLink.alert).selectinload(Alert.comments),
1103 + selectinload(Case.alerts).selectinload(CaseAlertLink.alert).selectinload(Alert.assets),
1104 + selectinload(Case.alerts).selectinload(CaseAlertLink.alert).selectinload(Alert.tags).selectinload(AlertToTag.tag),
1105 + ),
1106 + )
1107 + cases = result.scalars().all()
1108 + cases_out = []
1109 + for case in cases:
1110 + alerts_out = []
1111 + for case_alert_link in case.alerts:
1112 + alert = case_alert_link.alert
1113 + comments = [CommentBase(**comment.__dict__) for comment in alert.comments]
1114 + assets = [AssetBase(**asset.__dict__) for asset in alert.assets]
1115 + tags = [AlertTagBase(**alert_to_tag.tag.__dict__) for alert_to_tag in alert.tags]
1116 + alert_out = AlertOut(
1117 + id=alert.id,
1118 + alert_creation_time=alert.alert_creation_time,
1119 + time_closed=alert.time_closed,
1120 + alert_name=alert.alert_name,
1121 + alert_description=alert.alert_description,
1122 + status=alert.status,
1123 + customer_code=alert.customer_code,
1124 + source=alert.source,
1125 + assigned_to=alert.assigned_to,
1126 + comments=comments,
1127 + assets=assets,
1128 + tags=tags,
1129 + )
1130 + alerts_out.append(alert_out)
1131 + case_out = CaseOut(
1132 + id=case.id,
1133 + case_name=case.case_name,
1134 + case_description=case.case_description,
1135 + assigned_to=case.assigned_to,
1136 + alerts=alerts_out,
1137 + customer_code=case.customer_code,
1138 )
1139 cases_out.append(case_out)
1140 return cases_out
@@ -1153,6 +1375,166 @@ async def list_alerts_by_title(
1375 return alerts_out
1376
1377
1378 +async def list_alerts_by_customer_code(
1379 + customer_code: str,
1380 + db: AsyncSession,
1381 + page: int = 1,
1382 + page_size: int = 25,
1383 + order: str = "desc",
1384 +) -> List[AlertOut]:
1385 + offset = (page - 1) * page_size
1386 + order_by = asc(Alert.id) if order == "asc" else desc(Alert.id)
1387 +
1388 + result = await db.execute(
1389 + select(Alert)
1390 + .where(Alert.customer_code == customer_code)
1391 + .options(
1392 + selectinload(Alert.comments),
1393 + selectinload(Alert.assets),
1394 + selectinload(Alert.cases),
1395 + selectinload(Alert.tags).selectinload(AlertToTag.tag),
1396 + )
1397 + .order_by(order_by)
1398 + .offset(offset)
1399 + .limit(page_size),
1400 + )
1401 + alerts = result.scalars().all()
1402 + alerts_out = []
1403 + for alert in alerts:
1404 + comments = [CommentBase(**comment.__dict__) for comment in alert.comments]
1405 + assets = [AssetBase(**asset.__dict__) for asset in alert.assets]
1406 + tags = [AlertTagBase(**alert_to_tag.tag.__dict__) for alert_to_tag in alert.tags]
1407 + alert_out = AlertOut(
1408 + id=alert.id,
1409 + alert_creation_time=alert.alert_creation_time,
1410 + time_closed=alert.time_closed,
1411 + alert_name=alert.alert_name,
1412 + alert_description=alert.alert_description,
1413 + status=alert.status,
1414 + customer_code=alert.customer_code,
1415 + source=alert.source,
1416 + assigned_to=alert.assigned_to,
1417 + comments=comments,
1418 + assets=assets,
1419 + tags=tags,
1420 + )
1421 + alerts_out.append(alert_out)
1422 + return alerts_out
1423 +
1424 +
1425 +async def list_alerts_by_source(
1426 + source: str,
1427 + db: AsyncSession,
1428 + page: int = 1,
1429 + page_size: int = 25,
1430 + order: str = "desc",
1431 +) -> List[AlertOut]:
1432 + offset = (page - 1) * page_size
1433 + order_by = asc(Alert.id) if order == "asc" else desc(Alert.id)
1434 +
1435 + result = await db.execute(
1436 + select(Alert)
1437 + .where(Alert.source == source)
1438 + .options(
1439 + selectinload(Alert.comments),
1440 + selectinload(Alert.assets),
1441 + selectinload(Alert.cases),
1442 + selectinload(Alert.tags).selectinload(AlertToTag.tag),
1443 + )
1444 + .order_by(order_by)
1445 + .offset(offset)
1446 + .limit(page_size),
1447 + )
1448 + alerts = result.scalars().all()
1449 + alerts_out = []
1450 + for alert in alerts:
1451 + comments = [CommentBase(**comment.__dict__) for comment in alert.comments]
1452 + assets = [AssetBase(**asset.__dict__) for asset in alert.assets]
1453 + tags = [AlertTagBase(**alert_to_tag.tag.__dict__) for alert_to_tag in alert.tags]
1454 + alert_out = AlertOut(
1455 + id=alert.id,
1456 + alert_creation_time=alert.alert_creation_time,
1457 + time_closed=alert.time_closed,
1458 + alert_name=alert.alert_name,
1459 + alert_description=alert.alert_description,
1460 + status=alert.status,
1461 + customer_code=alert.customer_code,
1462 + source=alert.source,
1463 + assigned_to=alert.assigned_to,
1464 + comments=comments,
1465 + assets=assets,
1466 + tags=tags,
1467 + )
1468 + alerts_out.append(alert_out)
1469 + return alerts_out
1470 +
1471 +
1472 +async def list_alerts_multiple_filters(
1473 + db: AsyncSession,
1474 + assigned_to: Optional[str] = None,
1475 + alert_title: Optional[str] = None,
1476 + customer_code: Optional[str] = None,
1477 + source: Optional[str] = None,
1478 + page: int = 1,
1479 + page_size: int = 25,
1480 + order: str = "desc",
1481 +) -> List[AlertOut]:
1482 + offset = (page - 1) * page_size
1483 + order_by = asc(Alert.id) if order == "asc" else desc(Alert.id)
1484 +
1485 + # Build dynamic filters
1486 + filters = []
1487 + if assigned_to:
1488 + filters.append(Alert.assigned_to == assigned_to)
1489 + if alert_title:
1490 + filters.append(Alert.alert_name.like(f"%{alert_title}%"))
1491 + if customer_code:
1492 + filters.append(Alert.customer_code == customer_code)
1493 + if source:
1494 + filters.append(Alert.source == source)
1495 +
1496 + # Build the query with dynamic filters
1497 + query = (
1498 + select(Alert)
1499 + .where(*filters)
1500 + .options(
1501 + selectinload(Alert.comments),
1502 + selectinload(Alert.assets),
1503 + selectinload(Alert.cases),
1504 + selectinload(Alert.tags).selectinload(AlertToTag.tag),
1505 + )
1506 + .order_by(order_by)
1507 + .offset(offset)
1508 + .limit(page_size)
1509 + )
1510 +
1511 + result = await db.execute(query)
1512 + alerts = result.scalars().all()
1513 +
1514 + alerts_out = []
1515 + for alert in alerts:
1516 + comments = [CommentBase(**comment.__dict__) for comment in alert.comments]
1517 + assets = [AssetBase(**asset.__dict__) for asset in alert.assets]
1518 + tags = [AlertTagBase(**alert_to_tag.tag.__dict__) for alert_to_tag in alert.tags]
1519 + alert_out = AlertOut(
1520 + id=alert.id,
1521 + alert_creation_time=alert.alert_creation_time,
1522 + time_closed=alert.time_closed,
1523 + alert_name=alert.alert_name,
1524 + alert_description=alert.alert_description,
1525 + status=alert.status,
1526 + customer_code=alert.customer_code,
1527 + source=alert.source,
1528 + assigned_to=alert.assigned_to,
1529 + comments=comments,
1530 + assets=assets,
1531 + tags=tags,
1532 + )
1533 + alerts_out.append(alert_out)
1534 +
1535 + return alerts_out
1536 +
1537 +
1538 async def delete_comments(alert_id: int, db: AsyncSession):
1539 result = await db.execute(select(Comment).where(Comment.alert_id == alert_id))
1540 comments = result.scalars().all()
@@ -1253,3 +1635,100 @@ async def delete_case(case_id: int, db: AsyncSession):
1635 except IntegrityError:
1636 await db.rollback()
1637 raise HTTPException(status_code=400, detail="Error deleting case")
1638 +
1639 +
1640 +async def list_all_files(db: AsyncSession) -> List[CaseDataStore]:
1641 + query = select(CaseDataStore)
1642 + result = await db.execute(query)
1643 + return result.scalars().all()
1644 +
1645 +
1646 +async def list_files_by_case_id(case_id: int, db: AsyncSession) -> List[CaseDataStore]:
1647 + query = select(CaseDataStore).where(CaseDataStore.case_id == case_id)
1648 + result = await db.execute(query)
1649 + return result.scalars().all()
1650 +
1651 +
1652 +async def file_exists(case_id: int, file_name: str, db: AsyncSession) -> bool:
1653 + query = select(CaseDataStore).where(CaseDataStore.case_id == case_id, CaseDataStore.file_name == file_name)
1654 + result = await db.execute(query)
1655 + return result.scalars().first() is not None
1656 +
1657 +
1658 +async def sha256_hash_file(file: UploadFile) -> str:
1659 + await file.seek(0)
1660 + file_content = await file.read()
1661 + file_hash = hashlib.sha256(file_content).hexdigest()
1662 + return file_hash
1663 +
1664 +
1665 +async def get_file_size(file: UploadFile) -> int:
1666 + await file.seek(0)
1667 + content = await file.read()
1668 + return len(content)
1669 +
1670 +
1671 +async def add_file_to_db(case_id: int, file: UploadFile, file_size: int, file_hash: str, db: AsyncSession) -> None:
1672 + db_file = CaseDataStore(
1673 + case_id=case_id,
1674 + bucket_name="copilot-cases",
1675 + object_key=f"{case_id}/{file.filename}",
1676 + file_name=file.filename,
1677 + content_type=file.content_type,
1678 + file_size=file_size,
1679 + file_hash=file_hash,
1680 + )
1681 + db.add(db_file)
1682 + await db.commit()
1683 + return db_file
1684 +
1685 +
1686 +async def upload_file_to_case(case_id: int, file: UploadFile, db: AsyncSession) -> CaseDataStore:
1687 + file_size = await get_file_size(file)
1688 + file_hash = await sha256_hash_file(file)
1689 + await file.seek(0)
1690 + # Upload the file to Minio
1691 + await upload_case_data_store(
1692 + data=CaseDataStoreCreation(
1693 + case_id=case_id,
1694 + bucket_name="copilot-cases",
1695 + object_key=file.filename,
1696 + file_name=file.filename,
1697 + content_type=file.content_type,
1698 + file_hash=file_hash,
1699 + ),
1700 + file=file,
1701 + )
1702 +
1703 + # Add the file to the database
1704 + return await add_file_to_db(case_id, file, file_size, file_hash, db)
1705 +
1706 +
1707 +async def get_file_by_case_id_and_name(case_id: int, file_name: str, db: AsyncSession) -> CaseDataStore:
1708 + logger.info(f"Getting file {file_name} from case {case_id}")
1709 + query = select(CaseDataStore).where(CaseDataStore.case_id == case_id, CaseDataStore.file_name == file_name)
1710 + result = await db.execute(query)
1711 + return result.scalars().first()
1712 +
1713 +
1714 +async def remove_file_from_db(file_id: int, db: AsyncSession) -> None:
1715 + await db.execute(delete(CaseDataStore).where(CaseDataStore.id == file_id))
1716 + await db.commit()
1717 +
1718 +
1719 +async def delete_file_from_case(case_id: int, file_name: str, db: AsyncSession) -> None:
1720 + file = await get_file_by_case_id_and_name(case_id, file_name, db)
1721 + if not file:
1722 + raise HTTPException(status_code=404, detail="File not found")
1723 +
1724 + await delete_file(file.bucket_name, file.object_key)
1725 + await remove_file_from_db(file.id, db)
1726 +
1727 +
1728 +async def download_file_from_case(case_id: int, file_name: str, db: AsyncSession) -> Tuple[bytes, str]:
1729 + file = await get_file_by_case_id_and_name(case_id, file_name, db)
1730 + if not file:
1731 + raise HTTPException(status_code=404, detail="File not found")
1732 +
1733 + file_content = await download_case_data_store(file.bucket_name, file.object_key)
1734 + return file_content, file.content_type
backend/app/integrations/modules/services/carbonblack.py
+2 -2
@@ -4,7 +4,7 @@ from loguru import logger
4 from app.integrations.modules.schema.carbonblack import CollectCarbonBlack
5
6
7 -async def post_to_copilot_carbonblack_module(data: CollectCarbonBlack, license_key: str):
7 +async def post_to_copilot_carbonblack_module(data: CollectCarbonBlack, license_key: str = None) -> None:
8 """
9 Send a POST request to the copilot-huntress-module Docker container.
10
@@ -16,7 +16,7 @@ async def post_to_copilot_carbonblack_module(data: CollectCarbonBlack, license_k
16 await client.post(
17 "http://copilot-carbonblack-module/collect",
18 json=data.dict(),
19 - params={"license_key": license_key, "feature_name": "CARBONBLACK"},
19 + # params={"license_key": license_key, "feature_name": "CARBONBLACK"},
20 timeout=120,
21 )
22 return None
backend/app/integrations/modules/services/huntress.py
+2 -2
@@ -4,7 +4,7 @@ from loguru import logger
4 from app.integrations.modules.schema.huntress import CollectHuntress
5
6
7 -async def post_to_copilot_huntress_module(data: CollectHuntress, license_key: str):
7 +async def post_to_copilot_huntress_module(data: CollectHuntress, license_key: str = None) -> None:
8 """
9 Send a POST request to the copilot-huntress-module Docker container.
10
@@ -16,7 +16,7 @@ async def post_to_copilot_huntress_module(data: CollectHuntress, license_key: st
16 await client.post(
17 "http://copilot-huntress-module/collect",
18 json=data.dict(),
19 - params={"license_key": license_key, "feature_name": "HUNTRESS"},
19 + # params={"license_key": license_key, "feature_name": "HUNTRESS"},
20 timeout=120,
21 )
22 return None
backend/app/integrations/modules/services/mimecast.py
+2 -2
@@ -4,7 +4,7 @@ from loguru import logger
4 from app.integrations.modules.schema.mimecast import CollectMimecast
5
6
7 -async def post_to_copilot_mimecast_module(data: CollectMimecast, license_key: str):
7 +async def post_to_copilot_mimecast_module(data: CollectMimecast, license_key: str = None) -> None:
8 """
9 Send a POST request to the copilot-mimecast-module Docker container.
10
@@ -16,7 +16,7 @@ async def post_to_copilot_mimecast_module(data: CollectMimecast, license_key: st
16 await client.post(
17 "http://copilot-mimecast-module/collect",
18 json=data.dict(),
19 - params={"license_key": license_key, "feature_name": "MIMECAST"},
19 + # params={"license_key": license_key, "feature_name": "MIMECAST"},
20 timeout=120,
21 )
22 return None
backend/app/routers/incidents.py
+2
@@ -2,9 +2,11 @@ from fastapi import APIRouter
2
3 from app.incidents.routes.db_operations import incidents_db_operations_router
4 from app.incidents.routes.incident_alert import incidents_alerts_router
5 +from app.incidents.routes.incident_report import incidents_report_router
6
7 # Instantiate the APIRouter
8 router = APIRouter()
9
10 router.include_router(incidents_db_operations_router, prefix="/incidents/db_operations", tags=["incidents"])
11 router.include_router(incidents_alerts_router, prefix="/incidents/alerts", tags=["incidents-alerts"])
12 +router.include_router(incidents_report_router, prefix="/incidents/report", tags=["incidents-report"])
backend/app/schedulers/scheduler.py
+10 -2
@@ -126,8 +126,14 @@ async def initialize_job_metadata():
126 "function": agent_sync,
127 "description": "Synchronizes agents with the Wazuh Manager and Velociraptor server.",
128 },
129 + # {
130 + # "job_id": "wazuh_index_fields_resize",
131 + # "time_interval": 1,
132 + # "function": resize_wazuh_index_fields,
133 + # "description": "Resizes the Wazuh index fields.",
134 + # },
135 {
130 - "job_id": "wazuh_index_fields_resize",
136 + "job_id": "resize_wazuh_index_fields",
137 "time_interval": 1,
138 "function": resize_wazuh_index_fields,
139 "description": "Resizes the Wazuh index fields.",
@@ -195,6 +201,7 @@ async def schedule_enabled_jobs(scheduler):
201 "invoke_suricata_monitoring_alert",
202 "invoke_office365_exchange_online_alert",
203 "invoke_office365_threat_intel_alert",
204 + "wazuh_index_fields_resize",
205 ]
206
207 # Disable each job in the list
@@ -241,7 +248,8 @@ def get_function_by_name(function_name: str):
248 """
249 function_map = {
250 "agent_sync": agent_sync,
244 - "wazuh_index_fields_resize": resize_wazuh_index_fields,
251 + # "wazuh_index_fields_resize": resize_wazuh_index_fields,
252 + "resize_wazuh_index_fields": resize_wazuh_index_fields,
253 "invoke_alert_creation_collect": invoke_alert_creation_collect,
254 "invoke_sigma_queries_collect": invoke_sigma_queries_collect,
255 "invoke_mimecast_integration": invoke_mimecast_integration,
backend/app/schedulers/services/wazuh_index_resize.py
+8 -3
@@ -3,6 +3,7 @@ from datetime import datetime
3 from loguru import logger
4 from sqlalchemy.future import select
5
6 +from app.connectors.utils import is_connector_verified
7 from app.connectors.wazuh_indexer.routes.monitoring import (
8 resize_wazuh_index_fields_route,
9 )
@@ -18,13 +19,17 @@ async def resize_wazuh_index_fields():
19 to synchronize agents, and updates the job metadata with the last success timestamp.
20
21 If the token retrieval fails, it prints a failure message. If the job metadata for
21 - 'wazuh_index_fields_resize' does not exist, it prints a message indicating the absence of the metadata.
22 + 'resize_wazuh_index_fields' does not exist, it prints a message indicating the absence of the metadata.
23 """
24 logger.info("Resizing Wazuh index fields via scheduler...")
25 async with get_db_session() as session:
26 + if not await is_connector_verified("Wazuh-Indexer", session):
27 + logger.warning("Wazuh Indexer connector is not verified.")
28 + return None
29 + logger.info("Wazuh Indexer connector is verified.")
30 await resize_wazuh_index_fields_route()
31
27 - stmt = select(JobMetadata).where(JobMetadata.job_id == "wazuh_index_fields_resize")
32 + stmt = select(JobMetadata).where(JobMetadata.job_id == "resize_wazuh_index_fields")
33 result = await session.execute(stmt)
34 job_metadata = result.scalars().first()
35
@@ -34,4 +39,4 @@ async def resize_wazuh_index_fields():
39 await session.commit() # Asynchronously commit the transaction
40 logger.info("Updated job metadata with the last success timestamp.")
41 else:
37 - logger.warning("JobMetadata for 'wazuh_index_fields_resize' not found.")
42 + logger.warning("JobMetadata for 'resize_wazuh_index_fields' not found.")
backend/copilot.py
+2
@@ -11,6 +11,7 @@ from fastapi.staticfiles import StaticFiles
11 from loguru import logger
12
13 from app.auth.utils import AuthHandler
14 +from app.data_store.data_store_setup import create_buckets
15 from app.db.db_session import SQLALCHEMY_DATABASE_URI_NO_DB
16 from app.db.db_session import async_engine
17 from app.db.db_setup import add_connectors
@@ -167,6 +168,7 @@ async def init_db():
168 await create_database_if_not_exists(db_url=SQLALCHEMY_DATABASE_URI_NO_DB, db_name="copilot")
169 await create_copilot_user_if_not_exists(db_url=SQLALCHEMY_DATABASE_URI_NO_DB, db_user_name="copilot")
170 apply_migrations()
171 + await create_buckets()
172 await add_connectors(async_engine)
173 await delete_connectors(async_engine)
174 await create_roles(async_engine)
backend/package-lock.json
+1323 -1
@@ -2,5 +2,1327 @@
2 "name": "backend",
3 "lockfileVersion": 3,
4 "requires": true,
5 - "packages": {}
5 + "packages": {
6 + "": {
7 + "devDependencies": {
8 + "eslint-plugin-vue": "^9.28.0"
9 + }
10 + },
11 + "node_modules/@eslint-community/eslint-utils": {
12 + "version": "4.4.0",
13 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
14 + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
15 + "dev": true,
16 + "dependencies": {
17 + "eslint-visitor-keys": "^3.3.0"
18 + },
19 + "engines": {
20 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
21 + },
22 + "peerDependencies": {
23 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
24 + }
25 + },
26 + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
27 + "version": "3.4.3",
28 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
29 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
30 + "dev": true,
31 + "engines": {
32 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
33 + },
34 + "funding": {
35 + "url": "https://opencollective.com/eslint"
36 + }
37 + },
38 + "node_modules/@eslint-community/regexpp": {
39 + "version": "4.11.1",
40 + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
41 + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
42 + "dev": true,
43 + "peer": true,
44 + "engines": {
45 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
46 + }
47 + },
48 + "node_modules/@eslint/config-array": {
49 + "version": "0.18.0",
50 + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
51 + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
52 + "dev": true,
53 + "peer": true,
54 + "dependencies": {
55 + "@eslint/object-schema": "^2.1.4",
56 + "debug": "^4.3.1",
57 + "minimatch": "^3.1.2"
58 + },
59 + "engines": {
60 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
61 + }
62 + },
63 + "node_modules/@eslint/eslintrc": {
64 + "version": "3.1.0",
65 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
66 + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
67 + "dev": true,
68 + "peer": true,
69 + "dependencies": {
70 + "ajv": "^6.12.4",
71 + "debug": "^4.3.2",
72 + "espree": "^10.0.1",
73 + "globals": "^14.0.0",
74 + "ignore": "^5.2.0",
75 + "import-fresh": "^3.2.1",
76 + "js-yaml": "^4.1.0",
77 + "minimatch": "^3.1.2",
78 + "strip-json-comments": "^3.1.1"
79 + },
80 + "engines": {
81 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
82 + },
83 + "funding": {
84 + "url": "https://opencollective.com/eslint"
85 + }
86 + },
87 + "node_modules/@eslint/js": {
88 + "version": "9.11.0",
89 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.0.tgz",
90 + "integrity": "sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==",
91 + "dev": true,
92 + "peer": true,
93 + "engines": {
94 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
95 + }
96 + },
97 + "node_modules/@eslint/object-schema": {
98 + "version": "2.1.4",
99 + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
100 + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
101 + "dev": true,
102 + "peer": true,
103 + "engines": {
104 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
105 + }
106 + },
107 + "node_modules/@eslint/plugin-kit": {
108 + "version": "0.2.0",
109 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
110 + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
111 + "dev": true,
112 + "peer": true,
113 + "dependencies": {
114 + "levn": "^0.4.1"
115 + },
116 + "engines": {
117 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
118 + }
119 + },
120 + "node_modules/@humanwhocodes/module-importer": {
121 + "version": "1.0.1",
122 + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
123 + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
124 + "dev": true,
125 + "peer": true,
126 + "engines": {
127 + "node": ">=12.22"
128 + },
129 + "funding": {
130 + "type": "github",
131 + "url": "https://github.com/sponsors/nzakas"
132 + }
133 + },
134 + "node_modules/@humanwhocodes/retry": {
135 + "version": "0.3.0",
136 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
137 + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
138 + "dev": true,
139 + "peer": true,
140 + "engines": {
141 + "node": ">=18.18"
142 + },
143 + "funding": {
144 + "type": "github",
145 + "url": "https://github.com/sponsors/nzakas"
146 + }
147 + },
148 + "node_modules/@nodelib/fs.scandir": {
149 + "version": "2.1.5",
150 + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
151 + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
152 + "dev": true,
153 + "peer": true,
154 + "dependencies": {
155 + "@nodelib/fs.stat": "2.0.5",
156 + "run-parallel": "^1.1.9"
157 + },
158 + "engines": {
159 + "node": ">= 8"
160 + }
161 + },
162 + "node_modules/@nodelib/fs.stat": {
163 + "version": "2.0.5",
164 + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
165 + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
166 + "dev": true,
167 + "peer": true,
168 + "engines": {
169 + "node": ">= 8"
170 + }
171 + },
172 + "node_modules/@nodelib/fs.walk": {
173 + "version": "1.2.8",
174 + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
175 + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
176 + "dev": true,
177 + "peer": true,
178 + "dependencies": {
179 + "@nodelib/fs.scandir": "2.1.5",
180 + "fastq": "^1.6.0"
181 + },
182 + "engines": {
183 + "node": ">= 8"
184 + }
185 + },
186 + "node_modules/acorn": {
187 + "version": "8.12.1",
188 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
189 + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
190 + "dev": true,
191 + "bin": {
192 + "acorn": "bin/acorn"
193 + },
194 + "engines": {
195 + "node": ">=0.4.0"
196 + }
197 + },
198 + "node_modules/acorn-jsx": {
199 + "version": "5.3.2",
200 + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
201 + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
202 + "dev": true,
203 + "peerDependencies": {
204 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
205 + }
206 + },
207 + "node_modules/ajv": {
208 + "version": "6.12.6",
209 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
210 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
211 + "dev": true,
212 + "peer": true,
213 + "dependencies": {
214 + "fast-deep-equal": "^3.1.1",
215 + "fast-json-stable-stringify": "^2.0.0",
216 + "json-schema-traverse": "^0.4.1",
217 + "uri-js": "^4.2.2"
218 + },
219 + "funding": {
220 + "type": "github",
221 + "url": "https://github.com/sponsors/epoberezkin"
222 + }
223 + },
224 + "node_modules/ansi-regex": {
225 + "version": "5.0.1",
226 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
227 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
228 + "dev": true,
229 + "peer": true,
230 + "engines": {
231 + "node": ">=8"
232 + }
233 + },
234 + "node_modules/ansi-styles": {
235 + "version": "4.3.0",
236 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
237 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
238 + "dev": true,
239 + "peer": true,
240 + "dependencies": {
241 + "color-convert": "^2.0.1"
242 + },
243 + "engines": {
244 + "node": ">=8"
245 + },
246 + "funding": {
247 + "url": "https://github.com/chalk/ansi-styles?sponsor=1"
248 + }
249 + },
250 + "node_modules/argparse": {
251 + "version": "2.0.1",
252 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
253 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
254 + "dev": true,
255 + "peer": true
256 + },
257 + "node_modules/balanced-match": {
258 + "version": "1.0.2",
259 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
260 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
261 + "dev": true,
262 + "peer": true
263 + },
264 + "node_modules/boolbase": {
265 + "version": "1.0.0",
266 + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
267 + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
268 + "dev": true
269 + },
270 + "node_modules/brace-expansion": {
271 + "version": "1.1.11",
272 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
273 + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
274 + "dev": true,
275 + "peer": true,
276 + "dependencies": {
277 + "balanced-match": "^1.0.0",
278 + "concat-map": "0.0.1"
279 + }
280 + },
281 + "node_modules/callsites": {
282 + "version": "3.1.0",
283 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
284 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
285 + "dev": true,
286 + "peer": true,
287 + "engines": {
288 + "node": ">=6"
289 + }
290 + },
291 + "node_modules/chalk": {
292 + "version": "4.1.2",
293 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
294 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
295 + "dev": true,
296 + "peer": true,
297 + "dependencies": {
298 + "ansi-styles": "^4.1.0",
299 + "supports-color": "^7.1.0"
300 + },
301 + "engines": {
302 + "node": ">=10"
303 + },
304 + "funding": {
305 + "url": "https://github.com/chalk/chalk?sponsor=1"
306 + }
307 + },
308 + "node_modules/color-convert": {
309 + "version": "2.0.1",
310 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
311 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
312 + "dev": true,
313 + "peer": true,
314 + "dependencies": {
315 + "color-name": "~1.1.4"
316 + },
317 + "engines": {
318 + "node": ">=7.0.0"
319 + }
320 + },
321 + "node_modules/color-name": {
322 + "version": "1.1.4",
323 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
324 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
325 + "dev": true,
326 + "peer": true
327 + },
328 + "node_modules/concat-map": {
329 + "version": "0.0.1",
330 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
331 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
332 + "dev": true,
333 + "peer": true
334 + },
335 + "node_modules/cross-spawn": {
336 + "version": "7.0.3",
337 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
338 + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
339 + "dev": true,
340 + "peer": true,
341 + "dependencies": {
342 + "path-key": "^3.1.0",
343 + "shebang-command": "^2.0.0",
344 + "which": "^2.0.1"
345 + },
346 + "engines": {
347 + "node": ">= 8"
348 + }
349 + },
350 + "node_modules/cssesc": {
351 + "version": "3.0.0",
352 + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
353 + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
354 + "dev": true,
355 + "bin": {
356 + "cssesc": "bin/cssesc"
357 + },
358 + "engines": {
359 + "node": ">=4"
360 + }
361 + },
362 + "node_modules/debug": {
363 + "version": "4.3.7",
364 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
365 + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
366 + "dev": true,
367 + "dependencies": {
368 + "ms": "^2.1.3"
369 + },
370 + "engines": {
371 + "node": ">=6.0"
372 + },
373 + "peerDependenciesMeta": {
374 + "supports-color": {
375 + "optional": true
376 + }
377 + }
378 + },
379 + "node_modules/deep-is": {
380 + "version": "0.1.4",
381 + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
382 + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
383 + "dev": true,
384 + "peer": true
385 + },
386 + "node_modules/escape-string-regexp": {
387 + "version": "4.0.0",
388 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
389 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
390 + "dev": true,
391 + "peer": true,
392 + "engines": {
393 + "node": ">=10"
394 + },
395 + "funding": {
396 + "url": "https://github.com/sponsors/sindresorhus"
397 + }
398 + },
399 + "node_modules/eslint": {
400 + "version": "9.11.0",
401 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.0.tgz",
402 + "integrity": "sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==",
403 + "dev": true,
404 + "peer": true,
405 + "dependencies": {
406 + "@eslint-community/eslint-utils": "^4.2.0",
407 + "@eslint-community/regexpp": "^4.11.0",
408 + "@eslint/config-array": "^0.18.0",
409 + "@eslint/eslintrc": "^3.1.0",
410 + "@eslint/js": "9.11.0",
411 + "@eslint/plugin-kit": "^0.2.0",
412 + "@humanwhocodes/module-importer": "^1.0.1",
413 + "@humanwhocodes/retry": "^0.3.0",
414 + "@nodelib/fs.walk": "^1.2.8",
415 + "ajv": "^6.12.4",
416 + "chalk": "^4.0.0",
417 + "cross-spawn": "^7.0.2",
418 + "debug": "^4.3.2",
419 + "escape-string-regexp": "^4.0.0",
420 + "eslint-scope": "^8.0.2",
421 + "eslint-visitor-keys": "^4.0.0",
422 + "espree": "^10.1.0",
423 + "esquery": "^1.5.0",
424 + "esutils": "^2.0.2",
425 + "fast-deep-equal": "^3.1.3",
426 + "file-entry-cache": "^8.0.0",
427 + "find-up": "^5.0.0",
428 + "glob-parent": "^6.0.2",
429 + "ignore": "^5.2.0",
430 + "imurmurhash": "^0.1.4",
431 + "is-glob": "^4.0.0",
432 + "is-path-inside": "^3.0.3",
433 + "json-stable-stringify-without-jsonify": "^1.0.1",
434 + "lodash.merge": "^4.6.2",
435 + "minimatch": "^3.1.2",
436 + "natural-compare": "^1.4.0",
437 + "optionator": "^0.9.3",
438 + "strip-ansi": "^6.0.1",
439 + "text-table": "^0.2.0"
440 + },
441 + "bin": {
442 + "eslint": "bin/eslint.js"
443 + },
444 + "engines": {
445 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
446 + },
447 + "funding": {
448 + "url": "https://eslint.org/donate"
449 + },
450 + "peerDependencies": {
451 + "jiti": "*"
452 + },
453 + "peerDependenciesMeta": {
454 + "jiti": {
455 + "optional": true
456 + }
457 + }
458 + },
459 + "node_modules/eslint-plugin-vue": {
460 + "version": "9.28.0",
461 + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz",
462 + "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==",
463 + "dev": true,
464 + "dependencies": {
465 + "@eslint-community/eslint-utils": "^4.4.0",
466 + "globals": "^13.24.0",
467 + "natural-compare": "^1.4.0",
468 + "nth-check": "^2.1.1",
469 + "postcss-selector-parser": "^6.0.15",
470 + "semver": "^7.6.3",
471 + "vue-eslint-parser": "^9.4.3",
472 + "xml-name-validator": "^4.0.0"
473 + },
474 + "engines": {
475 + "node": "^14.17.0 || >=16.0.0"
476 + },
477 + "peerDependencies": {
478 + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
479 + }
480 + },
481 + "node_modules/eslint-plugin-vue/node_modules/globals": {
482 + "version": "13.24.0",
483 + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
484 + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
485 + "dev": true,
486 + "dependencies": {
487 + "type-fest": "^0.20.2"
488 + },
489 + "engines": {
490 + "node": ">=8"
491 + },
492 + "funding": {
493 + "url": "https://github.com/sponsors/sindresorhus"
494 + }
495 + },
496 + "node_modules/eslint-scope": {
497 + "version": "8.0.2",
498 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
499 + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
500 + "dev": true,
501 + "peer": true,
502 + "dependencies": {
503 + "esrecurse": "^4.3.0",
504 + "estraverse": "^5.2.0"
505 + },
506 + "engines": {
507 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
508 + },
509 + "funding": {
510 + "url": "https://opencollective.com/eslint"
511 + }
512 + },
513 + "node_modules/eslint-visitor-keys": {
514 + "version": "4.0.0",
515 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
516 + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
517 + "dev": true,
518 + "peer": true,
519 + "engines": {
520 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
521 + },
522 + "funding": {
523 + "url": "https://opencollective.com/eslint"
524 + }
525 + },
526 + "node_modules/espree": {
527 + "version": "10.1.0",
528 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
529 + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
530 + "dev": true,
531 + "peer": true,
532 + "dependencies": {
533 + "acorn": "^8.12.0",
534 + "acorn-jsx": "^5.3.2",
535 + "eslint-visitor-keys": "^4.0.0"
536 + },
537 + "engines": {
538 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
539 + },
540 + "funding": {
541 + "url": "https://opencollective.com/eslint"
542 + }
543 + },
544 + "node_modules/esquery": {
545 + "version": "1.6.0",
546 + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
547 + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
548 + "dev": true,
549 + "dependencies": {
550 + "estraverse": "^5.1.0"
551 + },
552 + "engines": {
553 + "node": ">=0.10"
554 + }
555 + },
556 + "node_modules/esrecurse": {
557 + "version": "4.3.0",
558 + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
559 + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
560 + "dev": true,
561 + "dependencies": {
562 + "estraverse": "^5.2.0"
563 + },
564 + "engines": {
565 + "node": ">=4.0"
566 + }
567 + },
568 + "node_modules/estraverse": {
569 + "version": "5.3.0",
570 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
571 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
572 + "dev": true,
573 + "engines": {
574 + "node": ">=4.0"
575 + }
576 + },
577 + "node_modules/esutils": {
578 + "version": "2.0.3",
579 + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
580 + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
581 + "dev": true,
582 + "peer": true,
583 + "engines": {
584 + "node": ">=0.10.0"
585 + }
586 + },
587 + "node_modules/fast-deep-equal": {
588 + "version": "3.1.3",
589 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
590 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
591 + "dev": true,
592 + "peer": true
593 + },
594 + "node_modules/fast-json-stable-stringify": {
595 + "version": "2.1.0",
596 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
597 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
598 + "dev": true,
599 + "peer": true
600 + },
601 + "node_modules/fast-levenshtein": {
602 + "version": "2.0.6",
603 + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
604 + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
605 + "dev": true,
606 + "peer": true
607 + },
608 + "node_modules/fastq": {
609 + "version": "1.17.1",
610 + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
611 + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
612 + "dev": true,
613 + "peer": true,
614 + "dependencies": {
615 + "reusify": "^1.0.4"
616 + }
617 + },
618 + "node_modules/file-entry-cache": {
619 + "version": "8.0.0",
620 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
621 + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
622 + "dev": true,
623 + "peer": true,
624 + "dependencies": {
625 + "flat-cache": "^4.0.0"
626 + },
627 + "engines": {
628 + "node": ">=16.0.0"
629 + }
630 + },
631 + "node_modules/find-up": {
632 + "version": "5.0.0",
633 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
634 + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
635 + "dev": true,
636 + "peer": true,
637 + "dependencies": {
638 + "locate-path": "^6.0.0",
639 + "path-exists": "^4.0.0"
640 + },
641 + "engines": {
642 + "node": ">=10"
643 + },
644 + "funding": {
645 + "url": "https://github.com/sponsors/sindresorhus"
646 + }
647 + },
648 + "node_modules/flat-cache": {
649 + "version": "4.0.1",
650 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
651 + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
652 + "dev": true,
653 + "peer": true,
654 + "dependencies": {
655 + "flatted": "^3.2.9",
656 + "keyv": "^4.5.4"
657 + },
658 + "engines": {
659 + "node": ">=16"
660 + }
661 + },
662 + "node_modules/flatted": {
663 + "version": "3.3.1",
664 + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
665 + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
666 + "dev": true,
667 + "peer": true
668 + },
669 + "node_modules/glob-parent": {
670 + "version": "6.0.2",
671 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
672 + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
673 + "dev": true,
674 + "peer": true,
675 + "dependencies": {
676 + "is-glob": "^4.0.3"
677 + },
678 + "engines": {
679 + "node": ">=10.13.0"
680 + }
681 + },
682 + "node_modules/globals": {
683 + "version": "14.0.0",
684 + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
685 + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
686 + "dev": true,
687 + "peer": true,
688 + "engines": {
689 + "node": ">=18"
690 + },
691 + "funding": {
692 + "url": "https://github.com/sponsors/sindresorhus"
693 + }
694 + },
695 + "node_modules/has-flag": {
696 + "version": "4.0.0",
697 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
698 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
699 + "dev": true,
700 + "peer": true,
701 + "engines": {
702 + "node": ">=8"
703 + }
704 + },
705 + "node_modules/ignore": {
706 + "version": "5.3.2",
707 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
708 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
709 + "dev": true,
710 + "peer": true,
711 + "engines": {
712 + "node": ">= 4"
713 + }
714 + },
715 + "node_modules/import-fresh": {
716 + "version": "3.3.0",
717 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
718 + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
719 + "dev": true,
720 + "peer": true,
721 + "dependencies": {
722 + "parent-module": "^1.0.0",
723 + "resolve-from": "^4.0.0"
724 + },
725 + "engines": {
726 + "node": ">=6"
727 + },
728 + "funding": {
729 + "url": "https://github.com/sponsors/sindresorhus"
730 + }
731 + },
732 + "node_modules/imurmurhash": {
733 + "version": "0.1.4",
734 + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
735 + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
736 + "dev": true,
737 + "peer": true,
738 + "engines": {
739 + "node": ">=0.8.19"
740 + }
741 + },
742 + "node_modules/is-extglob": {
743 + "version": "2.1.1",
744 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
745 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
746 + "dev": true,
747 + "peer": true,
748 + "engines": {
749 + "node": ">=0.10.0"
750 + }
751 + },
752 + "node_modules/is-glob": {
753 + "version": "4.0.3",
754 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
755 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
756 + "dev": true,
757 + "peer": true,
758 + "dependencies": {
759 + "is-extglob": "^2.1.1"
760 + },
761 + "engines": {
762 + "node": ">=0.10.0"
763 + }
764 + },
765 + "node_modules/is-path-inside": {
766 + "version": "3.0.3",
767 + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
768 + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
769 + "dev": true,
770 + "peer": true,
771 + "engines": {
772 + "node": ">=8"
773 + }
774 + },
775 + "node_modules/isexe": {
776 + "version": "2.0.0",
777 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
778 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
779 + "dev": true,
780 + "peer": true
781 + },
782 + "node_modules/js-yaml": {
783 + "version": "4.1.0",
784 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
785 + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
786 + "dev": true,
787 + "peer": true,
788 + "dependencies": {
789 + "argparse": "^2.0.1"
790 + },
791 + "bin": {
792 + "js-yaml": "bin/js-yaml.js"
793 + }
794 + },
795 + "node_modules/json-buffer": {
796 + "version": "3.0.1",
797 + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
798 + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
799 + "dev": true,
800 + "peer": true
801 + },
802 + "node_modules/json-schema-traverse": {
803 + "version": "0.4.1",
804 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
805 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
806 + "dev": true,
807 + "peer": true
808 + },
809 + "node_modules/json-stable-stringify-without-jsonify": {
810 + "version": "1.0.1",
811 + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
812 + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
813 + "dev": true,
814 + "peer": true
815 + },
816 + "node_modules/keyv": {
817 + "version": "4.5.4",
818 + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
819 + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
820 + "dev": true,
821 + "peer": true,
822 + "dependencies": {
823 + "json-buffer": "3.0.1"
824 + }
825 + },
826 + "node_modules/levn": {
827 + "version": "0.4.1",
828 + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
829 + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
830 + "dev": true,
831 + "peer": true,
832 + "dependencies": {
833 + "prelude-ls": "^1.2.1",
834 + "type-check": "~0.4.0"
835 + },
836 + "engines": {
837 + "node": ">= 0.8.0"
838 + }
839 + },
840 + "node_modules/locate-path": {
841 + "version": "6.0.0",
842 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
843 + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
844 + "dev": true,
845 + "peer": true,
846 + "dependencies": {
847 + "p-locate": "^5.0.0"
848 + },
849 + "engines": {
850 + "node": ">=10"
851 + },
852 + "funding": {
853 + "url": "https://github.com/sponsors/sindresorhus"
854 + }
855 + },
856 + "node_modules/lodash": {
857 + "version": "4.17.21",
858 + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
859 + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
860 + "dev": true
861 + },
862 + "node_modules/lodash.merge": {
863 + "version": "4.6.2",
864 + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
865 + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
866 + "dev": true,
867 + "peer": true
868 + },
869 + "node_modules/minimatch": {
870 + "version": "3.1.2",
871 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
872 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
873 + "dev": true,
874 + "peer": true,
875 + "dependencies": {
876 + "brace-expansion": "^1.1.7"
877 + },
878 + "engines": {
879 + "node": "*"
880 + }
881 + },
882 + "node_modules/ms": {
883 + "version": "2.1.3",
884 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
885 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
886 + "dev": true
887 + },
888 + "node_modules/natural-compare": {
889 + "version": "1.4.0",
890 + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
891 + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
892 + "dev": true
893 + },
894 + "node_modules/nth-check": {
895 + "version": "2.1.1",
896 + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
897 + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
898 + "dev": true,
899 + "dependencies": {
900 + "boolbase": "^1.0.0"
901 + },
902 + "funding": {
903 + "url": "https://github.com/fb55/nth-check?sponsor=1"
904 + }
905 + },
906 + "node_modules/optionator": {
907 + "version": "0.9.4",
908 + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
909 + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
910 + "dev": true,
911 + "peer": true,
912 + "dependencies": {
913 + "deep-is": "^0.1.3",
914 + "fast-levenshtein": "^2.0.6",
915 + "levn": "^0.4.1",
916 + "prelude-ls": "^1.2.1",
917 + "type-check": "^0.4.0",
918 + "word-wrap": "^1.2.5"
919 + },
920 + "engines": {
921 + "node": ">= 0.8.0"
922 + }
923 + },
924 + "node_modules/p-limit": {
925 + "version": "3.1.0",
926 + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
927 + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
928 + "dev": true,
929 + "peer": true,
930 + "dependencies": {
931 + "yocto-queue": "^0.1.0"
932 + },
933 + "engines": {
934 + "node": ">=10"
935 + },
936 + "funding": {
937 + "url": "https://github.com/sponsors/sindresorhus"
938 + }
939 + },
940 + "node_modules/p-locate": {
941 + "version": "5.0.0",
942 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
943 + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
944 + "dev": true,
945 + "peer": true,
946 + "dependencies": {
947 + "p-limit": "^3.0.2"
948 + },
949 + "engines": {
950 + "node": ">=10"
951 + },
952 + "funding": {
953 + "url": "https://github.com/sponsors/sindresorhus"
954 + }
955 + },
956 + "node_modules/parent-module": {
957 + "version": "1.0.1",
958 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
959 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
960 + "dev": true,
961 + "peer": true,
962 + "dependencies": {
963 + "callsites": "^3.0.0"
964 + },
965 + "engines": {
966 + "node": ">=6"
967 + }
968 + },
969 + "node_modules/path-exists": {
970 + "version": "4.0.0",
971 + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
972 + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
973 + "dev": true,
974 + "peer": true,
975 + "engines": {
976 + "node": ">=8"
977 + }
978 + },
979 + "node_modules/path-key": {
980 + "version": "3.1.1",
981 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
982 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
983 + "dev": true,
984 + "peer": true,
985 + "engines": {
986 + "node": ">=8"
987 + }
988 + },
989 + "node_modules/postcss-selector-parser": {
990 + "version": "6.1.2",
991 + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
992 + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
993 + "dev": true,
994 + "dependencies": {
995 + "cssesc": "^3.0.0",
996 + "util-deprecate": "^1.0.2"
997 + },
998 + "engines": {
999 + "node": ">=4"
1000 + }
1001 + },
1002 + "node_modules/prelude-ls": {
1003 + "version": "1.2.1",
1004 + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
1005 + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
1006 + "dev": true,
1007 + "peer": true,
1008 + "engines": {
1009 + "node": ">= 0.8.0"
1010 + }
1011 + },
1012 + "node_modules/punycode": {
1013 + "version": "2.3.1",
1014 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1015 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1016 + "dev": true,
1017 + "peer": true,
1018 + "engines": {
1019 + "node": ">=6"
1020 + }
1021 + },
1022 + "node_modules/queue-microtask": {
1023 + "version": "1.2.3",
1024 + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1025 + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1026 + "dev": true,
1027 + "funding": [
1028 + {
1029 + "type": "github",
1030 + "url": "https://github.com/sponsors/feross"
1031 + },
1032 + {
1033 + "type": "patreon",
1034 + "url": "https://www.patreon.com/feross"
1035 + },
1036 + {
1037 + "type": "consulting",
1038 + "url": "https://feross.org/support"
1039 + }
1040 + ],
1041 + "peer": true
1042 + },
1043 + "node_modules/resolve-from": {
1044 + "version": "4.0.0",
1045 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
1046 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
1047 + "dev": true,
1048 + "peer": true,
1049 + "engines": {
1050 + "node": ">=4"
1051 + }
1052 + },
1053 + "node_modules/reusify": {
1054 + "version": "1.0.4",
1055 + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
1056 + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
1057 + "dev": true,
1058 + "peer": true,
1059 + "engines": {
1060 + "iojs": ">=1.0.0",
1061 + "node": ">=0.10.0"
1062 + }
1063 + },
1064 + "node_modules/run-parallel": {
1065 + "version": "1.2.0",
1066 + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1067 + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1068 + "dev": true,
1069 + "funding": [
1070 + {
1071 + "type": "github",
1072 + "url": "https://github.com/sponsors/feross"
1073 + },
1074 + {
1075 + "type": "patreon",
1076 + "url": "https://www.patreon.com/feross"
1077 + },
1078 + {
1079 + "type": "consulting",
1080 + "url": "https://feross.org/support"
1081 + }
1082 + ],
1083 + "peer": true,
1084 + "dependencies": {
1085 + "queue-microtask": "^1.2.2"
1086 + }
1087 + },
1088 + "node_modules/semver": {
1089 + "version": "7.6.3",
1090 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
1091 + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
1092 + "dev": true,
1093 + "bin": {
1094 + "semver": "bin/semver.js"
1095 + },
1096 + "engines": {
1097 + "node": ">=10"
1098 + }
1099 + },
1100 + "node_modules/shebang-command": {
1101 + "version": "2.0.0",
1102 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1103 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1104 + "dev": true,
1105 + "peer": true,
1106 + "dependencies": {
1107 + "shebang-regex": "^3.0.0"
1108 + },
1109 + "engines": {
1110 + "node": ">=8"
1111 + }
1112 + },
1113 + "node_modules/shebang-regex": {
1114 + "version": "3.0.0",
1115 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1116 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1117 + "dev": true,
1118 + "peer": true,
1119 + "engines": {
1120 + "node": ">=8"
1121 + }
1122 + },
1123 + "node_modules/strip-ansi": {
1124 + "version": "6.0.1",
1125 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1126 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1127 + "dev": true,
1128 + "peer": true,
1129 + "dependencies": {
1130 + "ansi-regex": "^5.0.1"
1131 + },
1132 + "engines": {
1133 + "node": ">=8"
1134 + }
1135 + },
1136 + "node_modules/strip-json-comments": {
1137 + "version": "3.1.1",
1138 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
1139 + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
1140 + "dev": true,
1141 + "peer": true,
1142 + "engines": {
1143 + "node": ">=8"
1144 + },
1145 + "funding": {
1146 + "url": "https://github.com/sponsors/sindresorhus"
1147 + }
1148 + },
1149 + "node_modules/supports-color": {
1150 + "version": "7.2.0",
1151 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
1152 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
1153 + "dev": true,
1154 + "peer": true,
1155 + "dependencies": {
1156 + "has-flag": "^4.0.0"
1157 + },
1158 + "engines": {
1159 + "node": ">=8"
1160 + }
1161 + },
1162 + "node_modules/text-table": {
1163 + "version": "0.2.0",
1164 + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
1165 + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
1166 + "dev": true,
1167 + "peer": true
1168 + },
1169 + "node_modules/type-check": {
1170 + "version": "0.4.0",
1171 + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
1172 + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
1173 + "dev": true,
1174 + "peer": true,
1175 + "dependencies": {
1176 + "prelude-ls": "^1.2.1"
1177 + },
1178 + "engines": {
1179 + "node": ">= 0.8.0"
1180 + }
1181 + },
1182 + "node_modules/type-fest": {
1183 + "version": "0.20.2",
1184 + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
1185 + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
1186 + "dev": true,
1187 + "engines": {
1188 + "node": ">=10"
1189 + },
1190 + "funding": {
1191 + "url": "https://github.com/sponsors/sindresorhus"
1192 + }
1193 + },
1194 + "node_modules/uri-js": {
1195 + "version": "4.4.1",
1196 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
1197 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
1198 + "dev": true,
1199 + "peer": true,
1200 + "dependencies": {
1201 + "punycode": "^2.1.0"
1202 + }
1203 + },
1204 + "node_modules/util-deprecate": {
1205 + "version": "1.0.2",
1206 + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1207 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1208 + "dev": true
1209 + },
1210 + "node_modules/vue-eslint-parser": {
1211 + "version": "9.4.3",
1212 + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
1213 + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
1214 + "dev": true,
1215 + "dependencies": {
1216 + "debug": "^4.3.4",
1217 + "eslint-scope": "^7.1.1",
1218 + "eslint-visitor-keys": "^3.3.0",
1219 + "espree": "^9.3.1",
1220 + "esquery": "^1.4.0",
1221 + "lodash": "^4.17.21",
1222 + "semver": "^7.3.6"
1223 + },
1224 + "engines": {
1225 + "node": "^14.17.0 || >=16.0.0"
1226 + },
1227 + "funding": {
1228 + "url": "https://github.com/sponsors/mysticatea"
1229 + },
1230 + "peerDependencies": {
1231 + "eslint": ">=6.0.0"
1232 + }
1233 + },
1234 + "node_modules/vue-eslint-parser/node_modules/eslint-scope": {
1235 + "version": "7.2.2",
1236 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
1237 + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
1238 + "dev": true,
1239 + "dependencies": {
1240 + "esrecurse": "^4.3.0",
1241 + "estraverse": "^5.2.0"
1242 + },
1243 + "engines": {
1244 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1245 + },
1246 + "funding": {
1247 + "url": "https://opencollective.com/eslint"
1248 + }
1249 + },
1250 + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": {
1251 + "version": "3.4.3",
1252 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
1253 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
1254 + "dev": true,
1255 + "engines": {
1256 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1257 + },
1258 + "funding": {
1259 + "url": "https://opencollective.com/eslint"
1260 + }
1261 + },
1262 + "node_modules/vue-eslint-parser/node_modules/espree": {
1263 + "version": "9.6.1",
1264 + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
1265 + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
1266 + "dev": true,
1267 + "dependencies": {
1268 + "acorn": "^8.9.0",
1269 + "acorn-jsx": "^5.3.2",
1270 + "eslint-visitor-keys": "^3.4.1"
1271 + },
1272 + "engines": {
1273 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1274 + },
1275 + "funding": {
1276 + "url": "https://opencollective.com/eslint"
1277 + }
1278 + },
1279 + "node_modules/which": {
1280 + "version": "2.0.2",
1281 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1282 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1283 + "dev": true,
1284 + "peer": true,
1285 + "dependencies": {
1286 + "isexe": "^2.0.0"
1287 + },
1288 + "bin": {
1289 + "node-which": "bin/node-which"
1290 + },
1291 + "engines": {
1292 + "node": ">= 8"
1293 + }
1294 + },
1295 + "node_modules/word-wrap": {
1296 + "version": "1.2.5",
1297 + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
1298 + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
1299 + "dev": true,
1300 + "peer": true,
1301 + "engines": {
1302 + "node": ">=0.10.0"
1303 + }
1304 + },
1305 + "node_modules/xml-name-validator": {
1306 + "version": "4.0.0",
1307 + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
1308 + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
1309 + "dev": true,
1310 + "engines": {
1311 + "node": ">=12"
1312 + }
1313 + },
1314 + "node_modules/yocto-queue": {
1315 + "version": "0.1.0",
1316 + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
1317 + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
1318 + "dev": true,
1319 + "peer": true,
1320 + "engines": {
1321 + "node": ">=10"
1322 + },
1323 + "funding": {
1324 + "url": "https://github.com/sponsors/sindresorhus"
1325 + }
1326 + }
1327 + }
1328 }
backend/package.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "devDependencies": {
3 + "eslint-plugin-vue": "^9.28.0"
4 + }
5 +}
backend/requirements.txt
+1
@@ -81,6 +81,7 @@ marshmallow==3.20.1
81 marshmallow-sqlalchemy==0.29.0
82 matplotlib==3.8.0
83 mdurl==0.1.2
84 +miniopy-async==1.21.1
85 mitreattack-python==2.0.14
86 mixbox==1.0.5
87 multidict==6.0.4
docker-compose.yml
+11
@@ -34,6 +34,17 @@ services:
34 volumes:
35 - mysql-data:/var/lib/mysql
36
37 + copilot-minio:
38 + image: quay.io/minio/minio:RELEASE.2024-09-13T20-26-02Z
39 + environment:
40 + MINIO_ROOT_USER: ${MINIO_ROOT_USER}
41 + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
42 + ports:
43 + - "9000:9000"
44 + volumes:
45 + - ./data/data/minio-data:/data
46 + command: server /data --console-address ":9001"
47 +
48 copilot-nuclei-module:
49 image: ghcr.io/socfortress/copilot-nuclei-module:latest
50
frontend/cypress/e2e/tsconfig.json
+4 -4
@@ -1,11 +1,11 @@
1 {
2 "extends": "@vue/tsconfig/tsconfig.dom.json",
3 - "include": ["./**/*", "../support/**/*"],
3 "compilerOptions": {
5 - "isolatedModules": false,
4 "target": "es5",
5 "lib": ["es5", "dom"],
6 "types": ["cypress"],
9 - "sourceMap": false
10 - }
7 + "sourceMap": false,
8 + "isolatedModules": false
9 + },
10 + "include": ["./**/*", "../support/**/*"]
11 }
frontend/eslint.config.js deleted
-51
@@ -1,51 +0,0 @@
1 -import globals from "globals"
2 -import path from "node:path"
3 -import { fileURLToPath } from "node:url"
4 -import js from "@eslint/js"
5 -import pluginVue from "eslint-plugin-vue"
6 -import { FlatCompat } from "@eslint/eslintrc"
7 -
8 -const __filename = fileURLToPath(import.meta.url)
9 -const __dirname = path.dirname(__filename)
10 -const compat = new FlatCompat({
11 - baseDirectory: __dirname,
12 - recommendedConfig: js.configs.recommended,
13 - allConfig: js.configs.all
14 -})
15 -
16 -export default [
17 - {
18 - ignores: ["**/dist/*", "**/tests/*", "**/.gitignore", "**/vite-env.d.ts"]
19 - },
20 - ...pluginVue.configs["flat/essential"],
21 - ...compat.extends(
22 - "eslint:recommended",
23 - "@vue/eslint-config-typescript/recommended",
24 - "@vue/eslint-config-prettier/skip-formatting"
25 - ),
26 - {
27 - files: [
28 - "**/*.vue",
29 - "**/*.js",
30 - "**/*.jsx",
31 - "**/*.cjs",
32 - "**/*.mjs",
33 - "**/*.ts",
34 - "**/*.tsx",
35 - "**/*.cts",
36 - "**/*.mts"
37 - ],
38 - languageOptions: {
39 - ecmaVersion: "latest",
40 - globals: {
41 - ...globals.browser,
42 - ...globals.node,
43 - ...globals.amd
44 - }
45 - },
46 - rules: {
47 - "vue/multi-word-component-names": "off",
48 - "vue/no-setup-props-destructure": "off"
49 - }
50 - }
51 -]
frontend/eslint.config.mjs new
+48
@@ -0,0 +1,48 @@
1 +import antfu from "@antfu/eslint-config"
2 +
3 +export default antfu({
4 + vue: true,
5 + typescript: true,
6 + javascript: true,
7 + jsx: true,
8 +
9 + stylistic: {
10 + indent: "tab",
11 + quotes: "double"
12 + },
13 + rules: {
14 + "antfu/if-newline": "off",
15 + "style/operator-linebreak": "off",
16 + "style/arrow-parens": "off",
17 + "style/brace-style": "off",
18 + "style/indent-binary-ops": "off",
19 + "style/indent": "off",
20 + "style/member-delimiter-style": "off",
21 + "style/quotes": "off",
22 + "style/quote-props": "off",
23 + "style/eol-last": "off",
24 + "style/no-mixed-spaces-and-tabs": "off",
25 + "style/comma-dangle": [
26 + "error",
27 + {
28 + arrays: "never",
29 + objects: "never",
30 + imports: "never",
31 + exports: "never",
32 + functions: "never"
33 + }
34 + ],
35 + "vue/block-order": [
36 + "error",
37 + {
38 + order: ["template", "script", "style"]
39 + }
40 + ],
41 + "vue/component-name-in-template-casing": "off",
42 + "vue/custom-event-name-casing": "off",
43 + "vue/html-self-closing": "off",
44 + "vue/singleline-html-element-content-newline": "off",
45 + "vue/comma-dangle": "off",
46 + "vue/quote-props": "off"
47 + }
48 +})
frontend/package-lock.json
+3444 -1052
@@ -14,9 +14,9 @@
14 "@fontsource/lexend": "^5.1.0",
15 "@fontsource/public-sans": "^5.1.0",
16 "@popperjs/core": "^2.11.8",
17 - "@shikijs/markdown-it": "^1.17.6",
18 - "@vueuse/components": "^11.0.3",
19 - "@vueuse/core": "^11.0.3",
17 + "@shikijs/markdown-it": "^1.18.0",
18 + "@vueuse/components": "^11.1.0",
19 + "@vueuse/core": "^11.1.0",
20 "apexcharts": "^3.53.0",
21 "axios": "^1.7.7",
22 "bytes": "^3.1.2",
@@ -27,18 +27,18 @@
27 "echarts": "^5.5.1",
28 "file-saver": "^2.0.5",
29 "html-entities": "^2.5.2",
30 - "jose": "^5.9.0",
30 + "jose": "^5.9.2",
31 "js-md5": "^0.8.3",
32 "lodash": "^4.17.21",
33 "mitt": "^3.0.1",
34 "naive-ui": "^2.39.0",
35 "password-validator": "^5.3.0",
36 "pinia": "^2.2.2",
37 - "pinia-plugin-persistedstate": "^4.0.1",
37 + "pinia-plugin-persistedstate": "^4.0.2",
38 "secure-ls": "^2.0.0",
39 - "shiki": "^1.17.6",
39 + "shiki": "^1.18.0",
40 "validator": "^13.12.0",
41 - "vue": "^3.5.5",
41 + "vue": "^3.5.7",
42 "vue-advanced-cropper": "^2.8.9",
43 "vue-highlight-words": "^3.0.1",
44 "vue-i18n": "^10.0.1",
@@ -49,6 +49,7 @@
49 "vuedraggable": "^4.1.0"
50 },
51 "devDependencies": {
52 + "@antfu/eslint-config": "^3.7.1",
53 "@clack/prompts": "^0.7.0",
54 "@iconify/vue": "^4.1.2",
55 "@tsconfig/node20": "^20.1.4",
@@ -58,38 +59,34 @@
59 "@types/inquirer": "^9.0.7",
60 "@types/jsdom": "^21.1.7",
61 "@types/lodash": "^4.17.7",
61 - "@types/node": "^22.5.4",
62 - "@types/validator": "^13.12.1",
63 - "@vitejs/plugin-vue": "^5.1.3",
62 + "@types/node": "^22.5.5",
63 + "@types/validator": "^13.12.2",
64 + "@vitejs/plugin-vue": "^5.1.4",
65 "@vitejs/plugin-vue-jsx": "^4.0.1",
65 - "@vue/eslint-config-prettier": "^9.0.0",
66 - "@vue/eslint-config-typescript": "^13.0.0",
66 "@vue/test-utils": "^2.4.6",
67 "@vue/tsconfig": "^0.5.1",
68 "autoprefixer": "^10.4.20",
69 "cypress": "^13.14.2",
71 - "eslint": "^9.10.0",
72 - "eslint-plugin-cypress": "^3.5.0",
73 - "eslint-plugin-vue": "^9.28.0",
70 + "eslint": "^9.11.0",
71 "flourite": "^1.3.0",
72 "fs-extra": "^11.2.0",
73 "globals": "^15.9.0",
74 "jsdom": "^25.0.0",
75 "json5": "^2.2.3",
79 - "npm-run-all2": "^6.2.2",
76 + "npm-run-all2": "^6.2.3",
77 "picocolors": "^1.1.0",
81 - "postcss": "^8.4.45",
78 + "postcss": "^8.4.47",
79 "prettier": "^3.3.3",
83 - "sass": "^1.78.0",
84 - "start-server-and-test": "^2.0.7",
80 + "sass": "^1.79.3",
81 + "start-server-and-test": "^2.0.8",
82 "tailwind-config-viewer": "^2.0.4",
86 - "tailwindcss": "^3.4.11",
87 - "taze": "^0.16.8",
83 + "tailwindcss": "^3.4.12",
84 + "taze": "^0.16.9",
85 "type-fest": "^4.26.1",
86 "typescript": "~5.5.4",
87 "unplugin-vue-components": "^0.27.4",
91 - "vite": "^5.4.5",
92 - "vite-bundle-analyzer": "^0.10.6",
88 + "vite": "^5.4.7",
89 + "vite-bundle-analyzer": "^0.11.1",
90 "vite-bundle-visualizer": "^1.2.1",
91 "vite-plugin-vue-devtools": "^7.4.5",
92 "vite-svg-loader": "^5.1.0",
@@ -100,7 +97,7 @@
97 "node": ">=18.0.0"
98 },
99 "optionalDependencies": {
103 - "@rollup/rollup-linux-x64-gnu": "^4.21.3"
100 + "@rollup/rollup-linux-x64-gnu": "^4.22.4"
101 }
102 },
103 "node_modules/@ajoelp/json-to-formdata": {
@@ -135,6 +132,125 @@
132 "node": ">=6.0.0"
133 }
134 },
135 + "node_modules/@antfu/eslint-config": {
136 + "version": "3.7.1",
137 + "resolved": "https://registry.npmjs.org/@antfu/eslint-config/-/eslint-config-3.7.1.tgz",
138 + "integrity": "sha512-cDBAj+Le9XakAK83ZWVNxxyBJKeBfQ6H3VkUGnJPkXn/KJEzXC0fcHDtbu9k633hasLba5gXN5pjm4C70ik+Fg==",
139 + "dev": true,
140 + "dependencies": {
141 + "@antfu/install-pkg": "^0.4.1",
142 + "@clack/prompts": "^0.7.0",
143 + "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
144 + "@eslint/markdown": "^6.1.0",
145 + "@stylistic/eslint-plugin": "^2.8.0",
146 + "@typescript-eslint/eslint-plugin": "^8.6.0",
147 + "@typescript-eslint/parser": "^8.6.0",
148 + "@vitest/eslint-plugin": "^1.1.4",
149 + "eslint-config-flat-gitignore": "^0.3.0",
150 + "eslint-flat-config-utils": "^0.4.0",
151 + "eslint-merge-processors": "^0.1.0",
152 + "eslint-plugin-antfu": "^2.7.0",
153 + "eslint-plugin-command": "^0.2.5",
154 + "eslint-plugin-import-x": "^4.2.1",
155 + "eslint-plugin-jsdoc": "^50.2.4",
156 + "eslint-plugin-jsonc": "^2.16.0",
157 + "eslint-plugin-n": "^17.10.3",
158 + "eslint-plugin-no-only-tests": "^3.3.0",
159 + "eslint-plugin-perfectionist": "^3.6.0",
160 + "eslint-plugin-regexp": "^2.6.0",
161 + "eslint-plugin-toml": "^0.11.1",
162 + "eslint-plugin-unicorn": "^55.0.0",
163 + "eslint-plugin-unused-imports": "^4.1.4",
164 + "eslint-plugin-vue": "^9.28.0",
165 + "eslint-plugin-yml": "^1.14.0",
166 + "eslint-processor-vue-blocks": "^0.1.2",
167 + "globals": "^15.9.0",
168 + "jsonc-eslint-parser": "^2.4.0",
169 + "local-pkg": "^0.5.0",
170 + "parse-gitignore": "^2.0.0",
171 + "picocolors": "^1.1.0",
172 + "toml-eslint-parser": "^0.10.0",
173 + "vue-eslint-parser": "^9.4.3",
174 + "yaml-eslint-parser": "^1.2.3",
175 + "yargs": "^17.7.2"
176 + },
177 + "bin": {
178 + "eslint-config": "bin/index.js"
179 + },
180 + "funding": {
181 + "url": "https://github.com/sponsors/antfu"
182 + },
183 + "peerDependencies": {
184 + "@eslint-react/eslint-plugin": "^1.5.8",
185 + "@prettier/plugin-xml": "^3.4.1",
186 + "@unocss/eslint-plugin": ">=0.50.0",
187 + "astro-eslint-parser": "^1.0.2",
188 + "eslint": "^9.10.0",
189 + "eslint-plugin-astro": "^1.2.0",
190 + "eslint-plugin-format": ">=0.1.0",
191 + "eslint-plugin-react-hooks": "^4.6.0",
192 + "eslint-plugin-react-refresh": "^0.4.4",
193 + "eslint-plugin-solid": "^0.14.3",
194 + "eslint-plugin-svelte": ">=2.35.1",
195 + "prettier-plugin-astro": "^0.13.0",
196 + "prettier-plugin-slidev": "^1.0.5",
197 + "svelte-eslint-parser": ">=0.37.0"
198 + },
199 + "peerDependenciesMeta": {
200 + "@eslint-react/eslint-plugin": {
201 + "optional": true
202 + },
203 + "@prettier/plugin-xml": {
204 + "optional": true
205 + },
206 + "@unocss/eslint-plugin": {
207 + "optional": true
208 + },
209 + "astro-eslint-parser": {
210 + "optional": true
211 + },
212 + "eslint-plugin-astro": {
213 + "optional": true
214 + },
215 + "eslint-plugin-format": {
216 + "optional": true
217 + },
218 + "eslint-plugin-react-hooks": {
219 + "optional": true
220 + },
221 + "eslint-plugin-react-refresh": {
222 + "optional": true
223 + },
224 + "eslint-plugin-solid": {
225 + "optional": true
226 + },
227 + "eslint-plugin-svelte": {
228 + "optional": true
229 + },
230 + "prettier-plugin-astro": {
231 + "optional": true
232 + },
233 + "prettier-plugin-slidev": {
234 + "optional": true
235 + },
236 + "svelte-eslint-parser": {
237 + "optional": true
238 + }
239 + }
240 + },
241 + "node_modules/@antfu/install-pkg": {
242 + "version": "0.4.1",
243 + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz",
244 + "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==",
245 + "dev": true,
246 + "dependencies": {
247 + "package-manager-detector": "^0.2.0",
248 + "tinyexec": "^0.3.0"
249 + },
250 + "funding": {
251 + "url": "https://github.com/sponsors/antfu"
252 + }
253 + },
254 "node_modules/@antfu/ni": {
255 "version": "0.23.0",
256 "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.23.0.tgz",
@@ -668,7 +784,6 @@
784 },
785 "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
786 "version": "1.3.0",
671 - "dev": true,
787 "inBundle": true,
788 "license": "MIT",
789 "engines": {
@@ -757,6 +872,20 @@
872 "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
873 "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
874 },
875 + "node_modules/@es-joy/jsdoccomment": {
876 + "version": "0.48.0",
877 + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz",
878 + "integrity": "sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==",
879 + "dev": true,
880 + "dependencies": {
881 + "comment-parser": "1.4.1",
882 + "esquery": "^1.6.0",
883 + "jsdoc-type-pratt-parser": "~4.1.0"
884 + },
885 + "engines": {
886 + "node": ">=16"
887 + }
888 + },
889 "node_modules/@esbuild/aix-ppc64": {
890 "version": "0.21.5",
891 "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -1141,6 +1270,25 @@
1270 "node": ">=12"
1271 }
1272 },
1273 + "node_modules/@eslint-community/eslint-plugin-eslint-comments": {
1274 + "version": "4.4.0",
1275 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.0.tgz",
1276 + "integrity": "sha512-yljsWl5Qv3IkIRmJ38h3NrHXFCm4EUl55M8doGTF6hvzvFF8kRpextgSrg2dwHev9lzBZyafCr9RelGIyQm6fw==",
1277 + "dev": true,
1278 + "dependencies": {
1279 + "escape-string-regexp": "^4.0.0",
1280 + "ignore": "^5.2.4"
1281 + },
1282 + "engines": {
1283 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1284 + },
1285 + "funding": {
1286 + "url": "https://opencollective.com/eslint"
1287 + },
1288 + "peerDependencies": {
1289 + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
1290 + }
1291 + },
1292 "node_modules/@eslint-community/eslint-utils": {
1293 "version": "4.4.0",
1294 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1155,14 +1303,34 @@
1303 "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
1304 }
1305 },
1306 + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
1307 + "version": "3.4.3",
1308 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
1309 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
1310 + "engines": {
1311 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1312 + },
1313 + "funding": {
1314 + "url": "https://opencollective.com/eslint"
1315 + }
1316 + },
1317 "node_modules/@eslint-community/regexpp": {
1159 - "version": "4.11.0",
1160 - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
1161 - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
1318 + "version": "4.11.1",
1319 + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
1320 + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
1321 "engines": {
1322 "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
1323 }
1324 },
1325 + "node_modules/@eslint/compat": {
1326 + "version": "1.1.1",
1327 + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.1.1.tgz",
1328 + "integrity": "sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==",
1329 + "dev": true,
1330 + "engines": {
1331 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1332 + }
1333 + },
1334 "node_modules/@eslint/config-array": {
1335 "version": "0.18.0",
1336 "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
@@ -1250,13 +1418,30 @@
1418 }
1419 },
1420 "node_modules/@eslint/js": {
1253 - "version": "8.57.0",
1254 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
1255 - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
1421 + "version": "8.57.1",
1422 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
1423 + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
1424 "engines": {
1425 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1426 }
1427 },
1428 + "node_modules/@eslint/markdown": {
1429 + "version": "6.1.0",
1430 + "resolved": "https://registry.npmjs.org/@eslint/markdown/-/markdown-6.1.0.tgz",
1431 + "integrity": "sha512-cX1tyD+aIbhzKrCKe/9M5s2jZhldWGOR+cy7cIVpxG9RkoaN4XU+gG3dy6oEKtBFXjDx06GtP0OGO7jgbqa2DA==",
1432 + "dev": true,
1433 + "dependencies": {
1434 + "mdast-util-from-markdown": "^2.0.1",
1435 + "mdast-util-gfm": "^3.0.0",
1436 + "micromark-extension-gfm": "^3.0.0"
1437 + },
1438 + "engines": {
1439 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1440 + },
1441 + "peerDependencies": {
1442 + "eslint": ">=9"
1443 + }
1444 + },
1445 "node_modules/@eslint/object-schema": {
1446 "version": "2.1.4",
1447 "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
@@ -1266,9 +1451,9 @@
1451 }
1452 },
1453 "node_modules/@eslint/plugin-kit": {
1269 - "version": "0.1.0",
1270 - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
1271 - "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
1454 + "version": "0.2.0",
1455 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
1456 + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
1457 "dependencies": {
1458 "levn": "^0.4.1"
1459 },
@@ -1367,6 +1552,22 @@
1552 }
1553 }
1554 },
1555 + "node_modules/@f3ve/eslint-config/node_modules/@typescript-eslint/scope-manager": {
1556 + "version": "6.21.0",
1557 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
1558 + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
1559 + "dependencies": {
1560 + "@typescript-eslint/types": "6.21.0",
1561 + "@typescript-eslint/visitor-keys": "6.21.0"
1562 + },
1563 + "engines": {
1564 + "node": "^16.0.0 || >=18.0.0"
1565 + },
1566 + "funding": {
1567 + "type": "opencollective",
1568 + "url": "https://opencollective.com/typescript-eslint"
1569 + }
1570 + },
1571 "node_modules/@f3ve/eslint-config/node_modules/@typescript-eslint/type-utils": {
1572 "version": "6.21.0",
1573 "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
@@ -1393,6 +1594,96 @@
1594 }
1595 }
1596 },
1597 + "node_modules/@f3ve/eslint-config/node_modules/@typescript-eslint/types": {
1598 + "version": "6.21.0",
1599 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
1600 + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
1601 + "engines": {
1602 + "node": "^16.0.0 || >=18.0.0"
1603 + },
1604 + "funding": {
1605 + "type": "opencollective",
1606 + "url": "https://opencollective.com/typescript-eslint"
1607 + }
1608 + },
1609 + "node_modules/@f3ve/eslint-config/node_modules/@typescript-eslint/typescript-estree": {
1610 + "version": "6.21.0",
1611 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
1612 + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
1613 + "dependencies": {
1614 + "@typescript-eslint/types": "6.21.0",
1615 + "@typescript-eslint/visitor-keys": "6.21.0",
1616 + "debug": "^4.3.4",
1617 + "globby": "^11.1.0",
1618 + "is-glob": "^4.0.3",
1619 + "minimatch": "9.0.3",
1620 + "semver": "^7.5.4",
1621 + "ts-api-utils": "^1.0.1"
1622 + },
1623 + "engines": {
1624 + "node": "^16.0.0 || >=18.0.0"
1625 + },
1626 + "funding": {
1627 + "type": "opencollective",
1628 + "url": "https://opencollective.com/typescript-eslint"
1629 + },
1630 + "peerDependenciesMeta": {
1631 + "typescript": {
1632 + "optional": true
1633 + }
1634 + }
1635 + },
1636 + "node_modules/@f3ve/eslint-config/node_modules/@typescript-eslint/utils": {
1637 + "version": "6.21.0",
1638 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
1639 + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
1640 + "dependencies": {
1641 + "@eslint-community/eslint-utils": "^4.4.0",
1642 + "@types/json-schema": "^7.0.12",
1643 + "@types/semver": "^7.5.0",
1644 + "@typescript-eslint/scope-manager": "6.21.0",
1645 + "@typescript-eslint/types": "6.21.0",
1646 + "@typescript-eslint/typescript-estree": "6.21.0",
1647 + "semver": "^7.5.4"
1648 + },
1649 + "engines": {
1650 + "node": "^16.0.0 || >=18.0.0"
1651 + },
1652 + "funding": {
1653 + "type": "opencollective",
1654 + "url": "https://opencollective.com/typescript-eslint"
1655 + },
1656 + "peerDependencies": {
1657 + "eslint": "^7.0.0 || ^8.0.0"
1658 + }
1659 + },
1660 + "node_modules/@f3ve/eslint-config/node_modules/@typescript-eslint/visitor-keys": {
1661 + "version": "6.21.0",
1662 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
1663 + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
1664 + "dependencies": {
1665 + "@typescript-eslint/types": "6.21.0",
1666 + "eslint-visitor-keys": "^3.4.1"
1667 + },
1668 + "engines": {
1669 + "node": "^16.0.0 || >=18.0.0"
1670 + },
1671 + "funding": {
1672 + "type": "opencollective",
1673 + "url": "https://opencollective.com/typescript-eslint"
1674 + }
1675 + },
1676 + "node_modules/@f3ve/eslint-config/node_modules/eslint-visitor-keys": {
1677 + "version": "3.4.3",
1678 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
1679 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
1680 + "engines": {
1681 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1682 + },
1683 + "funding": {
1684 + "url": "https://opencollective.com/eslint"
1685 + }
1686 + },
1687 "node_modules/@f3ve/eslint-config/node_modules/globals": {
1688 "version": "13.24.0",
1689 "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@@ -1407,6 +1698,47 @@
1698 "url": "https://github.com/sponsors/sindresorhus"
1699 }
1700 },
1701 + "node_modules/@f3ve/eslint-config/node_modules/globby": {
1702 + "version": "11.1.0",
1703 + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
1704 + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
1705 + "dependencies": {
1706 + "array-union": "^2.1.0",
1707 + "dir-glob": "^3.0.1",
1708 + "fast-glob": "^3.2.9",
1709 + "ignore": "^5.2.0",
1710 + "merge2": "^1.4.1",
1711 + "slash": "^3.0.0"
1712 + },
1713 + "engines": {
1714 + "node": ">=10"
1715 + },
1716 + "funding": {
1717 + "url": "https://github.com/sponsors/sindresorhus"
1718 + }
1719 + },
1720 + "node_modules/@f3ve/eslint-config/node_modules/minimatch": {
1721 + "version": "9.0.3",
1722 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
1723 + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
1724 + "dependencies": {
1725 + "brace-expansion": "^2.0.1"
1726 + },
1727 + "engines": {
1728 + "node": ">=16 || 14 >=14.17"
1729 + },
1730 + "funding": {
1731 + "url": "https://github.com/sponsors/isaacs"
1732 + }
1733 + },
1734 + "node_modules/@f3ve/eslint-config/node_modules/slash": {
1735 + "version": "3.0.0",
1736 + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
1737 + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
1738 + "engines": {
1739 + "node": ">=8"
1740 + }
1741 + },
1742 "node_modules/@f3ve/eslint-config/node_modules/type-fest": {
1743 "version": "0.20.2",
1744 "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -1460,18 +1792,62 @@
1792 "@hapi/hoek": "^9.0.0"
1793 }
1794 },
1463 - "node_modules/@humanwhocodes/module-importer": {
1464 - "version": "1.0.1",
1465 - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
1466 - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
1467 - "engines": {
1468 - "node": ">=12.22"
1795 + "node_modules/@humanwhocodes/config-array": {
1796 + "version": "0.13.0",
1797 + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
1798 + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
1799 + "deprecated": "Use @eslint/config-array instead",
1800 + "peer": true,
1801 + "dependencies": {
1802 + "@humanwhocodes/object-schema": "^2.0.3",
1803 + "debug": "^4.3.1",
1804 + "minimatch": "^3.0.5"
1805 },
1470 - "funding": {
1471 - "type": "github",
1806 + "engines": {
1807 + "node": ">=10.10.0"
1808 + }
1809 + },
1810 + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
1811 + "version": "1.1.11",
1812 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
1813 + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
1814 + "peer": true,
1815 + "dependencies": {
1816 + "balanced-match": "^1.0.0",
1817 + "concat-map": "0.0.1"
1818 + }
1819 + },
1820 + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
1821 + "version": "3.1.2",
1822 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
1823 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
1824 + "peer": true,
1825 + "dependencies": {
1826 + "brace-expansion": "^1.1.7"
1827 + },
1828 + "engines": {
1829 + "node": "*"
1830 + }
1831 + },
1832 + "node_modules/@humanwhocodes/module-importer": {
1833 + "version": "1.0.1",
1834 + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
1835 + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
1836 + "engines": {
1837 + "node": ">=12.22"
1838 + },
1839 + "funding": {
1840 + "type": "github",
1841 "url": "https://github.com/sponsors/nzakas"
1842 }
1843 },
1844 + "node_modules/@humanwhocodes/object-schema": {
1845 + "version": "2.0.3",
1846 + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
1847 + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
1848 + "deprecated": "Use @eslint/object-schema instead",
1849 + "peer": true
1850 + },
1851 "node_modules/@humanwhocodes/retry": {
1852 "version": "0.3.0",
1853 "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
@@ -1662,16 +2038,16 @@
2038 "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
2039 },
2040 "node_modules/@koa/router": {
1665 - "version": "12.0.1",
1666 - "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.1.tgz",
1667 - "integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==",
2041 + "version": "12.0.2",
2042 + "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.2.tgz",
2043 + "integrity": "sha512-sYcHglGKTxGF+hQ6x67xDfkE9o+NhVlRHBqq6gLywaMc6CojK/5vFZByphdonKinYlMLkEkacm+HEse9HzwgTA==",
2044 "dev": true,
2045 "dependencies": {
2046 "debug": "^4.3.4",
2047 "http-errors": "^2.0.0",
2048 "koa-compose": "^4.1.0",
2049 "methods": "^1.1.2",
1674 - "path-to-regexp": "^6.2.1"
2050 + "path-to-regexp": "^6.3.0"
2051 },
2052 "engines": {
2053 "node": ">= 12"
@@ -1710,11 +2086,11 @@
2086 }
2087 },
2088 "node_modules/@nuxt/kit": {
1713 - "version": "3.13.1",
1714 - "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.13.1.tgz",
1715 - "integrity": "sha512-FkUL349lp/3nVfTIyws4UDJ3d2jyv5Pk1DC1HQUCOkSloYYMdbRcQAUcb4fe2TCLNWvHM+FhU8jnzGTzjALZYA==",
2089 + "version": "3.13.2",
2090 + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.13.2.tgz",
2091 + "integrity": "sha512-KvRw21zU//wdz25IeE1E5m/aFSzhJloBRAQtv+evcFeZvuroIxpIQuUqhbzuwznaUwpiWbmwlcsp5uOWmi4vwA==",
2092 "dependencies": {
1717 - "@nuxt/schema": "3.13.1",
2093 + "@nuxt/schema": "3.13.2",
2094 "c12": "^1.11.2",
2095 "consola": "^3.2.3",
2096 "defu": "^6.1.4",
@@ -1732,58 +2108,17 @@
2108 "semver": "^7.6.3",
2109 "ufo": "^1.5.4",
2110 "unctx": "^2.3.1",
1735 - "unimport": "^3.11.1",
2111 + "unimport": "^3.12.0",
2112 "untyped": "^1.4.2"
2113 },
2114 "engines": {
2115 "node": "^14.18.0 || >=16.10.0"
2116 }
2117 },
1742 - "node_modules/@nuxt/kit/node_modules/globby": {
1743 - "version": "14.0.2",
1744 - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz",
1745 - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==",
1746 - "dependencies": {
1747 - "@sindresorhus/merge-streams": "^2.1.0",
1748 - "fast-glob": "^3.3.2",
1749 - "ignore": "^5.2.4",
1750 - "path-type": "^5.0.0",
1751 - "slash": "^5.1.0",
1752 - "unicorn-magic": "^0.1.0"
1753 - },
1754 - "engines": {
1755 - "node": ">=18"
1756 - },
1757 - "funding": {
1758 - "url": "https://github.com/sponsors/sindresorhus"
1759 - }
1760 - },
1761 - "node_modules/@nuxt/kit/node_modules/path-type": {
1762 - "version": "5.0.0",
1763 - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
1764 - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
1765 - "engines": {
1766 - "node": ">=12"
1767 - },
1768 - "funding": {
1769 - "url": "https://github.com/sponsors/sindresorhus"
1770 - }
1771 - },
1772 - "node_modules/@nuxt/kit/node_modules/slash": {
1773 - "version": "5.1.0",
1774 - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
1775 - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
1776 - "engines": {
1777 - "node": ">=14.16"
1778 - },
1779 - "funding": {
1780 - "url": "https://github.com/sponsors/sindresorhus"
1781 - }
1782 - },
2118 "node_modules/@nuxt/schema": {
1784 - "version": "3.13.1",
1785 - "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.13.1.tgz",
1786 - "integrity": "sha512-ishbhzVGspjshG9AG0hYnKYY6LWXzCtua7OXV7C/DQ2yA7rRcy1xHpzKZUDbIRyxCHHCAcBd8jfHEUmEuhEPrA==",
2119 + "version": "3.13.2",
2120 + "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.13.2.tgz",
2121 + "integrity": "sha512-CCZgpm+MkqtOMDEgF9SWgGPBXlQ01hV/6+2reDEpJuqFPGzV8HYKPBcIFvn7/z5ahtgutHLzjP71Na+hYcqSpw==",
2122 "dependencies": {
2123 "compatx": "^0.1.8",
2124 "consola": "^3.2.3",
@@ -1795,7 +2130,7 @@
2130 "std-env": "^3.7.0",
2131 "ufo": "^1.5.4",
2132 "uncrypto": "^0.1.3",
1798 - "unimport": "^3.11.1",
2133 + "unimport": "^3.12.0",
2134 "untyped": "^1.4.2"
2135 },
2136 "engines": {
@@ -1830,9 +2165,9 @@
2165 }
2166 },
2167 "node_modules/@polka/url": {
1833 - "version": "1.0.0-next.25",
1834 - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
1835 - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==",
2168 + "version": "1.0.0-next.28",
2169 + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
2170 + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
2171 "dev": true
2172 },
2173 "node_modules/@popperjs/core": {
@@ -1865,10 +2200,21 @@
2200 }
2201 }
2202 },
2203 + "node_modules/@rollup/pluginutils/node_modules/picomatch": {
2204 + "version": "2.3.1",
2205 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
2206 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
2207 + "engines": {
2208 + "node": ">=8.6"
2209 + },
2210 + "funding": {
2211 + "url": "https://github.com/sponsors/jonschlinkert"
2212 + }
2213 + },
2214 "node_modules/@rollup/rollup-android-arm-eabi": {
1869 - "version": "4.21.3",
1870 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz",
1871 - "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==",
2215 + "version": "4.22.4",
2216 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
2217 + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
2218 "cpu": [
2219 "arm"
2220 ],
@@ -1879,9 +2225,9 @@
2225 ]
2226 },
2227 "node_modules/@rollup/rollup-android-arm64": {
1882 - "version": "4.21.3",
1883 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz",
1884 - "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==",
2228 + "version": "4.22.4",
2229 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
2230 + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
2231 "cpu": [
2232 "arm64"
2233 ],
@@ -1892,9 +2238,9 @@
2238 ]
2239 },
2240 "node_modules/@rollup/rollup-darwin-arm64": {
1895 - "version": "4.21.3",
1896 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz",
1897 - "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==",
2241 + "version": "4.22.4",
2242 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
2243 + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
2244 "cpu": [
2245 "arm64"
2246 ],
@@ -1905,9 +2251,9 @@
2251 ]
2252 },
2253 "node_modules/@rollup/rollup-darwin-x64": {
1908 - "version": "4.21.3",
1909 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz",
1910 - "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==",
2254 + "version": "4.22.4",
2255 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
2256 + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
2257 "cpu": [
2258 "x64"
2259 ],
@@ -1918,9 +2264,9 @@
2264 ]
2265 },
2266 "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
1921 - "version": "4.21.3",
1922 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz",
1923 - "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==",
2267 + "version": "4.22.4",
2268 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
2269 + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
2270 "cpu": [
2271 "arm"
2272 ],
@@ -1931,9 +2277,9 @@
2277 ]
2278 },
2279 "node_modules/@rollup/rollup-linux-arm-musleabihf": {
1934 - "version": "4.21.3",
1935 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz",
1936 - "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==",
2280 + "version": "4.22.4",
2281 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
2282 + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
2283 "cpu": [
2284 "arm"
2285 ],
@@ -1944,9 +2290,9 @@
2290 ]
2291 },
2292 "node_modules/@rollup/rollup-linux-arm64-gnu": {
1947 - "version": "4.21.3",
1948 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz",
1949 - "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==",
2293 + "version": "4.22.4",
2294 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
2295 + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
2296 "cpu": [
2297 "arm64"
2298 ],
@@ -1957,9 +2303,9 @@
2303 ]
2304 },
2305 "node_modules/@rollup/rollup-linux-arm64-musl": {
1960 - "version": "4.21.3",
1961 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz",
1962 - "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==",
2306 + "version": "4.22.4",
2307 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
2308 + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
2309 "cpu": [
2310 "arm64"
2311 ],
@@ -1970,9 +2316,9 @@
2316 ]
2317 },
2318 "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
1973 - "version": "4.21.3",
1974 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz",
1975 - "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==",
2319 + "version": "4.22.4",
2320 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
2321 + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
2322 "cpu": [
2323 "ppc64"
2324 ],
@@ -1983,9 +2329,9 @@
2329 ]
2330 },
2331 "node_modules/@rollup/rollup-linux-riscv64-gnu": {
1986 - "version": "4.21.3",
1987 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz",
1988 - "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==",
2332 + "version": "4.22.4",
2333 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
2334 + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
2335 "cpu": [
2336 "riscv64"
2337 ],
@@ -1996,9 +2342,9 @@
2342 ]
2343 },
2344 "node_modules/@rollup/rollup-linux-s390x-gnu": {
1999 - "version": "4.21.3",
2000 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz",
2001 - "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==",
2345 + "version": "4.22.4",
2346 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
2347 + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
2348 "cpu": [
2349 "s390x"
2350 ],
@@ -2009,9 +2355,9 @@
2355 ]
2356 },
2357 "node_modules/@rollup/rollup-linux-x64-gnu": {
2012 - "version": "4.21.3",
2013 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz",
2014 - "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==",
2358 + "version": "4.22.4",
2359 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
2360 + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
2361 "cpu": [
2362 "x64"
2363 ],
@@ -2021,9 +2367,9 @@
2367 ]
2368 },
2369 "node_modules/@rollup/rollup-linux-x64-musl": {
2024 - "version": "4.21.3",
2025 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz",
2026 - "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==",
2370 + "version": "4.22.4",
2371 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
2372 + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
2373 "cpu": [
2374 "x64"
2375 ],
@@ -2034,9 +2380,9 @@
2380 ]
2381 },
2382 "node_modules/@rollup/rollup-win32-arm64-msvc": {
2037 - "version": "4.21.3",
2038 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz",
2039 - "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==",
2383 + "version": "4.22.4",
2384 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
2385 + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
2386 "cpu": [
2387 "arm64"
2388 ],
@@ -2047,9 +2393,9 @@
2393 ]
2394 },
2395 "node_modules/@rollup/rollup-win32-ia32-msvc": {
2050 - "version": "4.21.3",
2051 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz",
2052 - "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==",
2396 + "version": "4.22.4",
2397 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
2398 + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
2399 "cpu": [
2400 "ia32"
2401 ],
@@ -2060,9 +2406,9 @@
2406 ]
2407 },
2408 "node_modules/@rollup/rollup-win32-x64-msvc": {
2063 - "version": "4.21.3",
2064 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz",
2065 - "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==",
2409 + "version": "4.22.4",
2410 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
2411 + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
2412 "cpu": [
2413 "x64"
2414 ],
@@ -2073,58 +2419,50 @@
2419 ]
2420 },
2421 "node_modules/@shikijs/core": {
2076 - "version": "1.17.6",
2077 - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.17.6.tgz",
2078 - "integrity": "sha512-9ztslig6/YmCg/XwESAXbKjAjOhaq6HVced9NY6qcbDz1X5g/S90Wco2vMjBNX/6V71ASkzri76JewSGPa7kiQ==",
2422 + "version": "1.18.0",
2423 + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.18.0.tgz",
2424 + "integrity": "sha512-VK4BNVCd2leY62Nm2JjyxtRLkyrZT/tv104O81eyaCjHq4Adceq2uJVFJJAIof6lT1mBwZrEo2qT/T+grv3MQQ==",
2425 "dependencies": {
2080 - "@shikijs/engine-javascript": "1.17.6",
2081 - "@shikijs/engine-oniguruma": "1.17.6",
2082 - "@shikijs/types": "1.17.6",
2426 + "@shikijs/engine-javascript": "1.18.0",
2427 + "@shikijs/engine-oniguruma": "1.18.0",
2428 + "@shikijs/types": "1.18.0",
2429 "@shikijs/vscode-textmate": "^9.2.2",
2430 "@types/hast": "^3.0.4",
2085 - "hast-util-to-html": "^9.0.2"
2431 + "hast-util-to-html": "^9.0.3"
2432 }
2433 },
2434 "node_modules/@shikijs/engine-javascript": {
2089 - "version": "1.17.6",
2090 - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.17.6.tgz",
2091 - "integrity": "sha512-5EEZj8tVcierNxm4V0UMS2PVoflb0UJPalWWV8l9rRg+oOfnr5VivqBJbkyq5grltVPvByIXvVbY8GSM/356jQ==",
2435 + "version": "1.18.0",
2436 + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.18.0.tgz",
2437 + "integrity": "sha512-qoP/aO/ATNwYAUw1YMdaip/YVEstMZEgrwhePm83Ll9OeQPuxDZd48szZR8oSQNQBT8m8UlWxZv8EA3lFuyI5A==",
2438 "dependencies": {
2093 - "@shikijs/types": "1.17.6",
2439 + "@shikijs/types": "1.18.0",
2440 + "@shikijs/vscode-textmate": "^9.2.2",
2441 "oniguruma-to-js": "0.4.3"
2442 }
2443 },
2444 "node_modules/@shikijs/engine-oniguruma": {
2098 - "version": "1.17.6",
2099 - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.17.6.tgz",
2100 - "integrity": "sha512-NLfWDMXFYe0nDHFbEoyZdz89aIIey3bTfF3zLYSUNTXks5s4uinZVmuPOFf1HfTeGqIn8uErJSBc3VnpJO7Alw==",
2445 + "version": "1.18.0",
2446 + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.18.0.tgz",
2447 + "integrity": "sha512-B9u0ZKI/cud+TcmF8Chyh+R4V5qQVvyDOqXC2l2a4x73PBSBc6sZ0JRAX3eqyJswqir6ktwApUUGBYePdKnMJg==",
2448 "dependencies": {
2102 - "@shikijs/types": "1.17.6",
2449 + "@shikijs/types": "1.18.0",
2450 "@shikijs/vscode-textmate": "^9.2.2"
2451 }
2452 },
2453 "node_modules/@shikijs/markdown-it": {
2107 - "version": "1.17.6",
2108 - "resolved": "https://registry.npmjs.org/@shikijs/markdown-it/-/markdown-it-1.17.6.tgz",
2109 - "integrity": "sha512-2nPKUcnny7rYvmz8S9Qe5rGRi3ycpLsmLmtV8HoK7t54JtqS65KIOwI+VUbRfKfUzc9CqUJzD432k9mY3BzGUg==",
2454 + "version": "1.18.0",
2455 + "resolved": "https://registry.npmjs.org/@shikijs/markdown-it/-/markdown-it-1.18.0.tgz",
2456 + "integrity": "sha512-acU44AAYYk7cESJbpEmcmLP5KJzuIUBzuCHcVd4glJPRK1UYc7Pqfg/chKv0DFYsC0u5BdFT15Od959tPA3xJQ==",
2457 "dependencies": {
2111 - "@shikijs/transformers": "1.17.6",
2458 "markdown-it": "^14.1.0",
2113 - "shiki": "1.17.6"
2114 - }
2115 - },
2116 - "node_modules/@shikijs/transformers": {
2117 - "version": "1.17.6",
2118 - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.17.6.tgz",
2119 - "integrity": "sha512-k2VAfbm19bF0ofPaEdWSQJrzT47ukCQcajWQsiGMwcH9DFBFFpFHzWTuwagmxwywAClCZiBkhP6U3uxngbJgRw==",
2120 - "dependencies": {
2121 - "shiki": "1.17.6"
2459 + "shiki": "1.18.0"
2460 }
2461 },
2462 "node_modules/@shikijs/types": {
2125 - "version": "1.17.6",
2126 - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.17.6.tgz",
2127 - "integrity": "sha512-ndTFa2TJi2w51ddKQDn3Jy8f6K4E5Q2x3dA3Hmsd3+YmxDQ10UWHjcw7VbVbKzv3VcUvYPLy+z9neqytSzUMUg==",
2463 + "version": "1.18.0",
2464 + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.18.0.tgz",
2465 + "integrity": "sha512-O9N36UEaGGrxv1yUrN2nye7gDLG5Uq0/c1LyfmxsvzNPqlHzWo9DI0A4+fhW2y3bGKuQu/fwS7EPdKJJCowcVA==",
2466 "dependencies": {
2467 "@shikijs/vscode-textmate": "^9.2.2",
2468 "@types/hast": "^3.0.4"
@@ -2167,6 +2505,25 @@
2505 "url": "https://github.com/sponsors/sindresorhus"
2506 }
2507 },
2508 + "node_modules/@stylistic/eslint-plugin": {
2509 + "version": "2.8.0",
2510 + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.8.0.tgz",
2511 + "integrity": "sha512-Ufvk7hP+bf+pD35R/QfunF793XlSRIC7USr3/EdgduK9j13i2JjmsM0LUz3/foS+jDYp2fzyWZA9N44CPur0Ow==",
2512 + "dev": true,
2513 + "dependencies": {
2514 + "@typescript-eslint/utils": "^8.4.0",
2515 + "eslint-visitor-keys": "^4.0.0",
2516 + "espree": "^10.1.0",
2517 + "estraverse": "^5.3.0",
2518 + "picomatch": "^4.0.2"
2519 + },
2520 + "engines": {
2521 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2522 + },
2523 + "peerDependencies": {
2524 + "eslint": ">=8.40.0"
2525 + }
2526 + },
2527 "node_modules/@trysound/sax": {
2528 "version": "0.2.0",
2529 "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -2188,10 +2545,19 @@
2545 "integrity": "sha512-A0uYgOj3zNc4hNjHc5lYUfJQ/HVyBXiUMKdXd7ysclaE6k9oJdavQzODHuwjpUu2/boCP8afjQYi8z/GtvNCWA==",
2546 "dev": true
2547 },
2548 + "node_modules/@types/debug": {
2549 + "version": "4.1.12",
2550 + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
2551 + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
2552 + "dev": true,
2553 + "dependencies": {
2554 + "@types/ms": "*"
2555 + }
2556 + },
2557 "node_modules/@types/estree": {
2192 - "version": "1.0.5",
2193 - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
2194 - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
2558 + "version": "1.0.6",
2559 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
2560 + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
2561 },
2562 "node_modules/@types/file-saver": {
2563 "version": "2.0.7",
@@ -2278,15 +2644,27 @@
2644 "@types/unist": "*"
2645 }
2646 },
2647 + "node_modules/@types/ms": {
2648 + "version": "0.7.34",
2649 + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
2650 + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
2651 + "dev": true
2652 + },
2653 "node_modules/@types/node": {
2282 - "version": "22.5.4",
2283 - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
2284 - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
2654 + "version": "22.5.5",
2655 + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz",
2656 + "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==",
2657 "dev": true,
2658 "dependencies": {
2659 "undici-types": "~6.19.2"
2660 }
2661 },
2662 + "node_modules/@types/normalize-package-data": {
2663 + "version": "2.4.4",
2664 + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
2665 + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
2666 + "dev": true
2667 + },
2668 "node_modules/@types/semver": {
2669 "version": "7.5.8",
2670 "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@@ -2325,9 +2703,9 @@
2703 "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
2704 },
2705 "node_modules/@types/validator": {
2328 - "version": "13.12.1",
2329 - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.1.tgz",
2330 - "integrity": "sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA==",
2706 + "version": "13.12.2",
2707 + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
2708 + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
2709 "dev": true
2710 },
2711 "node_modules/@types/web-bluetooth": {
@@ -2346,31 +2724,31 @@
2724 }
2725 },
2726 "node_modules/@typescript-eslint/eslint-plugin": {
2349 - "version": "7.18.0",
2350 - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
2351 - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
2727 + "version": "8.6.0",
2728 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
2729 + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
2730 "dev": true,
2731 "dependencies": {
2732 "@eslint-community/regexpp": "^4.10.0",
2355 - "@typescript-eslint/scope-manager": "7.18.0",
2356 - "@typescript-eslint/type-utils": "7.18.0",
2357 - "@typescript-eslint/utils": "7.18.0",
2358 - "@typescript-eslint/visitor-keys": "7.18.0",
2733 + "@typescript-eslint/scope-manager": "8.6.0",
2734 + "@typescript-eslint/type-utils": "8.6.0",
2735 + "@typescript-eslint/utils": "8.6.0",
2736 + "@typescript-eslint/visitor-keys": "8.6.0",
2737 "graphemer": "^1.4.0",
2738 "ignore": "^5.3.1",
2739 "natural-compare": "^1.4.0",
2740 "ts-api-utils": "^1.3.0"
2741 },
2742 "engines": {
2365 - "node": "^18.18.0 || >=20.0.0"
2743 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2744 },
2745 "funding": {
2746 "type": "opencollective",
2747 "url": "https://opencollective.com/typescript-eslint"
2748 },
2749 "peerDependencies": {
2372 - "@typescript-eslint/parser": "^7.0.0",
2373 - "eslint": "^8.56.0"
2750 + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
2751 + "eslint": "^8.57.0 || ^9.0.0"
2752 },
2753 "peerDependenciesMeta": {
2754 "typescript": {
@@ -2378,53 +2756,64 @@
2756 }
2757 }
2758 },
2381 - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
2382 - "version": "7.18.0",
2383 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
2384 - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
2759 + "node_modules/@typescript-eslint/parser": {
2760 + "version": "8.6.0",
2761 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
2762 + "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
2763 "dev": true,
2764 "dependencies": {
2387 - "@typescript-eslint/types": "7.18.0",
2388 - "@typescript-eslint/visitor-keys": "7.18.0"
2765 + "@typescript-eslint/scope-manager": "8.6.0",
2766 + "@typescript-eslint/types": "8.6.0",
2767 + "@typescript-eslint/typescript-estree": "8.6.0",
2768 + "@typescript-eslint/visitor-keys": "8.6.0",
2769 + "debug": "^4.3.4"
2770 },
2771 "engines": {
2391 - "node": "^18.18.0 || >=20.0.0"
2772 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2773 },
2774 "funding": {
2775 "type": "opencollective",
2776 "url": "https://opencollective.com/typescript-eslint"
2777 + },
2778 + "peerDependencies": {
2779 + "eslint": "^8.57.0 || ^9.0.0"
2780 + },
2781 + "peerDependenciesMeta": {
2782 + "typescript": {
2783 + "optional": true
2784 + }
2785 }
2786 },
2398 - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
2399 - "version": "7.18.0",
2400 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
2401 - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
2787 + "node_modules/@typescript-eslint/scope-manager": {
2788 + "version": "8.6.0",
2789 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
2790 + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
2791 "dev": true,
2792 + "dependencies": {
2793 + "@typescript-eslint/types": "8.6.0",
2794 + "@typescript-eslint/visitor-keys": "8.6.0"
2795 + },
2796 "engines": {
2404 - "node": "^18.18.0 || >=20.0.0"
2797 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2798 },
2799 "funding": {
2800 "type": "opencollective",
2801 "url": "https://opencollective.com/typescript-eslint"
2802 }
2803 },
2411 - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
2412 - "version": "7.18.0",
2413 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
2414 - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
2804 + "node_modules/@typescript-eslint/type-utils": {
2805 + "version": "8.6.0",
2806 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
2807 + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
2808 "dev": true,
2809 "dependencies": {
2417 - "@typescript-eslint/types": "7.18.0",
2418 - "@typescript-eslint/visitor-keys": "7.18.0",
2810 + "@typescript-eslint/typescript-estree": "8.6.0",
2811 + "@typescript-eslint/utils": "8.6.0",
2812 "debug": "^4.3.4",
2420 - "globby": "^11.1.0",
2421 - "is-glob": "^4.0.3",
2422 - "minimatch": "^9.0.4",
2423 - "semver": "^7.6.0",
2813 "ts-api-utils": "^1.3.0"
2814 },
2815 "engines": {
2427 - "node": "^18.18.0 || >=20.0.0"
2816 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2817 },
2818 "funding": {
2819 "type": "opencollective",
@@ -2436,334 +2825,220 @@
2825 }
2826 }
2827 },
2439 - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
2440 - "version": "7.18.0",
2441 - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
2442 - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
2828 + "node_modules/@typescript-eslint/types": {
2829 + "version": "8.6.0",
2830 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
2831 + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
2832 "dev": true,
2444 - "dependencies": {
2445 - "@eslint-community/eslint-utils": "^4.4.0",
2446 - "@typescript-eslint/scope-manager": "7.18.0",
2447 - "@typescript-eslint/types": "7.18.0",
2448 - "@typescript-eslint/typescript-estree": "7.18.0"
2449 - },
2833 "engines": {
2451 - "node": "^18.18.0 || >=20.0.0"
2834 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2835 },
2836 "funding": {
2837 "type": "opencollective",
2838 "url": "https://opencollective.com/typescript-eslint"
2456 - },
2457 - "peerDependencies": {
2458 - "eslint": "^8.56.0"
2839 }
2840 },
2461 - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
2462 - "version": "7.18.0",
2463 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
2464 - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
2841 + "node_modules/@typescript-eslint/typescript-estree": {
2842 + "version": "8.6.0",
2843 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
2844 + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
2845 "dev": true,
2846 "dependencies": {
2467 - "@typescript-eslint/types": "7.18.0",
2468 - "eslint-visitor-keys": "^3.4.3"
2847 + "@typescript-eslint/types": "8.6.0",
2848 + "@typescript-eslint/visitor-keys": "8.6.0",
2849 + "debug": "^4.3.4",
2850 + "fast-glob": "^3.3.2",
2851 + "is-glob": "^4.0.3",
2852 + "minimatch": "^9.0.4",
2853 + "semver": "^7.6.0",
2854 + "ts-api-utils": "^1.3.0"
2855 },
2856 "engines": {
2471 - "node": "^18.18.0 || >=20.0.0"
2857 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2858 },
2859 "funding": {
2860 "type": "opencollective",
2861 "url": "https://opencollective.com/typescript-eslint"
2476 - }
2477 - },
2478 - "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": {
2479 - "version": "9.0.5",
2480 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2481 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2482 - "dev": true,
2483 - "dependencies": {
2484 - "brace-expansion": "^2.0.1"
2485 - },
2486 - "engines": {
2487 - "node": ">=16 || 14 >=14.17"
2862 },
2489 - "funding": {
2490 - "url": "https://github.com/sponsors/isaacs"
2863 + "peerDependenciesMeta": {
2864 + "typescript": {
2865 + "optional": true
2866 + }
2867 }
2868 },
2493 - "node_modules/@typescript-eslint/parser": {
2494 - "version": "7.18.0",
2495 - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
2496 - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
2869 + "node_modules/@typescript-eslint/utils": {
2870 + "version": "8.6.0",
2871 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
2872 + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
2873 "dev": true,
2874 "dependencies": {
2499 - "@typescript-eslint/scope-manager": "7.18.0",
2500 - "@typescript-eslint/types": "7.18.0",
2501 - "@typescript-eslint/typescript-estree": "7.18.0",
2502 - "@typescript-eslint/visitor-keys": "7.18.0",
2503 - "debug": "^4.3.4"
2875 + "@eslint-community/eslint-utils": "^4.4.0",
2876 + "@typescript-eslint/scope-manager": "8.6.0",
2877 + "@typescript-eslint/types": "8.6.0",
2878 + "@typescript-eslint/typescript-estree": "8.6.0"
2879 },
2880 "engines": {
2506 - "node": "^18.18.0 || >=20.0.0"
2881 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2882 },
2883 "funding": {
2884 "type": "opencollective",
2885 "url": "https://opencollective.com/typescript-eslint"
2886 },
2887 "peerDependencies": {
2513 - "eslint": "^8.56.0"
2514 - },
2515 - "peerDependenciesMeta": {
2516 - "typescript": {
2517 - "optional": true
2518 - }
2888 + "eslint": "^8.57.0 || ^9.0.0"
2889 }
2890 },
2521 - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
2522 - "version": "7.18.0",
2523 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
2524 - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
2891 + "node_modules/@typescript-eslint/visitor-keys": {
2892 + "version": "8.6.0",
2893 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
2894 + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
2895 "dev": true,
2896 "dependencies": {
2527 - "@typescript-eslint/types": "7.18.0",
2528 - "@typescript-eslint/visitor-keys": "7.18.0"
2897 + "@typescript-eslint/types": "8.6.0",
2898 + "eslint-visitor-keys": "^3.4.3"
2899 },
2900 "engines": {
2531 - "node": "^18.18.0 || >=20.0.0"
2901 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2902 },
2903 "funding": {
2904 "type": "opencollective",
2905 "url": "https://opencollective.com/typescript-eslint"
2906 }
2907 },
2538 - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
2539 - "version": "7.18.0",
2540 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
2541 - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
2908 + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
2909 + "version": "3.4.3",
2910 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
2911 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
2912 "dev": true,
2913 "engines": {
2544 - "node": "^18.18.0 || >=20.0.0"
2914 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2915 },
2916 "funding": {
2547 - "type": "opencollective",
2548 - "url": "https://opencollective.com/typescript-eslint"
2917 + "url": "https://opencollective.com/eslint"
2918 }
2919 },
2551 - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
2552 - "version": "7.18.0",
2553 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
2554 - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
2555 - "dev": true,
2920 + "node_modules/@ungap/structured-clone": {
2921 + "version": "1.2.0",
2922 + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
2923 + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
2924 + },
2925 + "node_modules/@unocss/config": {
2926 + "version": "0.56.5",
2927 + "resolved": "https://registry.npmjs.org/@unocss/config/-/config-0.56.5.tgz",
2928 + "integrity": "sha512-rscnFIYgUlN/0hXHdhANyjFcDjDutt3JO0ZRITdNLzoglh7GVNiDTURBJwUZejF/vGJ7IkMd3qOdNhPFuRY1Bg==",
2929 "dependencies": {
2557 - "@typescript-eslint/types": "7.18.0",
2558 - "@typescript-eslint/visitor-keys": "7.18.0",
2559 - "debug": "^4.3.4",
2560 - "globby": "^11.1.0",
2561 - "is-glob": "^4.0.3",
2562 - "minimatch": "^9.0.4",
2563 - "semver": "^7.6.0",
2564 - "ts-api-utils": "^1.3.0"
2930 + "@unocss/core": "0.56.5",
2931 + "unconfig": "^0.3.10"
2932 },
2933 "engines": {
2567 - "node": "^18.18.0 || >=20.0.0"
2934 + "node": ">=14"
2935 },
2936 "funding": {
2570 - "type": "opencollective",
2571 - "url": "https://opencollective.com/typescript-eslint"
2572 - },
2573 - "peerDependenciesMeta": {
2574 - "typescript": {
2575 - "optional": true
2576 - }
2937 + "url": "https://github.com/sponsors/antfu"
2938 }
2939 },
2579 - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
2580 - "version": "7.18.0",
2581 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
2582 - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
2583 - "dev": true,
2940 + "node_modules/@unocss/core": {
2941 + "version": "0.56.5",
2942 + "resolved": "https://registry.npmjs.org/@unocss/core/-/core-0.56.5.tgz",
2943 + "integrity": "sha512-fx5VhOjSHn0HdV2D34pEwFMAHJcJQRTCp1xEE4GzxY1irXzaa+m2aYf5PZjmDxehiOC16IH7TO9FOWANXk1E0w==",
2944 + "funding": {
2945 + "url": "https://github.com/sponsors/antfu"
2946 + }
2947 + },
2948 + "node_modules/@unocss/eslint-config": {
2949 + "version": "0.56.5",
2950 + "resolved": "https://registry.npmjs.org/@unocss/eslint-config/-/eslint-config-0.56.5.tgz",
2951 + "integrity": "sha512-UP7EBTl4ORRO0yqptW7oUWxM0qNh1Nk+z4rufC0C2kI8vKc8yab+sWvlBSC8DeX3kzR7uJxvtUsHGcBcoqZujg==",
2952 "dependencies": {
2585 - "@typescript-eslint/types": "7.18.0",
2586 - "eslint-visitor-keys": "^3.4.3"
2953 + "@unocss/eslint-plugin": "0.56.5"
2954 },
2955 "engines": {
2589 - "node": "^18.18.0 || >=20.0.0"
2956 + "node": ">=14"
2957 },
2958 "funding": {
2592 - "type": "opencollective",
2593 - "url": "https://opencollective.com/typescript-eslint"
2959 + "url": "https://github.com/sponsors/antfu"
2960 }
2961 },
2596 - "node_modules/@typescript-eslint/parser/node_modules/minimatch": {
2597 - "version": "9.0.5",
2598 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2599 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2600 - "dev": true,
2962 + "node_modules/@unocss/eslint-plugin": {
2963 + "version": "0.56.5",
2964 + "resolved": "https://registry.npmjs.org/@unocss/eslint-plugin/-/eslint-plugin-0.56.5.tgz",
2965 + "integrity": "sha512-nMVw/kc0sYU5i8UBU1rEahzKhZRPTUjCztpPzo2KUKFKLBPAbTv0gJAOmU/n2kV3YiGk5Tl6jmShxu1MHjk5rA==",
2966 "dependencies": {
2602 - "brace-expansion": "^2.0.1"
2967 + "@typescript-eslint/utils": "^6.7.3",
2968 + "@unocss/config": "0.56.5",
2969 + "@unocss/core": "0.56.5",
2970 + "magic-string": "^0.30.3",
2971 + "synckit": "^0.8.5"
2972 },
2973 "engines": {
2605 - "node": ">=16 || 14 >=14.17"
2974 + "node": ">=14"
2975 },
2976 "funding": {
2608 - "url": "https://github.com/sponsors/isaacs"
2977 + "url": "https://github.com/sponsors/antfu"
2978 }
2979 },
2611 - "node_modules/@typescript-eslint/scope-manager": {
2612 - "version": "6.21.0",
2613 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
2614 - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
2980 + "node_modules/@unocss/eslint-plugin/node_modules/@eslint/eslintrc": {
2981 + "version": "2.1.4",
2982 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
2983 + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
2984 + "peer": true,
2985 "dependencies": {
2616 - "@typescript-eslint/types": "6.21.0",
2617 - "@typescript-eslint/visitor-keys": "6.21.0"
2618 - },
2619 - "engines": {
2620 - "node": "^16.0.0 || >=18.0.0"
2621 - },
2622 - "funding": {
2623 - "type": "opencollective",
2624 - "url": "https://opencollective.com/typescript-eslint"
2625 - }
2626 - },
2627 - "node_modules/@typescript-eslint/type-utils": {
2628 - "version": "7.18.0",
2629 - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
2630 - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
2631 - "dev": true,
2632 - "dependencies": {
2633 - "@typescript-eslint/typescript-estree": "7.18.0",
2634 - "@typescript-eslint/utils": "7.18.0",
2635 - "debug": "^4.3.4",
2636 - "ts-api-utils": "^1.3.0"
2637 - },
2638 - "engines": {
2639 - "node": "^18.18.0 || >=20.0.0"
2640 - },
2641 - "funding": {
2642 - "type": "opencollective",
2643 - "url": "https://opencollective.com/typescript-eslint"
2644 - },
2645 - "peerDependencies": {
2646 - "eslint": "^8.56.0"
2647 - },
2648 - "peerDependenciesMeta": {
2649 - "typescript": {
2650 - "optional": true
2651 - }
2652 - }
2653 - },
2654 - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
2655 - "version": "7.18.0",
2656 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
2657 - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
2658 - "dev": true,
2659 - "dependencies": {
2660 - "@typescript-eslint/types": "7.18.0",
2661 - "@typescript-eslint/visitor-keys": "7.18.0"
2662 - },
2663 - "engines": {
2664 - "node": "^18.18.0 || >=20.0.0"
2986 + "ajv": "^6.12.4",
2987 + "debug": "^4.3.2",
2988 + "espree": "^9.6.0",
2989 + "globals": "^13.19.0",
2990 + "ignore": "^5.2.0",
2991 + "import-fresh": "^3.2.1",
2992 + "js-yaml": "^4.1.0",
2993 + "minimatch": "^3.1.2",
2994 + "strip-json-comments": "^3.1.1"
2995 },
2666 - "funding": {
2667 - "type": "opencollective",
2668 - "url": "https://opencollective.com/typescript-eslint"
2669 - }
2670 - },
2671 - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
2672 - "version": "7.18.0",
2673 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
2674 - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
2675 - "dev": true,
2996 "engines": {
2677 - "node": "^18.18.0 || >=20.0.0"
2997 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2998 },
2999 "funding": {
2680 - "type": "opencollective",
2681 - "url": "https://opencollective.com/typescript-eslint"
3000 + "url": "https://opencollective.com/eslint"
3001 }
3002 },
2684 - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
2685 - "version": "7.18.0",
2686 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
2687 - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
2688 - "dev": true,
3003 + "node_modules/@unocss/eslint-plugin/node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
3004 + "version": "1.1.11",
3005 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
3006 + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
3007 + "peer": true,
3008 "dependencies": {
2690 - "@typescript-eslint/types": "7.18.0",
2691 - "@typescript-eslint/visitor-keys": "7.18.0",
2692 - "debug": "^4.3.4",
2693 - "globby": "^11.1.0",
2694 - "is-glob": "^4.0.3",
2695 - "minimatch": "^9.0.4",
2696 - "semver": "^7.6.0",
2697 - "ts-api-utils": "^1.3.0"
2698 - },
2699 - "engines": {
2700 - "node": "^18.18.0 || >=20.0.0"
2701 - },
2702 - "funding": {
2703 - "type": "opencollective",
2704 - "url": "https://opencollective.com/typescript-eslint"
2705 - },
2706 - "peerDependenciesMeta": {
2707 - "typescript": {
2708 - "optional": true
2709 - }
3009 + "balanced-match": "^1.0.0",
3010 + "concat-map": "0.0.1"
3011 }
3012 },
2712 - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
2713 - "version": "7.18.0",
2714 - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
2715 - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
2716 - "dev": true,
3013 + "node_modules/@unocss/eslint-plugin/node_modules/@eslint/eslintrc/node_modules/minimatch": {
3014 + "version": "3.1.2",
3015 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
3016 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
3017 + "peer": true,
3018 "dependencies": {
2718 - "@eslint-community/eslint-utils": "^4.4.0",
2719 - "@typescript-eslint/scope-manager": "7.18.0",
2720 - "@typescript-eslint/types": "7.18.0",
2721 - "@typescript-eslint/typescript-estree": "7.18.0"
3019 + "brace-expansion": "^1.1.7"
3020 },
3021 "engines": {
2724 - "node": "^18.18.0 || >=20.0.0"
2725 - },
2726 - "funding": {
2727 - "type": "opencollective",
2728 - "url": "https://opencollective.com/typescript-eslint"
2729 - },
2730 - "peerDependencies": {
2731 - "eslint": "^8.56.0"
3022 + "node": "*"
3023 }
3024 },
2734 - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
2735 - "version": "7.18.0",
2736 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
2737 - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
2738 - "dev": true,
3025 + "node_modules/@unocss/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
3026 + "version": "6.21.0",
3027 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
3028 + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
3029 "dependencies": {
2740 - "@typescript-eslint/types": "7.18.0",
2741 - "eslint-visitor-keys": "^3.4.3"
3030 + "@typescript-eslint/types": "6.21.0",
3031 + "@typescript-eslint/visitor-keys": "6.21.0"
3032 },
3033 "engines": {
2744 - "node": "^18.18.0 || >=20.0.0"
3034 + "node": "^16.0.0 || >=18.0.0"
3035 },
3036 "funding": {
3037 "type": "opencollective",
3038 "url": "https://opencollective.com/typescript-eslint"
3039 }
3040 },
2751 - "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": {
2752 - "version": "9.0.5",
2753 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2754 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2755 - "dev": true,
2756 - "dependencies": {
2757 - "brace-expansion": "^2.0.1"
2758 - },
2759 - "engines": {
2760 - "node": ">=16 || 14 >=14.17"
2761 - },
2762 - "funding": {
2763 - "url": "https://github.com/sponsors/isaacs"
2764 - }
2765 - },
2766 - "node_modules/@typescript-eslint/types": {
3041 + "node_modules/@unocss/eslint-plugin/node_modules/@typescript-eslint/types": {
3042 "version": "6.21.0",
3043 "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
3044 "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
@@ -2775,7 +3050,7 @@
3050 "url": "https://opencollective.com/typescript-eslint"
3051 }
3052 },
2778 - "node_modules/@typescript-eslint/typescript-estree": {
3053 + "node_modules/@unocss/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
3054 "version": "6.21.0",
3055 "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
3056 "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
@@ -2802,7 +3077,7 @@
3077 }
3078 }
3079 },
2805 - "node_modules/@typescript-eslint/utils": {
3080 + "node_modules/@unocss/eslint-plugin/node_modules/@typescript-eslint/utils": {
3081 "version": "6.21.0",
3082 "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
3083 "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
@@ -2826,7 +3101,7 @@
3101 "eslint": "^7.0.0 || ^8.0.0"
3102 }
3103 },
2829 - "node_modules/@typescript-eslint/visitor-keys": {
3104 + "node_modules/@unocss/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
3105 "version": "6.21.0",
3106 "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
3107 "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
@@ -2842,115 +3117,363 @@
3117 "url": "https://opencollective.com/typescript-eslint"
3118 }
3119 },
2845 - "node_modules/@ungap/structured-clone": {
2846 - "version": "1.2.0",
2847 - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
2848 - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
2849 - },
2850 - "node_modules/@unocss/config": {
2851 - "version": "0.56.5",
2852 - "resolved": "https://registry.npmjs.org/@unocss/config/-/config-0.56.5.tgz",
2853 - "integrity": "sha512-rscnFIYgUlN/0hXHdhANyjFcDjDutt3JO0ZRITdNLzoglh7GVNiDTURBJwUZejF/vGJ7IkMd3qOdNhPFuRY1Bg==",
3120 + "node_modules/@unocss/eslint-plugin/node_modules/ansi-styles": {
3121 + "version": "4.3.0",
3122 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
3123 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
3124 + "peer": true,
3125 "dependencies": {
2855 - "@unocss/core": "0.56.5",
2856 - "unconfig": "^0.3.10"
3126 + "color-convert": "^2.0.1"
3127 },
3128 "engines": {
2859 - "node": ">=14"
3129 + "node": ">=8"
3130 },
3131 "funding": {
2862 - "url": "https://github.com/sponsors/antfu"
2863 - }
2864 - },
2865 - "node_modules/@unocss/core": {
2866 - "version": "0.56.5",
2867 - "resolved": "https://registry.npmjs.org/@unocss/core/-/core-0.56.5.tgz",
2868 - "integrity": "sha512-fx5VhOjSHn0HdV2D34pEwFMAHJcJQRTCp1xEE4GzxY1irXzaa+m2aYf5PZjmDxehiOC16IH7TO9FOWANXk1E0w==",
2869 - "funding": {
2870 - "url": "https://github.com/sponsors/antfu"
3132 + "url": "https://github.com/chalk/ansi-styles?sponsor=1"
3133 }
3134 },
2873 - "node_modules/@unocss/eslint-config": {
2874 - "version": "0.56.5",
2875 - "resolved": "https://registry.npmjs.org/@unocss/eslint-config/-/eslint-config-0.56.5.tgz",
2876 - "integrity": "sha512-UP7EBTl4ORRO0yqptW7oUWxM0qNh1Nk+z4rufC0C2kI8vKc8yab+sWvlBSC8DeX3kzR7uJxvtUsHGcBcoqZujg==",
3135 + "node_modules/@unocss/eslint-plugin/node_modules/chalk": {
3136 + "version": "4.1.2",
3137 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
3138 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
3139 + "peer": true,
3140 "dependencies": {
2878 - "@unocss/eslint-plugin": "0.56.5"
3141 + "ansi-styles": "^4.1.0",
3142 + "supports-color": "^7.1.0"
3143 },
3144 "engines": {
2881 - "node": ">=14"
3145 + "node": ">=10"
3146 },
3147 "funding": {
2884 - "url": "https://github.com/sponsors/antfu"
3148 + "url": "https://github.com/chalk/chalk?sponsor=1"
3149 }
3150 },
2887 - "node_modules/@unocss/eslint-plugin": {
2888 - "version": "0.56.5",
2889 - "resolved": "https://registry.npmjs.org/@unocss/eslint-plugin/-/eslint-plugin-0.56.5.tgz",
2890 - "integrity": "sha512-nMVw/kc0sYU5i8UBU1rEahzKhZRPTUjCztpPzo2KUKFKLBPAbTv0gJAOmU/n2kV3YiGk5Tl6jmShxu1MHjk5rA==",
3151 + "node_modules/@unocss/eslint-plugin/node_modules/color-convert": {
3152 + "version": "2.0.1",
3153 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
3154 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
3155 + "peer": true,
3156 "dependencies": {
2892 - "@typescript-eslint/utils": "^6.7.3",
2893 - "@unocss/config": "0.56.5",
2894 - "@unocss/core": "0.56.5",
2895 - "magic-string": "^0.30.3",
2896 - "synckit": "^0.8.5"
3157 + "color-name": "~1.1.4"
3158 },
3159 "engines": {
2899 - "node": ">=14"
2900 - },
2901 - "funding": {
2902 - "url": "https://github.com/sponsors/antfu"
3160 + "node": ">=7.0.0"
3161 }
3162 },
2905 - "node_modules/@vitejs/plugin-vue": {
2906 - "version": "5.1.3",
2907 - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.3.tgz",
2908 - "integrity": "sha512-3xbWsKEKXYlmX82aOHufFQVnkbMC/v8fLpWwh6hWOUrK5fbbtBh9Q/WWse27BFgSy2/e2c0fz5Scgya9h2GLhw==",
2909 - "dev": true,
3163 + "node_modules/@unocss/eslint-plugin/node_modules/color-name": {
3164 + "version": "1.1.4",
3165 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
3166 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
3167 + "peer": true
3168 + },
3169 + "node_modules/@unocss/eslint-plugin/node_modules/eslint": {
3170 + "version": "8.57.1",
3171 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
3172 + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
3173 + "peer": true,
3174 + "dependencies": {
3175 + "@eslint-community/eslint-utils": "^4.2.0",
3176 + "@eslint-community/regexpp": "^4.6.1",
3177 + "@eslint/eslintrc": "^2.1.4",
3178 + "@eslint/js": "8.57.1",
3179 + "@humanwhocodes/config-array": "^0.13.0",
3180 + "@humanwhocodes/module-importer": "^1.0.1",
3181 + "@nodelib/fs.walk": "^1.2.8",
3182 + "@ungap/structured-clone": "^1.2.0",
3183 + "ajv": "^6.12.4",
3184 + "chalk": "^4.0.0",
3185 + "cross-spawn": "^7.0.2",
3186 + "debug": "^4.3.2",
3187 + "doctrine": "^3.0.0",
3188 + "escape-string-regexp": "^4.0.0",
3189 + "eslint-scope": "^7.2.2",
3190 + "eslint-visitor-keys": "^3.4.3",
3191 + "espree": "^9.6.1",
3192 + "esquery": "^1.4.2",
3193 + "esutils": "^2.0.2",
3194 + "fast-deep-equal": "^3.1.3",
3195 + "file-entry-cache": "^6.0.1",
3196 + "find-up": "^5.0.0",
3197 + "glob-parent": "^6.0.2",
3198 + "globals": "^13.19.0",
3199 + "graphemer": "^1.4.0",
3200 + "ignore": "^5.2.0",
3201 + "imurmurhash": "^0.1.4",
3202 + "is-glob": "^4.0.0",
3203 + "is-path-inside": "^3.0.3",
3204 + "js-yaml": "^4.1.0",
3205 + "json-stable-stringify-without-jsonify": "^1.0.1",
3206 + "levn": "^0.4.1",
3207 + "lodash.merge": "^4.6.2",
3208 + "minimatch": "^3.1.2",
3209 + "natural-compare": "^1.4.0",
3210 + "optionator": "^0.9.3",
3211 + "strip-ansi": "^6.0.1",
3212 + "text-table": "^0.2.0"
3213 + },
3214 + "bin": {
3215 + "eslint": "bin/eslint.js"
3216 + },
3217 "engines": {
2911 - "node": "^18.0.0 || >=20.0.0"
3218 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
3219 },
2913 - "peerDependencies": {
2914 - "vite": "^5.0.0",
2915 - "vue": "^3.2.25"
3220 + "funding": {
3221 + "url": "https://opencollective.com/eslint"
3222 }
3223 },
2918 - "node_modules/@vitejs/plugin-vue-jsx": {
2919 - "version": "4.0.1",
2920 - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-4.0.1.tgz",
2921 - "integrity": "sha512-7mg9HFGnFHMEwCdB6AY83cVK4A6sCqnrjFYF4WIlebYAQVVJ/sC/CiTruVdrRlhrFoeZ8rlMxY9wYpPTIRhhAg==",
2922 - "dev": true,
3224 + "node_modules/@unocss/eslint-plugin/node_modules/eslint-scope": {
3225 + "version": "7.2.2",
3226 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
3227 + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
3228 + "peer": true,
3229 "dependencies": {
2924 - "@babel/core": "^7.24.7",
2925 - "@babel/plugin-transform-typescript": "^7.24.7",
2926 - "@vue/babel-plugin-jsx": "^1.2.2"
3230 + "esrecurse": "^4.3.0",
3231 + "estraverse": "^5.2.0"
3232 },
3233 "engines": {
2929 - "node": "^18.0.0 || >=20.0.0"
3234 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
3235 },
2931 - "peerDependencies": {
2932 - "vite": "^5.0.0",
2933 - "vue": "^3.0.0"
3236 + "funding": {
3237 + "url": "https://opencollective.com/eslint"
3238 }
3239 },
2936 - "node_modules/@vitest/expect": {
2937 - "version": "2.1.1",
2938 - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz",
2939 - "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==",
2940 - "dev": true,
2941 - "dependencies": {
2942 - "@vitest/spy": "2.1.1",
2943 - "@vitest/utils": "2.1.1",
2944 - "chai": "^5.1.1",
2945 - "tinyrainbow": "^1.2.0"
3240 + "node_modules/@unocss/eslint-plugin/node_modules/eslint-visitor-keys": {
3241 + "version": "3.4.3",
3242 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
3243 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
3244 + "engines": {
3245 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
3246 },
3247 "funding": {
2948 - "url": "https://opencollective.com/vitest"
3248 + "url": "https://opencollective.com/eslint"
3249 }
3250 },
2951 - "node_modules/@vitest/mocker": {
2952 - "version": "2.1.1",
2953 - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz",
3251 + "node_modules/@unocss/eslint-plugin/node_modules/eslint/node_modules/brace-expansion": {
3252 + "version": "1.1.11",
3253 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
3254 + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
3255 + "peer": true,
3256 + "dependencies": {
3257 + "balanced-match": "^1.0.0",
3258 + "concat-map": "0.0.1"
3259 + }
3260 + },
3261 + "node_modules/@unocss/eslint-plugin/node_modules/eslint/node_modules/minimatch": {
3262 + "version": "3.1.2",
3263 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
3264 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
3265 + "peer": true,
3266 + "dependencies": {
3267 + "brace-expansion": "^1.1.7"
3268 + },
3269 + "engines": {
3270 + "node": "*"
3271 + }
3272 + },
3273 + "node_modules/@unocss/eslint-plugin/node_modules/espree": {
3274 + "version": "9.6.1",
3275 + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
3276 + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
3277 + "peer": true,
3278 + "dependencies": {
3279 + "acorn": "^8.9.0",
3280 + "acorn-jsx": "^5.3.2",
3281 + "eslint-visitor-keys": "^3.4.1"
3282 + },
3283 + "engines": {
3284 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
3285 + },
3286 + "funding": {
3287 + "url": "https://opencollective.com/eslint"
3288 + }
3289 + },
3290 + "node_modules/@unocss/eslint-plugin/node_modules/file-entry-cache": {
3291 + "version": "6.0.1",
3292 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
3293 + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
3294 + "peer": true,
3295 + "dependencies": {
3296 + "flat-cache": "^3.0.4"
3297 + },
3298 + "engines": {
3299 + "node": "^10.12.0 || >=12.0.0"
3300 + }
3301 + },
3302 + "node_modules/@unocss/eslint-plugin/node_modules/flat-cache": {
3303 + "version": "3.2.0",
3304 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
3305 + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
3306 + "peer": true,
3307 + "dependencies": {
3308 + "flatted": "^3.2.9",
3309 + "keyv": "^4.5.3",
3310 + "rimraf": "^3.0.2"
3311 + },
3312 + "engines": {
3313 + "node": "^10.12.0 || >=12.0.0"
3314 + }
3315 + },
3316 + "node_modules/@unocss/eslint-plugin/node_modules/globals": {
3317 + "version": "13.24.0",
3318 + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
3319 + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
3320 + "peer": true,
3321 + "dependencies": {
3322 + "type-fest": "^0.20.2"
3323 + },
3324 + "engines": {
3325 + "node": ">=8"
3326 + },
3327 + "funding": {
3328 + "url": "https://github.com/sponsors/sindresorhus"
3329 + }
3330 + },
3331 + "node_modules/@unocss/eslint-plugin/node_modules/globby": {
3332 + "version": "11.1.0",
3333 + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
3334 + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
3335 + "dependencies": {
3336 + "array-union": "^2.1.0",
3337 + "dir-glob": "^3.0.1",
3338 + "fast-glob": "^3.2.9",
3339 + "ignore": "^5.2.0",
3340 + "merge2": "^1.4.1",
3341 + "slash": "^3.0.0"
3342 + },
3343 + "engines": {
3344 + "node": ">=10"
3345 + },
3346 + "funding": {
3347 + "url": "https://github.com/sponsors/sindresorhus"
3348 + }
3349 + },
3350 + "node_modules/@unocss/eslint-plugin/node_modules/has-flag": {
3351 + "version": "4.0.0",
3352 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
3353 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
3354 + "peer": true,
3355 + "engines": {
3356 + "node": ">=8"
3357 + }
3358 + },
3359 + "node_modules/@unocss/eslint-plugin/node_modules/minimatch": {
3360 + "version": "9.0.3",
3361 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
3362 + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
3363 + "dependencies": {
3364 + "brace-expansion": "^2.0.1"
3365 + },
3366 + "engines": {
3367 + "node": ">=16 || 14 >=14.17"
3368 + },
3369 + "funding": {
3370 + "url": "https://github.com/sponsors/isaacs"
3371 + }
3372 + },
3373 + "node_modules/@unocss/eslint-plugin/node_modules/slash": {
3374 + "version": "3.0.0",
3375 + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
3376 + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
3377 + "engines": {
3378 + "node": ">=8"
3379 + }
3380 + },
3381 + "node_modules/@unocss/eslint-plugin/node_modules/supports-color": {
3382 + "version": "7.2.0",
3383 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
3384 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
3385 + "peer": true,
3386 + "dependencies": {
3387 + "has-flag": "^4.0.0"
3388 + },
3389 + "engines": {
3390 + "node": ">=8"
3391 + }
3392 + },
3393 + "node_modules/@unocss/eslint-plugin/node_modules/type-fest": {
3394 + "version": "0.20.2",
3395 + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
3396 + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
3397 + "peer": true,
3398 + "engines": {
3399 + "node": ">=10"
3400 + },
3401 + "funding": {
3402 + "url": "https://github.com/sponsors/sindresorhus"
3403 + }
3404 + },
3405 + "node_modules/@vitejs/plugin-vue": {
3406 + "version": "5.1.4",
3407 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz",
3408 + "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
3409 + "dev": true,
3410 + "engines": {
3411 + "node": "^18.0.0 || >=20.0.0"
3412 + },
3413 + "peerDependencies": {
3414 + "vite": "^5.0.0",
3415 + "vue": "^3.2.25"
3416 + }
3417 + },
3418 + "node_modules/@vitejs/plugin-vue-jsx": {
3419 + "version": "4.0.1",
3420 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-4.0.1.tgz",
3421 + "integrity": "sha512-7mg9HFGnFHMEwCdB6AY83cVK4A6sCqnrjFYF4WIlebYAQVVJ/sC/CiTruVdrRlhrFoeZ8rlMxY9wYpPTIRhhAg==",
3422 + "dev": true,
3423 + "dependencies": {
3424 + "@babel/core": "^7.24.7",
3425 + "@babel/plugin-transform-typescript": "^7.24.7",
3426 + "@vue/babel-plugin-jsx": "^1.2.2"
3427 + },
3428 + "engines": {
3429 + "node": "^18.0.0 || >=20.0.0"
3430 + },
3431 + "peerDependencies": {
3432 + "vite": "^5.0.0",
3433 + "vue": "^3.0.0"
3434 + }
3435 + },
3436 + "node_modules/@vitest/eslint-plugin": {
3437 + "version": "1.1.4",
3438 + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.4.tgz",
3439 + "integrity": "sha512-kudjgefmJJ7xQ2WfbUU6pZbm7Ou4gLYRaao/8Ynide3G0QhVKHd978sDyWX4KOH0CCMH9cyrGAkFd55eGzJ48Q==",
3440 + "dev": true,
3441 + "peerDependencies": {
3442 + "@typescript-eslint/utils": ">= 8.0",
3443 + "eslint": ">= 8.57.0",
3444 + "typescript": ">= 5.0.0",
3445 + "vitest": "*"
3446 + },
3447 + "peerDependenciesMeta": {
3448 + "@typescript-eslint/utils": {
3449 + "optional": true
3450 + },
3451 + "typescript": {
3452 + "optional": true
3453 + },
3454 + "vitest": {
3455 + "optional": true
3456 + }
3457 + }
3458 + },
3459 + "node_modules/@vitest/expect": {
3460 + "version": "2.1.1",
3461 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz",
3462 + "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==",
3463 + "dev": true,
3464 + "dependencies": {
3465 + "@vitest/spy": "2.1.1",
3466 + "@vitest/utils": "2.1.1",
3467 + "chai": "^5.1.1",
3468 + "tinyrainbow": "^1.2.0"
3469 + },
3470 + "funding": {
3471 + "url": "https://opencollective.com/vitest"
3472 + }
3473 + },
3474 + "node_modules/@vitest/mocker": {
3475 + "version": "2.1.1",
3476 + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz",
3477 "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==",
3478 "dev": true,
3479 "dependencies": {
@@ -3050,27 +3573,27 @@
3573 }
3574 },
3575 "node_modules/@volar/language-core": {
3053 - "version": "2.4.4",
3054 - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.4.tgz",
3055 - "integrity": "sha512-kO9k4kTLfxpg+6lq7/KAIv3m2d62IHuCL6GbVgYZTpfKvIGoAIlDxK7pFcB/eczN2+ydg/vnyaeZ6SGyZrJw2w==",
3576 + "version": "2.4.5",
3577 + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.5.tgz",
3578 + "integrity": "sha512-F4tA0DCO5Q1F5mScHmca0umsi2ufKULAnMOVBfMsZdT4myhVl4WdKRwCaKcfOkIEuyrAVvtq1ESBdZ+rSyLVww==",
3579 "dev": true,
3580 "dependencies": {
3058 - "@volar/source-map": "2.4.4"
3581 + "@volar/source-map": "2.4.5"
3582 }
3583 },
3584 "node_modules/@volar/source-map": {
3062 - "version": "2.4.4",
3063 - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.4.tgz",
3064 - "integrity": "sha512-xG3PZqOP2haG8XG4Pg3PD1UGDAdqZg24Ru8c/qYjYAnmcj6GBR64mstx+bZux5QOyRaJK+/lNM/RnpvBD3489g==",
3585 + "version": "2.4.5",
3586 + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.5.tgz",
3587 + "integrity": "sha512-varwD7RaKE2J/Z+Zu6j3mNNJbNT394qIxXwdvz/4ao/vxOfyClZpSDtLKkwWmecinkOVos5+PWkWraelfMLfpw==",
3588 "dev": true
3589 },
3590 "node_modules/@volar/typescript": {
3068 - "version": "2.4.4",
3069 - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.4.tgz",
3070 - "integrity": "sha512-QQMQRVj0fVHJ3XdRKiS1LclhG0VBXdFYlyuHRQF/xLk2PuJuHNWP26MDZNvEVCvnyUQuUQhIAfylwY5TGPgc6w==",
3591 + "version": "2.4.5",
3592 + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.5.tgz",
3593 + "integrity": "sha512-mcT1mHvLljAEtHviVcBuOyAwwMKz1ibXTi5uYtP/pf4XxoAzpdkQ+Br2IC0NPCvLCbjPZmbf3I0udndkfB1CDg==",
3594 "dev": true,
3595 "dependencies": {
3073 - "@volar/language-core": "2.4.4",
3596 + "@volar/language-core": "2.4.5",
3597 "path-browserify": "^1.0.1",
3598 "vscode-uri": "^3.0.8"
3599 }
@@ -3124,49 +3647,49 @@
3647 }
3648 },
3649 "node_modules/@vue/compiler-core": {
3127 - "version": "3.5.5",
3128 - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.5.tgz",
3129 - "integrity": "sha512-ZrxcY8JMoV+kgDrmRwlDufz0SjDZ7jfoNZiIBluAACMBmgr55o/jTbxnyrccH6VSJXnFaDI4Ik1UFCiq9r8i7w==",
3650 + "version": "3.5.7",
3651 + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.7.tgz",
3652 + "integrity": "sha512-A0gay3lK71MddsSnGlBxRPOugIVdACze9L/rCo5X5srCyjQfZOfYtSFMJc3aOZCM+xN55EQpb4R97rYn/iEbSw==",
3653 "dependencies": {
3654 "@babel/parser": "^7.25.3",
3132 - "@vue/shared": "3.5.5",
3655 + "@vue/shared": "3.5.7",
3656 "entities": "^4.5.0",
3657 "estree-walker": "^2.0.2",
3658 "source-map-js": "^1.2.0"
3659 }
3660 },
3661 "node_modules/@vue/compiler-dom": {
3139 - "version": "3.5.5",
3140 - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.5.tgz",
3141 - "integrity": "sha512-HSvK5q1gmBbxRse3S0Wt34RcKuOyjDJKDDMuF3i7NC+QkDFrbAqw8NnrEm/z7zFDxWZa4/5eUwsBOMQzm1RHBA==",
3662 + "version": "3.5.7",
3663 + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.7.tgz",
3664 + "integrity": "sha512-GYWl3+gO8/g0ZdYaJ18fYHdI/WVic2VuuUd1NsPp60DWXKy+XjdhFsDW7FbUto8siYYZcosBGn9yVBkjhq1M8Q==",
3665 "dependencies": {
3143 - "@vue/compiler-core": "3.5.5",
3144 - "@vue/shared": "3.5.5"
3666 + "@vue/compiler-core": "3.5.7",
3667 + "@vue/shared": "3.5.7"
3668 }
3669 },
3670 "node_modules/@vue/compiler-sfc": {
3148 - "version": "3.5.5",
3149 - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.5.tgz",
3150 - "integrity": "sha512-MzBHDxwZhgQPHrwJ5tj92gdTYRCuPDSZr8PY3+JFv8cv2UD5/WayH5yo0kKCkKfrtJhc39jNSMityHrkMSbfnA==",
3671 + "version": "3.5.7",
3672 + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.7.tgz",
3673 + "integrity": "sha512-EjOJtCWJrC7HqoCEzOwpIYHm+JH7YmkxC1hG6VkqIukYRqj8KFUlTLK6hcT4nGgtVov2+ZfrdrRlcaqS78HnBA==",
3674 "dependencies": {
3675 "@babel/parser": "^7.25.3",
3153 - "@vue/compiler-core": "3.5.5",
3154 - "@vue/compiler-dom": "3.5.5",
3155 - "@vue/compiler-ssr": "3.5.5",
3156 - "@vue/shared": "3.5.5",
3676 + "@vue/compiler-core": "3.5.7",
3677 + "@vue/compiler-dom": "3.5.7",
3678 + "@vue/compiler-ssr": "3.5.7",
3679 + "@vue/shared": "3.5.7",
3680 "estree-walker": "^2.0.2",
3681 "magic-string": "^0.30.11",
3159 - "postcss": "^8.4.44",
3682 + "postcss": "^8.4.47",
3683 "source-map-js": "^1.2.0"
3684 }
3685 },
3686 "node_modules/@vue/compiler-ssr": {
3164 - "version": "3.5.5",
3165 - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.5.tgz",
3166 - "integrity": "sha512-oFasHnpv/upubjJEmqiTKQYb4qS3ziJddf4UVWuFw6ebk/QTrTUc+AUoTJdo39x9g+AOQBzhOU0ICCRuUjvkmw==",
3687 + "version": "3.5.7",
3688 + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.7.tgz",
3689 + "integrity": "sha512-oZx+jXP2k5arV/8Ly3TpQbfFyimMw2ANrRqvHJoKjPqtEzazxQGZjCLOfq8TnZ3wy2TOXdqfmVp4q7FyYeHV4g==",
3690 "dependencies": {
3168 - "@vue/compiler-dom": "3.5.5",
3169 - "@vue/shared": "3.5.5"
3691 + "@vue/compiler-dom": "3.5.7",
3692 + "@vue/shared": "3.5.7"
3693 }
3694 },
3695 "node_modules/@vue/compiler-vue2": {
@@ -3225,44 +3748,6 @@
3748 "rfdc": "^1.4.1"
3749 }
3750 },
3228 - "node_modules/@vue/eslint-config-prettier": {
3229 - "version": "9.0.0",
3230 - "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
3231 - "integrity": "sha512-z1ZIAAUS9pKzo/ANEfd2sO+v2IUalz7cM/cTLOZ7vRFOPk5/xuRKQteOu1DErFLAh/lYGXMVZ0IfYKlyInuDVg==",
3232 - "dev": true,
3233 - "dependencies": {
3234 - "eslint-config-prettier": "^9.0.0",
3235 - "eslint-plugin-prettier": "^5.0.0"
3236 - },
3237 - "peerDependencies": {
3238 - "eslint": ">= 8.0.0",
3239 - "prettier": ">= 3.0.0"
3240 - }
3241 - },
3242 - "node_modules/@vue/eslint-config-typescript": {
3243 - "version": "13.0.0",
3244 - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-13.0.0.tgz",
3245 - "integrity": "sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==",
3246 - "dev": true,
3247 - "dependencies": {
3248 - "@typescript-eslint/eslint-plugin": "^7.1.1",
3249 - "@typescript-eslint/parser": "^7.1.1",
3250 - "vue-eslint-parser": "^9.3.1"
3251 - },
3252 - "engines": {
3253 - "node": "^18.18.0 || >=20.0.0"
3254 - },
3255 - "peerDependencies": {
3256 - "eslint": "^8.56.0",
3257 - "eslint-plugin-vue": "^9.0.0",
3258 - "typescript": ">=4.7.4"
3259 - },
3260 - "peerDependenciesMeta": {
3261 - "typescript": {
3262 - "optional": true
3263 - }
3264 - }
3265 - },
3751 "node_modules/@vue/language-core": {
3752 "version": "2.1.6",
3753 "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.6.tgz",
@@ -3288,49 +3773,49 @@
3773 }
3774 },
3775 "node_modules/@vue/reactivity": {
3291 - "version": "3.5.5",
3292 - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.5.tgz",
3293 - "integrity": "sha512-V4tTWElZQhT73PSK3Wnax9R9m4qvMX+LeKHnfylZc6SLh4Jc5/BPakp6e3zEhKWi5AN8TDzRkGnLkp8OqycYng==",
3776 + "version": "3.5.7",
3777 + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.7.tgz",
3778 + "integrity": "sha512-yF0EpokpOHRNXyn/h6abXc9JFIzfdAf0MJHIi92xxCWS0mqrXH6+2aZ+A6EbSrspGzX5MHTd5N8iBA28HnXu9g==",
3779 "dependencies": {
3295 - "@vue/shared": "3.5.5"
3780 + "@vue/shared": "3.5.7"
3781 }
3782 },
3783 "node_modules/@vue/runtime-core": {
3299 - "version": "3.5.5",
3300 - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.5.tgz",
3301 - "integrity": "sha512-2/CFaRN17jgsXy4MpigWFBCAMmLkXPb4CjaHrndglwYSra7ajvkH2cat21dscuXaH91G8fXAeg5gCyxWJ+wCRA==",
3784 + "version": "3.5.7",
3785 + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.7.tgz",
3786 + "integrity": "sha512-OzLpBpKbZEaZVSNfd+hQbfBrDKux+b7Yl5hYhhWWWhHD7fEpF+CdI3Brm5k5GsufHEfvMcjruPxwQZuBN6nFYQ==",
3787 "dependencies": {
3303 - "@vue/reactivity": "3.5.5",
3304 - "@vue/shared": "3.5.5"
3788 + "@vue/reactivity": "3.5.7",
3789 + "@vue/shared": "3.5.7"
3790 }
3791 },
3792 "node_modules/@vue/runtime-dom": {
3308 - "version": "3.5.5",
3309 - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.5.tgz",
3310 - "integrity": "sha512-0bQGgCuL+4Muz5PsCLgF4Ata9BTdhHi5VjsxtTDyI0Wy4MgoSvBGaA6bDc7W7CGgZOyirf9LNeetMYHQ05pgpw==",
3793 + "version": "3.5.7",
3794 + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.7.tgz",
3795 + "integrity": "sha512-fL7cETfE27U2jyTgqzE382IGFY6a6uyznErn27KbbEzNctzxxUWYDbaN3B55l9nXh0xW2LRWPuWKOvjtO2UewQ==",
3796 "dependencies": {
3312 - "@vue/reactivity": "3.5.5",
3313 - "@vue/runtime-core": "3.5.5",
3314 - "@vue/shared": "3.5.5",
3797 + "@vue/reactivity": "3.5.7",
3798 + "@vue/runtime-core": "3.5.7",
3799 + "@vue/shared": "3.5.7",
3800 "csstype": "^3.1.3"
3801 }
3802 },
3803 "node_modules/@vue/server-renderer": {
3319 - "version": "3.5.5",
3320 - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.5.tgz",
3321 - "integrity": "sha512-XjRamLIq5f47cxgy+hiX7zUIY+4RHdPDVrPvvMDAUTdW5RJWX/S0ji/rCbm3LWTT/9Co9bvQME8ZI15ahL4/Qw==",
3804 + "version": "3.5.7",
3805 + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.7.tgz",
3806 + "integrity": "sha512-peRypij815eIDjpPpPXvYQGYqPH6QXwLJGWraJYPPn8JqWGl29A8QXnS7/Mh3TkMiOcdsJNhbFCoW2Agc2NgAQ==",
3807 "dependencies": {
3323 - "@vue/compiler-ssr": "3.5.5",
3324 - "@vue/shared": "3.5.5"
3808 + "@vue/compiler-ssr": "3.5.7",
3809 + "@vue/shared": "3.5.7"
3810 },
3811 "peerDependencies": {
3327 - "vue": "3.5.5"
3812 + "vue": "3.5.7"
3813 }
3814 },
3815 "node_modules/@vue/shared": {
3331 - "version": "3.5.5",
3332 - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.5.tgz",
3333 - "integrity": "sha512-0KyMXyEgnmFAs6rNUL+6eUHtUCqCaNrVd+AW3MX3LyA0Yry5SA0Km03CDKiOua1x1WWnIr+W9+S0GMFoSDWERQ=="
3816 + "version": "3.5.7",
3817 + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.7.tgz",
3818 + "integrity": "sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g=="
3819 },
3820 "node_modules/@vue/test-utils": {
3821 "version": "2.4.6",
@@ -3349,12 +3834,12 @@
3834 "dev": true
3835 },
3836 "node_modules/@vueuse/components": {
3352 - "version": "11.0.3",
3353 - "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-11.0.3.tgz",
3354 - "integrity": "sha512-/mbv4R1ody3rYz42U14SQ6GkAKHCAHhFIBdR6nKJbEJeFMoiB93dzX0sT2S05CpYCJJgv3JLAwEHeehYqqmbyQ==",
3837 + "version": "11.1.0",
3838 + "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-11.1.0.tgz",
3839 + "integrity": "sha512-8CTbvH1rHfAlqsMdMg6G2OiIvHiHn8HRx0yyJ+Ri/C8J6cLylXAd7VESawoCAkRdAXT4ucPnj/uYHZqEb0JbBQ==",
3840 "dependencies": {
3356 - "@vueuse/core": "11.0.3",
3357 - "@vueuse/shared": "11.0.3",
3841 + "@vueuse/core": "11.1.0",
3842 + "@vueuse/shared": "11.1.0",
3843 "vue-demi": ">=0.14.10"
3844 }
3845 },
@@ -3384,13 +3869,13 @@
3869 }
3870 },
3871 "node_modules/@vueuse/core": {
3387 - "version": "11.0.3",
3388 - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.0.3.tgz",
3389 - "integrity": "sha512-RENlh64+SYA9XMExmmH1a3TPqeIuJBNNB/63GT35MZI+zpru3oMRUA6cEFr9HmGqEgUisurwGwnIieF6qu3aXw==",
3872 + "version": "11.1.0",
3873 + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.1.0.tgz",
3874 + "integrity": "sha512-P6dk79QYA6sKQnghrUz/1tHi0n9mrb/iO1WTMk/ElLmTyNqgDeSZ3wcDf6fRBGzRJbeG1dxzEOvLENMjr+E3fg==",
3875 "dependencies": {
3876 "@types/web-bluetooth": "^0.0.20",
3392 - "@vueuse/metadata": "11.0.3",
3393 - "@vueuse/shared": "11.0.3",
3877 + "@vueuse/metadata": "11.1.0",
3878 + "@vueuse/shared": "11.1.0",
3879 "vue-demi": ">=0.14.10"
3880 },
3881 "funding": {
@@ -3423,17 +3908,17 @@
3908 }
3909 },
3910 "node_modules/@vueuse/metadata": {
3426 - "version": "11.0.3",
3427 - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.0.3.tgz",
3428 - "integrity": "sha512-+FtbO4SD5WpsOcQTcC0hAhNlOid6QNLzqedtquTtQ+CRNBoAt9GuV07c6KNHK1wCmlq8DFPwgiLF2rXwgSHX5Q==",
3911 + "version": "11.1.0",
3912 + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.1.0.tgz",
3913 + "integrity": "sha512-l9Q502TBTaPYGanl1G+hPgd3QX5s4CGnpXriVBR5fEZ/goI6fvDaVmIl3Td8oKFurOxTmbXvBPSsgrd6eu6HYg==",
3914 "funding": {
3915 "url": "https://github.com/sponsors/antfu"
3916 }
3917 },
3918 "node_modules/@vueuse/shared": {
3434 - "version": "11.0.3",
3435 - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.0.3.tgz",
3436 - "integrity": "sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==",
3919 + "version": "11.1.0",
3920 + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.1.0.tgz",
3921 + "integrity": "sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==",
3922 "dependencies": {
3923 "vue-demi": ">=0.14.10"
3924 },
@@ -3625,6 +4110,17 @@
4110 "node": ">= 8"
4111 }
4112 },
4113 + "node_modules/anymatch/node_modules/picomatch": {
4114 + "version": "2.3.1",
4115 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
4116 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
4117 + "engines": {
4118 + "node": ">=8.6"
4119 + },
4120 + "funding": {
4121 + "url": "https://github.com/sponsors/jonschlinkert"
4122 + }
4123 + },
4124 "node_modules/apexcharts": {
4125 "version": "3.53.0",
4126 "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.53.0.tgz",
@@ -3659,6 +4155,15 @@
4155 }
4156 ]
4157 },
4158 + "node_modules/are-docs-informative": {
4159 + "version": "0.0.2",
4160 + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz",
4161 + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==",
4162 + "dev": true,
4163 + "engines": {
4164 + "node": ">=14"
4165 + }
4166 + },
4167 "node_modules/arg": {
4168 "version": "5.0.2",
4169 "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -3955,6 +4460,18 @@
4460 "node": "*"
4461 }
4462 },
4463 + "node_modules/builtin-modules": {
4464 + "version": "3.3.0",
4465 + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
4466 + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
4467 + "dev": true,
4468 + "engines": {
4469 + "node": ">=6"
4470 + },
4471 + "funding": {
4472 + "url": "https://github.com/sponsors/sindresorhus"
4473 + }
4474 + },
4475 "node_modules/bundle-name": {
4476 "version": "4.1.0",
4477 "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
@@ -4088,9 +4605,9 @@
4605 }
4606 },
4607 "node_modules/caniuse-lite": {
4091 - "version": "1.0.30001660",
4092 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
4093 - "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
4608 + "version": "1.0.30001662",
4609 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz",
4610 + "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==",
4611 "funding": [
4612 {
4613 "type": "opencollective",
@@ -4150,6 +4667,24 @@
4667 "node": ">=4"
4668 }
4669 },
4670 + "node_modules/chalk/node_modules/escape-string-regexp": {
4671 + "version": "1.0.5",
4672 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
4673 + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
4674 + "engines": {
4675 + "node": ">=0.8.0"
4676 + }
4677 + },
4678 + "node_modules/character-entities": {
4679 + "version": "2.0.2",
4680 + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
4681 + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
4682 + "dev": true,
4683 + "funding": {
4684 + "type": "github",
4685 + "url": "https://github.com/sponsors/wooorm"
4686 + }
4687 + },
4688 "node_modules/character-entities-html4": {
4689 "version": "2.1.0",
4690 "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
@@ -4229,9 +4764,9 @@
4764 }
4765 },
4766 "node_modules/ci-info": {
4232 - "version": "3.9.0",
4233 - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
4234 - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
4767 + "version": "4.0.0",
4768 + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz",
4769 + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==",
4770 "dev": true,
4771 "funding": [
4772 {
@@ -4256,6 +4791,27 @@
4791 "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
4792 "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
4793 },
4794 + "node_modules/clean-regexp": {
4795 + "version": "1.0.0",
4796 + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
4797 + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==",
4798 + "dev": true,
4799 + "dependencies": {
4800 + "escape-string-regexp": "^1.0.5"
4801 + },
4802 + "engines": {
4803 + "node": ">=4"
4804 + }
4805 + },
4806 + "node_modules/clean-regexp/node_modules/escape-string-regexp": {
4807 + "version": "1.0.5",
4808 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
4809 + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
4810 + "dev": true,
4811 + "engines": {
4812 + "node": ">=0.8.0"
4813 + }
4814 + },
4815 "node_modules/clean-stack": {
4816 "version": "2.2.0",
4817 "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -4435,6 +4991,15 @@
4991 "node": ">= 6"
4992 }
4993 },
4994 + "node_modules/comment-parser": {
4995 + "version": "1.4.1",
4996 + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
4997 + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
4998 + "dev": true,
4999 + "engines": {
5000 + "node": ">= 12.0.0"
5001 + }
5002 + },
5003 "node_modules/common-tags": {
5004 "version": "1.8.2",
5005 "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
@@ -4543,6 +5108,19 @@
5108 "url": "https://github.com/sponsors/mesqueeb"
5109 }
5110 },
5111 + "node_modules/core-js-compat": {
5112 + "version": "3.38.1",
5113 + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
5114 + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==",
5115 + "dev": true,
5116 + "dependencies": {
5117 + "browserslist": "^4.23.3"
5118 + },
5119 + "funding": {
5120 + "type": "opencollective",
5121 + "url": "https://opencollective.com/core-js"
5122 + }
5123 + },
5124 "node_modules/core-util-is": {
5125 "version": "1.0.2",
5126 "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -4932,6 +5510,19 @@
5510 "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
5511 "dev": true
5512 },
5513 + "node_modules/decode-named-character-reference": {
5514 + "version": "1.0.2",
5515 + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
5516 + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
5517 + "dev": true,
5518 + "dependencies": {
5519 + "character-entities": "^2.0.0"
5520 + },
5521 + "funding": {
5522 + "type": "github",
5523 + "url": "https://github.com/sponsors/wooorm"
5524 + }
5525 + },
5526 "node_modules/deep-eql": {
5527 "version": "5.0.2",
5528 "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
@@ -5096,12 +5687,31 @@
5687 "node": ">=8"
5688 }
5689 },
5690 + "node_modules/dir-glob/node_modules/path-type": {
5691 + "version": "4.0.0",
5692 + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
5693 + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
5694 + "engines": {
5695 + "node": ">=8"
5696 + }
5697 + },
5698 "node_modules/dlv": {
5699 "version": "1.1.3",
5700 "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
5701 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
5702 "dev": true
5703 },
5704 + "node_modules/doctrine": {
5705 + "version": "3.0.0",
5706 + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
5707 + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
5708 + "dependencies": {
5709 + "esutils": "^2.0.2"
5710 + },
5711 + "engines": {
5712 + "node": ">=6.0.0"
5713 + }
5714 + },
5715 "node_modules/dom-serializer": {
5716 "version": "2.0.0",
5717 "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -5253,9 +5863,9 @@
5863 "dev": true
5864 },
5865 "node_modules/electron-to-chromium": {
5256 - "version": "1.5.22",
5257 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.22.tgz",
5258 - "integrity": "sha512-tKYm5YHPU1djz0O+CGJ+oJIvimtsCcwR2Z9w7Skh08lUdyzXY5djods3q+z2JkWdb7tCcmM//eVavSRAiaPRNg=="
5866 + "version": "1.5.27",
5867 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz",
5868 + "integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw=="
5869 },
5870 "node_modules/emoji-regex": {
5871 "version": "8.0.0",
@@ -5281,6 +5891,19 @@
5891 "once": "^1.4.0"
5892 }
5893 },
5894 + "node_modules/enhanced-resolve": {
5895 + "version": "5.17.1",
5896 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
5897 + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
5898 + "dev": true,
5899 + "dependencies": {
5900 + "graceful-fs": "^4.2.4",
5901 + "tapable": "^2.2.0"
5902 + },
5903 + "engines": {
5904 + "node": ">=10.13.0"
5905 + }
5906 + },
5907 "node_modules/enquirer": {
5908 "version": "2.4.1",
5909 "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
@@ -5305,6 +5928,15 @@
5928 "url": "https://github.com/fb55/entities?sponsor=1"
5929 }
5930 },
5931 + "node_modules/error-ex": {
5932 + "version": "1.3.2",
5933 + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
5934 + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
5935 + "dev": true,
5936 + "dependencies": {
5937 + "is-arrayish": "^0.2.1"
5938 + }
5939 + },
5940 "node_modules/error-stack-parser-es": {
5941 "version": "0.1.5",
5942 "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz",
@@ -5335,6 +5967,12 @@
5967 "node": ">= 0.4"
5968 }
5969 },
5970 + "node_modules/es-module-lexer": {
5971 + "version": "1.5.4",
5972 + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
5973 + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
5974 + "dev": true
5975 + },
5976 "node_modules/esbuild": {
5977 "version": "0.21.5",
5978 "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -5388,24 +6026,27 @@
6026 "dev": true
6027 },
6028 "node_modules/escape-string-regexp": {
5391 - "version": "1.0.5",
5392 - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
5393 - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
6029 + "version": "4.0.0",
6030 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
6031 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
6032 "engines": {
5395 - "node": ">=0.8.0"
6033 + "node": ">=10"
6034 + },
6035 + "funding": {
6036 + "url": "https://github.com/sponsors/sindresorhus"
6037 }
6038 },
6039 "node_modules/eslint": {
5399 - "version": "9.10.0",
5400 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
5401 - "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
6040 + "version": "9.11.0",
6041 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.0.tgz",
6042 + "integrity": "sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==",
6043 "dependencies": {
6044 "@eslint-community/eslint-utils": "^4.2.0",
6045 "@eslint-community/regexpp": "^4.11.0",
6046 "@eslint/config-array": "^0.18.0",
6047 "@eslint/eslintrc": "^3.1.0",
5407 - "@eslint/js": "9.10.0",
5408 - "@eslint/plugin-kit": "^0.1.0",
6048 + "@eslint/js": "9.11.0",
6049 + "@eslint/plugin-kit": "^0.2.0",
6050 "@humanwhocodes/module-importer": "^1.0.1",
6051 "@humanwhocodes/retry": "^0.3.0",
6052 "@nodelib/fs.walk": "^1.2.8",
@@ -5453,6 +6094,37 @@
6094 }
6095 }
6096 },
6097 + "node_modules/eslint-compat-utils": {
6098 + "version": "0.5.1",
6099 + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
6100 + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
6101 + "dev": true,
6102 + "dependencies": {
6103 + "semver": "^7.5.4"
6104 + },
6105 + "engines": {
6106 + "node": ">=12"
6107 + },
6108 + "peerDependencies": {
6109 + "eslint": ">=6.0.0"
6110 + }
6111 + },
6112 + "node_modules/eslint-config-flat-gitignore": {
6113 + "version": "0.3.0",
6114 + "resolved": "https://registry.npmjs.org/eslint-config-flat-gitignore/-/eslint-config-flat-gitignore-0.3.0.tgz",
6115 + "integrity": "sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==",
6116 + "dev": true,
6117 + "dependencies": {
6118 + "@eslint/compat": "^1.1.1",
6119 + "find-up-simple": "^1.0.0"
6120 + },
6121 + "funding": {
6122 + "url": "https://github.com/sponsors/antfu"
6123 + },
6124 + "peerDependencies": {
6125 + "eslint": "^9.5.0"
6126 + }
6127 + },
6128 "node_modules/eslint-config-prettier": {
6129 "version": "9.1.0",
6130 "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
@@ -5484,159 +6156,207 @@
6156 "pnpm": ">= 8.6.0"
6157 }
6158 },
5487 - "node_modules/eslint-plugin-cypress": {
5488 - "version": "3.5.0",
5489 - "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.5.0.tgz",
5490 - "integrity": "sha512-JZQ6XnBTNI8h1B9M7wJSFzc48SYbh7VMMKaNTQOFa3BQlnmXPrVc4PKen8R+fpv6VleiPeej6VxloGb42zdRvw==",
6159 + "node_modules/eslint-flat-config-utils": {
6160 + "version": "0.4.0",
6161 + "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-0.4.0.tgz",
6162 + "integrity": "sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==",
6163 "dev": true,
6164 "dependencies": {
5493 - "globals": "^13.20.0"
6165 + "pathe": "^1.1.2"
6166 },
5495 - "peerDependencies": {
5496 - "eslint": ">=7"
6167 + "funding": {
6168 + "url": "https://github.com/sponsors/antfu"
6169 }
6170 },
5499 - "node_modules/eslint-plugin-cypress/node_modules/globals": {
5500 - "version": "13.24.0",
5501 - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
5502 - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
6171 + "node_modules/eslint-import-resolver-node": {
6172 + "version": "0.3.9",
6173 + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
6174 + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
6175 "dev": true,
6176 "dependencies": {
5505 - "type-fest": "^0.20.2"
6177 + "debug": "^3.2.7",
6178 + "is-core-module": "^2.13.0",
6179 + "resolve": "^1.22.4"
6180 + }
6181 + },
6182 + "node_modules/eslint-import-resolver-node/node_modules/debug": {
6183 + "version": "3.2.7",
6184 + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
6185 + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
6186 + "dev": true,
6187 + "dependencies": {
6188 + "ms": "^2.1.1"
6189 + }
6190 + },
6191 + "node_modules/eslint-merge-processors": {
6192 + "version": "0.1.0",
6193 + "resolved": "https://registry.npmjs.org/eslint-merge-processors/-/eslint-merge-processors-0.1.0.tgz",
6194 + "integrity": "sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==",
6195 + "dev": true,
6196 + "funding": {
6197 + "url": "https://github.com/sponsors/antfu"
6198 },
5507 - "engines": {
5508 - "node": ">=8"
6199 + "peerDependencies": {
6200 + "eslint": "*"
6201 + }
6202 + },
6203 + "node_modules/eslint-plugin-antfu": {
6204 + "version": "2.7.0",
6205 + "resolved": "https://registry.npmjs.org/eslint-plugin-antfu/-/eslint-plugin-antfu-2.7.0.tgz",
6206 + "integrity": "sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==",
6207 + "dev": true,
6208 + "dependencies": {
6209 + "@antfu/utils": "^0.7.10"
6210 },
6211 "funding": {
5511 - "url": "https://github.com/sponsors/sindresorhus"
6212 + "url": "https://github.com/sponsors/antfu"
6213 + },
6214 + "peerDependencies": {
6215 + "eslint": "*"
6216 }
6217 },
5514 - "node_modules/eslint-plugin-cypress/node_modules/type-fest": {
5515 - "version": "0.20.2",
5516 - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
5517 - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
6218 + "node_modules/eslint-plugin-command": {
6219 + "version": "0.2.5",
6220 + "resolved": "https://registry.npmjs.org/eslint-plugin-command/-/eslint-plugin-command-0.2.5.tgz",
6221 + "integrity": "sha512-mbCaSHD37MT8nVJnJUz2oeDfhz0wdOjfrqQVWkSpXuj3uU8m7/FK/niV2bL922af3M1js5x7Xcu3PwqWsrahfA==",
6222 "dev": true,
5519 - "engines": {
5520 - "node": ">=10"
6223 + "dependencies": {
6224 + "@es-joy/jsdoccomment": "^0.48.0"
6225 },
6226 "funding": {
5523 - "url": "https://github.com/sponsors/sindresorhus"
6227 + "url": "https://github.com/sponsors/antfu"
6228 + },
6229 + "peerDependencies": {
6230 + "eslint": "*"
6231 }
6232 },
5526 - "node_modules/eslint-plugin-prettier": {
5527 - "version": "5.2.1",
5528 - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
5529 - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==",
6233 + "node_modules/eslint-plugin-es-x": {
6234 + "version": "7.8.0",
6235 + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
6236 + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==",
6237 + "dev": true,
6238 + "funding": [
6239 + "https://github.com/sponsors/ota-meshi",
6240 + "https://opencollective.com/eslint"
6241 + ],
6242 "dependencies": {
5531 - "prettier-linter-helpers": "^1.0.0",
5532 - "synckit": "^0.9.1"
6243 + "@eslint-community/eslint-utils": "^4.1.2",
6244 + "@eslint-community/regexpp": "^4.11.0",
6245 + "eslint-compat-utils": "^0.5.1"
6246 },
6247 "engines": {
6248 "node": "^14.18.0 || >=16.0.0"
6249 },
5537 - "funding": {
5538 - "url": "https://opencollective.com/eslint-plugin-prettier"
5539 - },
6250 "peerDependencies": {
5541 - "@types/eslint": ">=8.0.0",
5542 - "eslint": ">=8.0.0",
5543 - "eslint-config-prettier": "*",
5544 - "prettier": ">=3.0.0"
5545 - },
5546 - "peerDependenciesMeta": {
5547 - "@types/eslint": {
5548 - "optional": true
5549 - },
5550 - "eslint-config-prettier": {
5551 - "optional": true
5552 - }
6251 + "eslint": ">=8"
6252 }
6253 },
5555 - "node_modules/eslint-plugin-prettier/node_modules/synckit": {
5556 - "version": "0.9.1",
5557 - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
5558 - "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==",
6254 + "node_modules/eslint-plugin-import-x": {
6255 + "version": "4.2.1",
6256 + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.2.1.tgz",
6257 + "integrity": "sha512-WWi2GedccIJa0zXxx3WDnTgouGQTtdYK1nhXMwywbqqAgB0Ov+p1pYBsWh3VaB0bvBOwLse6OfVII7jZD9xo5Q==",
6258 + "dev": true,
6259 "dependencies": {
5560 - "@pkgr/core": "^0.1.0",
5561 - "tslib": "^2.6.2"
6260 + "@typescript-eslint/utils": "^8.1.0",
6261 + "debug": "^4.3.4",
6262 + "doctrine": "^3.0.0",
6263 + "eslint-import-resolver-node": "^0.3.9",
6264 + "get-tsconfig": "^4.7.3",
6265 + "is-glob": "^4.0.3",
6266 + "minimatch": "^9.0.3",
6267 + "semver": "^7.6.3",
6268 + "stable-hash": "^0.0.4",
6269 + "tslib": "^2.6.3"
6270 },
6271 "engines": {
5564 - "node": "^14.18.0 || >=16.0.0"
6272 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6273 },
5566 - "funding": {
5567 - "url": "https://opencollective.com/unts"
6274 + "peerDependencies": {
6275 + "eslint": "^8.57.0 || ^9.0.0"
6276 }
6277 },
5570 - "node_modules/eslint-plugin-prettier/node_modules/tslib": {
6278 + "node_modules/eslint-plugin-import-x/node_modules/tslib": {
6279 "version": "2.7.0",
6280 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
5573 - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
6281 + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
6282 + "dev": true
6283 },
5575 - "node_modules/eslint-plugin-vue": {
5576 - "version": "9.28.0",
5577 - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz",
5578 - "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==",
6284 + "node_modules/eslint-plugin-jsdoc": {
6285 + "version": "50.2.4",
6286 + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.2.4.tgz",
6287 + "integrity": "sha512-020jA+dXaXdb+TML3ZJBvpPmzwbNROjnYuTYi/g6A5QEmEjhptz4oPJDKkOGMIByNxsPpdTLzSU1HYVqebOX1w==",
6288 + "dev": true,
6289 "dependencies": {
5580 - "@eslint-community/eslint-utils": "^4.4.0",
5581 - "globals": "^13.24.0",
5582 - "natural-compare": "^1.4.0",
5583 - "nth-check": "^2.1.1",
5584 - "postcss-selector-parser": "^6.0.15",
6290 + "@es-joy/jsdoccomment": "~0.48.0",
6291 + "are-docs-informative": "^0.0.2",
6292 + "comment-parser": "1.4.1",
6293 + "debug": "^4.3.6",
6294 + "escape-string-regexp": "^4.0.0",
6295 + "espree": "^10.1.0",
6296 + "esquery": "^1.6.0",
6297 + "parse-imports": "^2.1.1",
6298 "semver": "^7.6.3",
5586 - "vue-eslint-parser": "^9.4.3",
5587 - "xml-name-validator": "^4.0.0"
6299 + "spdx-expression-parse": "^4.0.0",
6300 + "synckit": "^0.9.1"
6301 },
6302 "engines": {
5590 - "node": "^14.17.0 || >=16.0.0"
6303 + "node": ">=18"
6304 },
6305 "peerDependencies": {
5593 - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
6306 + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
6307 }
6308 },
5596 - "node_modules/eslint-plugin-vue/node_modules/globals": {
5597 - "version": "13.24.0",
5598 - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
5599 - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
6309 + "node_modules/eslint-plugin-jsdoc/node_modules/synckit": {
6310 + "version": "0.9.1",
6311 + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
6312 + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==",
6313 + "dev": true,
6314 "dependencies": {
5601 - "type-fest": "^0.20.2"
6315 + "@pkgr/core": "^0.1.0",
6316 + "tslib": "^2.6.2"
6317 },
6318 "engines": {
5604 - "node": ">=8"
6319 + "node": "^14.18.0 || >=16.0.0"
6320 },
6321 "funding": {
5607 - "url": "https://github.com/sponsors/sindresorhus"
6322 + "url": "https://opencollective.com/unts"
6323 }
6324 },
5610 - "node_modules/eslint-plugin-vue/node_modules/type-fest": {
5611 - "version": "0.20.2",
5612 - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
5613 - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
5614 - "engines": {
5615 - "node": ">=10"
5616 - },
5617 - "funding": {
5618 - "url": "https://github.com/sponsors/sindresorhus"
5619 - }
6325 + "node_modules/eslint-plugin-jsdoc/node_modules/tslib": {
6326 + "version": "2.7.0",
6327 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
6328 + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
6329 + "dev": true
6330 },
5621 - "node_modules/eslint-scope": {
5622 - "version": "8.0.2",
5623 - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
5624 - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
6331 + "node_modules/eslint-plugin-jsonc": {
6332 + "version": "2.16.0",
6333 + "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz",
6334 + "integrity": "sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==",
6335 + "dev": true,
6336 "dependencies": {
5626 - "esrecurse": "^4.3.0",
5627 - "estraverse": "^5.2.0"
6337 + "@eslint-community/eslint-utils": "^4.2.0",
6338 + "eslint-compat-utils": "^0.5.0",
6339 + "espree": "^9.6.1",
6340 + "graphemer": "^1.4.0",
6341 + "jsonc-eslint-parser": "^2.0.4",
6342 + "natural-compare": "^1.4.0",
6343 + "synckit": "^0.6.0"
6344 },
6345 "engines": {
5630 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6346 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
6347 },
6348 "funding": {
5633 - "url": "https://opencollective.com/eslint"
6349 + "url": "https://github.com/sponsors/ota-meshi"
6350 + },
6351 + "peerDependencies": {
6352 + "eslint": ">=6.0.0"
6353 }
6354 },
5636 - "node_modules/eslint-visitor-keys": {
6355 + "node_modules/eslint-plugin-jsonc/node_modules/eslint-visitor-keys": {
6356 "version": "3.4.3",
6357 "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
6358 "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
6359 + "dev": true,
6360 "engines": {
6361 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
6362 },
@@ -5644,184 +6364,514 @@
6364 "url": "https://opencollective.com/eslint"
6365 }
6366 },
5647 - "node_modules/eslint/node_modules/@eslint/js": {
5648 - "version": "9.10.0",
5649 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
5650 - "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
5651 - "engines": {
5652 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
5653 - }
5654 - },
5655 - "node_modules/eslint/node_modules/ansi-styles": {
5656 - "version": "4.3.0",
5657 - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
5658 - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
6367 + "node_modules/eslint-plugin-jsonc/node_modules/espree": {
6368 + "version": "9.6.1",
6369 + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
6370 + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
6371 + "dev": true,
6372 "dependencies": {
5660 - "color-convert": "^2.0.1"
6373 + "acorn": "^8.9.0",
6374 + "acorn-jsx": "^5.3.2",
6375 + "eslint-visitor-keys": "^3.4.1"
6376 },
6377 "engines": {
5663 - "node": ">=8"
6378 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
6379 },
6380 "funding": {
5666 - "url": "https://github.com/chalk/ansi-styles?sponsor=1"
6381 + "url": "https://opencollective.com/eslint"
6382 }
6383 },
5669 - "node_modules/eslint/node_modules/brace-expansion": {
5670 - "version": "1.1.11",
5671 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
5672 - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
6384 + "node_modules/eslint-plugin-jsonc/node_modules/synckit": {
6385 + "version": "0.6.2",
6386 + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.6.2.tgz",
6387 + "integrity": "sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==",
6388 + "dev": true,
6389 "dependencies": {
5674 - "balanced-match": "^1.0.0",
5675 - "concat-map": "0.0.1"
6390 + "tslib": "^2.3.1"
6391 + },
6392 + "engines": {
6393 + "node": ">=12.20"
6394 }
6395 },
5678 - "node_modules/eslint/node_modules/chalk": {
5679 - "version": "4.1.2",
5680 - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
5681 - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
6396 + "node_modules/eslint-plugin-jsonc/node_modules/tslib": {
6397 + "version": "2.7.0",
6398 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
6399 + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
6400 + "dev": true
6401 + },
6402 + "node_modules/eslint-plugin-n": {
6403 + "version": "17.10.3",
6404 + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.3.tgz",
6405 + "integrity": "sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==",
6406 + "dev": true,
6407 "dependencies": {
5683 - "ansi-styles": "^4.1.0",
5684 - "supports-color": "^7.1.0"
6408 + "@eslint-community/eslint-utils": "^4.4.0",
6409 + "enhanced-resolve": "^5.17.0",
6410 + "eslint-plugin-es-x": "^7.5.0",
6411 + "get-tsconfig": "^4.7.0",
6412 + "globals": "^15.8.0",
6413 + "ignore": "^5.2.4",
6414 + "minimatch": "^9.0.5",
6415 + "semver": "^7.5.3"
6416 },
6417 "engines": {
5687 - "node": ">=10"
6418 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6419 },
6420 "funding": {
5690 - "url": "https://github.com/chalk/chalk?sponsor=1"
6421 + "url": "https://opencollective.com/eslint"
6422 + },
6423 + "peerDependencies": {
6424 + "eslint": ">=8.23.0"
6425 }
6426 },
5693 - "node_modules/eslint/node_modules/color-convert": {
5694 - "version": "2.0.1",
5695 - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
5696 - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
6427 + "node_modules/eslint-plugin-no-only-tests": {
6428 + "version": "3.3.0",
6429 + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz",
6430 + "integrity": "sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==",
6431 + "dev": true,
6432 + "engines": {
6433 + "node": ">=5.0.0"
6434 + }
6435 + },
6436 + "node_modules/eslint-plugin-perfectionist": {
6437 + "version": "3.6.0",
6438 + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-3.6.0.tgz",
6439 + "integrity": "sha512-sA6ljy6dL/9cM5ruZ/pMqRVt0FQ4Z7mbQWlBYpyX9941LVfm65d2jl2k1ZbWD3ud9Wm+/NKgOvRnAatsKhMJbA==",
6440 + "dev": true,
6441 "dependencies": {
5698 - "color-name": "~1.1.4"
6442 + "@typescript-eslint/types": "^8.5.0",
6443 + "@typescript-eslint/utils": "^8.5.0",
6444 + "minimatch": "^9.0.5",
6445 + "natural-compare-lite": "^1.4.0"
6446 },
6447 "engines": {
5701 - "node": ">=7.0.0"
6448 + "node": "^18.0.0 || >=20.0.0"
6449 + },
6450 + "peerDependencies": {
6451 + "astro-eslint-parser": "^1.0.2",
6452 + "eslint": ">=8.0.0",
6453 + "svelte": ">=3.0.0",
6454 + "svelte-eslint-parser": "^0.41.1",
6455 + "vue-eslint-parser": ">=9.0.0"
6456 + },
6457 + "peerDependenciesMeta": {
6458 + "astro-eslint-parser": {
6459 + "optional": true
6460 + },
6461 + "svelte": {
6462 + "optional": true
6463 + },
6464 + "svelte-eslint-parser": {
6465 + "optional": true
6466 + },
6467 + "vue-eslint-parser": {
6468 + "optional": true
6469 + }
6470 }
6471 },
5704 - "node_modules/eslint/node_modules/color-name": {
5705 - "version": "1.1.4",
5706 - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
5707 - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
5708 - },
5709 - "node_modules/eslint/node_modules/escape-string-regexp": {
5710 - "version": "4.0.0",
5711 - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
5712 - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
6472 + "node_modules/eslint-plugin-prettier": {
6473 + "version": "5.2.1",
6474 + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
6475 + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==",
6476 + "dependencies": {
6477 + "prettier-linter-helpers": "^1.0.0",
6478 + "synckit": "^0.9.1"
6479 + },
6480 "engines": {
5714 - "node": ">=10"
6481 + "node": "^14.18.0 || >=16.0.0"
6482 },
6483 "funding": {
5717 - "url": "https://github.com/sponsors/sindresorhus"
6484 + "url": "https://opencollective.com/eslint-plugin-prettier"
6485 + },
6486 + "peerDependencies": {
6487 + "@types/eslint": ">=8.0.0",
6488 + "eslint": ">=8.0.0",
6489 + "eslint-config-prettier": "*",
6490 + "prettier": ">=3.0.0"
6491 + },
6492 + "peerDependenciesMeta": {
6493 + "@types/eslint": {
6494 + "optional": true
6495 + },
6496 + "eslint-config-prettier": {
6497 + "optional": true
6498 + }
6499 }
6500 },
5720 - "node_modules/eslint/node_modules/eslint-visitor-keys": {
5721 - "version": "4.0.0",
5722 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
5723 - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
6501 + "node_modules/eslint-plugin-prettier/node_modules/synckit": {
6502 + "version": "0.9.1",
6503 + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
6504 + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==",
6505 + "dependencies": {
6506 + "@pkgr/core": "^0.1.0",
6507 + "tslib": "^2.6.2"
6508 + },
6509 "engines": {
5725 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6510 + "node": "^14.18.0 || >=16.0.0"
6511 },
6512 "funding": {
5728 - "url": "https://opencollective.com/eslint"
6513 + "url": "https://opencollective.com/unts"
6514 }
6515 },
5731 - "node_modules/eslint/node_modules/has-flag": {
5732 - "version": "4.0.0",
5733 - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
5734 - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
5735 - "engines": {
5736 - "node": ">=8"
5737 - }
6516 + "node_modules/eslint-plugin-prettier/node_modules/tslib": {
6517 + "version": "2.7.0",
6518 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
6519 + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
6520 },
5739 - "node_modules/eslint/node_modules/minimatch": {
5740 - "version": "3.1.2",
5741 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
5742 - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
6521 + "node_modules/eslint-plugin-regexp": {
6522 + "version": "2.6.0",
6523 + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.6.0.tgz",
6524 + "integrity": "sha512-FCL851+kislsTEQEMioAlpDuK5+E5vs0hi1bF8cFlPlHcEjeRhuAzEsGikXRreE+0j4WhW2uO54MqTjXtYOi3A==",
6525 + "dev": true,
6526 "dependencies": {
5744 - "brace-expansion": "^1.1.7"
6527 + "@eslint-community/eslint-utils": "^4.2.0",
6528 + "@eslint-community/regexpp": "^4.9.1",
6529 + "comment-parser": "^1.4.0",
6530 + "jsdoc-type-pratt-parser": "^4.0.0",
6531 + "refa": "^0.12.1",
6532 + "regexp-ast-analysis": "^0.7.1",
6533 + "scslre": "^0.3.0"
6534 },
6535 "engines": {
5747 - "node": "*"
6536 + "node": "^18 || >=20"
6537 + },
6538 + "peerDependencies": {
6539 + "eslint": ">=8.44.0"
6540 }
6541 },
5750 - "node_modules/eslint/node_modules/supports-color": {
5751 - "version": "7.2.0",
5752 - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
5753 - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
6542 + "node_modules/eslint-plugin-toml": {
6543 + "version": "0.11.1",
6544 + "resolved": "https://registry.npmjs.org/eslint-plugin-toml/-/eslint-plugin-toml-0.11.1.tgz",
6545 + "integrity": "sha512-Y1WuMSzfZpeMIrmlP1nUh3kT8p96mThIq4NnHrYUhg10IKQgGfBZjAWnrg9fBqguiX4iFps/x/3Hb5TxBisfdw==",
6546 + "dev": true,
6547 "dependencies": {
5755 - "has-flag": "^4.0.0"
6548 + "debug": "^4.1.1",
6549 + "eslint-compat-utils": "^0.5.0",
6550 + "lodash": "^4.17.19",
6551 + "toml-eslint-parser": "^0.10.0"
6552 },
6553 "engines": {
5758 - "node": ">=8"
6554 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
6555 + },
6556 + "funding": {
6557 + "url": "https://github.com/sponsors/ota-meshi"
6558 + },
6559 + "peerDependencies": {
6560 + "eslint": ">=6.0.0"
6561 }
6562 },
5761 - "node_modules/espree": {
5762 - "version": "10.1.0",
5763 - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
5764 - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
6563 + "node_modules/eslint-plugin-unicorn": {
6564 + "version": "55.0.0",
6565 + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz",
6566 + "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==",
6567 + "dev": true,
6568 "dependencies": {
5766 - "acorn": "^8.12.0",
5767 - "acorn-jsx": "^5.3.2",
5768 - "eslint-visitor-keys": "^4.0.0"
6569 + "@babel/helper-validator-identifier": "^7.24.5",
6570 + "@eslint-community/eslint-utils": "^4.4.0",
6571 + "ci-info": "^4.0.0",
6572 + "clean-regexp": "^1.0.0",
6573 + "core-js-compat": "^3.37.0",
6574 + "esquery": "^1.5.0",
6575 + "globals": "^15.7.0",
6576 + "indent-string": "^4.0.0",
6577 + "is-builtin-module": "^3.2.1",
6578 + "jsesc": "^3.0.2",
6579 + "pluralize": "^8.0.0",
6580 + "read-pkg-up": "^7.0.1",
6581 + "regexp-tree": "^0.1.27",
6582 + "regjsparser": "^0.10.0",
6583 + "semver": "^7.6.1",
6584 + "strip-indent": "^3.0.0"
6585 },
6586 "engines": {
5771 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6587 + "node": ">=18.18"
6588 },
6589 "funding": {
5774 - "url": "https://opencollective.com/eslint"
6590 + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
6591 + },
6592 + "peerDependencies": {
6593 + "eslint": ">=8.56.0"
6594 }
6595 },
5777 - "node_modules/espree/node_modules/eslint-visitor-keys": {
5778 - "version": "4.0.0",
5779 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
5780 - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
6596 + "node_modules/eslint-plugin-unicorn/node_modules/jsesc": {
6597 + "version": "3.0.2",
6598 + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
6599 + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
6600 + "dev": true,
6601 + "bin": {
6602 + "jsesc": "bin/jsesc"
6603 + },
6604 "engines": {
5782 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6605 + "node": ">=6"
6606 + }
6607 + },
6608 + "node_modules/eslint-plugin-unused-imports": {
6609 + "version": "4.1.4",
6610 + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz",
6611 + "integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==",
6612 + "dev": true,
6613 + "peerDependencies": {
6614 + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
6615 + "eslint": "^9.0.0 || ^8.0.0"
6616 },
5784 - "funding": {
5785 - "url": "https://opencollective.com/eslint"
6617 + "peerDependenciesMeta": {
6618 + "@typescript-eslint/eslint-plugin": {
6619 + "optional": true
6620 + }
6621 }
6622 },
5788 - "node_modules/esquery": {
5789 - "version": "1.6.0",
5790 - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
5791 - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
6623 + "node_modules/eslint-plugin-vue": {
6624 + "version": "9.28.0",
6625 + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz",
6626 + "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==",
6627 "dependencies": {
5793 - "estraverse": "^5.1.0"
6628 + "@eslint-community/eslint-utils": "^4.4.0",
6629 + "globals": "^13.24.0",
6630 + "natural-compare": "^1.4.0",
6631 + "nth-check": "^2.1.1",
6632 + "postcss-selector-parser": "^6.0.15",
6633 + "semver": "^7.6.3",
6634 + "vue-eslint-parser": "^9.4.3",
6635 + "xml-name-validator": "^4.0.0"
6636 },
6637 "engines": {
5796 - "node": ">=0.10"
6638 + "node": "^14.17.0 || >=16.0.0"
6639 + },
6640 + "peerDependencies": {
6641 + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
6642 }
6643 },
5799 - "node_modules/esrecurse": {
5800 - "version": "4.3.0",
5801 - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
5802 - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
6644 + "node_modules/eslint-plugin-vue/node_modules/globals": {
6645 + "version": "13.24.0",
6646 + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
6647 + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
6648 "dependencies": {
5804 - "estraverse": "^5.2.0"
6649 + "type-fest": "^0.20.2"
6650 },
6651 "engines": {
5807 - "node": ">=4.0"
6652 + "node": ">=8"
6653 + },
6654 + "funding": {
6655 + "url": "https://github.com/sponsors/sindresorhus"
6656 }
6657 },
5810 - "node_modules/estraverse": {
5811 - "version": "5.3.0",
5812 - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
5813 - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
6658 + "node_modules/eslint-plugin-vue/node_modules/type-fest": {
6659 + "version": "0.20.2",
6660 + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
6661 + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
6662 "engines": {
5815 - "node": ">=4.0"
6663 + "node": ">=10"
6664 + },
6665 + "funding": {
6666 + "url": "https://github.com/sponsors/sindresorhus"
6667 }
6668 },
5818 - "node_modules/estree-walker": {
5819 - "version": "2.0.2",
5820 - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
5821 - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
5822 - },
5823 - "node_modules/esutils": {
5824 - "version": "2.0.3",
6669 + "node_modules/eslint-plugin-yml": {
6670 + "version": "1.14.0",
6671 + "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.14.0.tgz",
6672 + "integrity": "sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==",
6673 + "dev": true,
6674 + "dependencies": {
6675 + "debug": "^4.3.2",
6676 + "eslint-compat-utils": "^0.5.0",
6677 + "lodash": "^4.17.21",
6678 + "natural-compare": "^1.4.0",
6679 + "yaml-eslint-parser": "^1.2.1"
6680 + },
6681 + "engines": {
6682 + "node": "^14.17.0 || >=16.0.0"
6683 + },
6684 + "funding": {
6685 + "url": "https://github.com/sponsors/ota-meshi"
6686 + },
6687 + "peerDependencies": {
6688 + "eslint": ">=6.0.0"
6689 + }
6690 + },
6691 + "node_modules/eslint-processor-vue-blocks": {
6692 + "version": "0.1.2",
6693 + "resolved": "https://registry.npmjs.org/eslint-processor-vue-blocks/-/eslint-processor-vue-blocks-0.1.2.tgz",
6694 + "integrity": "sha512-PfpJ4uKHnqeL/fXUnzYkOax3aIenlwewXRX8jFinA1a2yCFnLgMuiH3xvCgvHHUlV2xJWQHbCTdiJWGwb3NqpQ==",
6695 + "dev": true,
6696 + "funding": {
6697 + "url": "https://github.com/sponsors/antfu"
6698 + },
6699 + "peerDependencies": {
6700 + "@vue/compiler-sfc": "^3.3.0",
6701 + "eslint": "^8.50.0 || ^9.0.0"
6702 + }
6703 + },
6704 + "node_modules/eslint-scope": {
6705 + "version": "8.0.2",
6706 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
6707 + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
6708 + "dependencies": {
6709 + "esrecurse": "^4.3.0",
6710 + "estraverse": "^5.2.0"
6711 + },
6712 + "engines": {
6713 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6714 + },
6715 + "funding": {
6716 + "url": "https://opencollective.com/eslint"
6717 + }
6718 + },
6719 + "node_modules/eslint-visitor-keys": {
6720 + "version": "4.0.0",
6721 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
6722 + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
6723 + "engines": {
6724 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6725 + },
6726 + "funding": {
6727 + "url": "https://opencollective.com/eslint"
6728 + }
6729 + },
6730 + "node_modules/eslint/node_modules/@eslint/js": {
6731 + "version": "9.11.0",
6732 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.0.tgz",
6733 + "integrity": "sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==",
6734 + "engines": {
6735 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6736 + }
6737 + },
6738 + "node_modules/eslint/node_modules/ansi-styles": {
6739 + "version": "4.3.0",
6740 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
6741 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
6742 + "dependencies": {
6743 + "color-convert": "^2.0.1"
6744 + },
6745 + "engines": {
6746 + "node": ">=8"
6747 + },
6748 + "funding": {
6749 + "url": "https://github.com/chalk/ansi-styles?sponsor=1"
6750 + }
6751 + },
6752 + "node_modules/eslint/node_modules/brace-expansion": {
6753 + "version": "1.1.11",
6754 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
6755 + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
6756 + "dependencies": {
6757 + "balanced-match": "^1.0.0",
6758 + "concat-map": "0.0.1"
6759 + }
6760 + },
6761 + "node_modules/eslint/node_modules/chalk": {
6762 + "version": "4.1.2",
6763 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
6764 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
6765 + "dependencies": {
6766 + "ansi-styles": "^4.1.0",
6767 + "supports-color": "^7.1.0"
6768 + },
6769 + "engines": {
6770 + "node": ">=10"
6771 + },
6772 + "funding": {
6773 + "url": "https://github.com/chalk/chalk?sponsor=1"
6774 + }
6775 + },
6776 + "node_modules/eslint/node_modules/color-convert": {
6777 + "version": "2.0.1",
6778 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
6779 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
6780 + "dependencies": {
6781 + "color-name": "~1.1.4"
6782 + },
6783 + "engines": {
6784 + "node": ">=7.0.0"
6785 + }
6786 + },
6787 + "node_modules/eslint/node_modules/color-name": {
6788 + "version": "1.1.4",
6789 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
6790 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
6791 + },
6792 + "node_modules/eslint/node_modules/has-flag": {
6793 + "version": "4.0.0",
6794 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
6795 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
6796 + "engines": {
6797 + "node": ">=8"
6798 + }
6799 + },
6800 + "node_modules/eslint/node_modules/minimatch": {
6801 + "version": "3.1.2",
6802 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
6803 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
6804 + "dependencies": {
6805 + "brace-expansion": "^1.1.7"
6806 + },
6807 + "engines": {
6808 + "node": "*"
6809 + }
6810 + },
6811 + "node_modules/eslint/node_modules/supports-color": {
6812 + "version": "7.2.0",
6813 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
6814 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
6815 + "dependencies": {
6816 + "has-flag": "^4.0.0"
6817 + },
6818 + "engines": {
6819 + "node": ">=8"
6820 + }
6821 + },
6822 + "node_modules/espree": {
6823 + "version": "10.1.0",
6824 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
6825 + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
6826 + "dependencies": {
6827 + "acorn": "^8.12.0",
6828 + "acorn-jsx": "^5.3.2",
6829 + "eslint-visitor-keys": "^4.0.0"
6830 + },
6831 + "engines": {
6832 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
6833 + },
6834 + "funding": {
6835 + "url": "https://opencollective.com/eslint"
6836 + }
6837 + },
6838 + "node_modules/esquery": {
6839 + "version": "1.6.0",
6840 + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
6841 + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
6842 + "dependencies": {
6843 + "estraverse": "^5.1.0"
6844 + },
6845 + "engines": {
6846 + "node": ">=0.10"
6847 + }
6848 + },
6849 + "node_modules/esrecurse": {
6850 + "version": "4.3.0",
6851 + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
6852 + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
6853 + "dependencies": {
6854 + "estraverse": "^5.2.0"
6855 + },
6856 + "engines": {
6857 + "node": ">=4.0"
6858 + }
6859 + },
6860 + "node_modules/estraverse": {
6861 + "version": "5.3.0",
6862 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
6863 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
6864 + "engines": {
6865 + "node": ">=4.0"
6866 + }
6867 + },
6868 + "node_modules/estree-walker": {
6869 + "version": "2.0.2",
6870 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
6871 + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
6872 + },
6873 + "node_modules/esutils": {
6874 + "version": "2.0.3",
6875 "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
6876 "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
6877 "engines": {
@@ -6002,6 +7052,15 @@
7052 "url": "https://github.com/sponsors/sindresorhus"
7053 }
7054 },
7055 + "node_modules/figures/node_modules/escape-string-regexp": {
7056 + "version": "1.0.5",
7057 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
7058 + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
7059 + "dev": true,
7060 + "engines": {
7061 + "node": ">=0.8.0"
7062 + }
7063 + },
7064 "node_modules/file-entry-cache": {
7065 "version": "8.0.0",
7066 "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -6044,6 +7103,18 @@
7103 "url": "https://github.com/sponsors/sindresorhus"
7104 }
7105 },
7106 + "node_modules/find-up-simple": {
7107 + "version": "1.0.0",
7108 + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz",
7109 + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
7110 + "dev": true,
7111 + "engines": {
7112 + "node": ">=18"
7113 + },
7114 + "funding": {
7115 + "url": "https://github.com/sponsors/sindresorhus"
7116 + }
7117 + },
7118 "node_modules/flat-cache": {
7119 "version": "4.0.1",
7120 "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -6221,8 +7292,7 @@
7292 "node_modules/fs.realpath": {
7293 "version": "1.0.0",
7294 "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
6224 - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
6225 - "dev": true
7295 + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
7296 },
7297 "node_modules/fsevents": {
7298 "version": "2.3.3",
@@ -6385,21 +7455,6 @@
7455 "node": ">=10.13.0"
7456 }
7457 },
6388 - "node_modules/glob/node_modules/minimatch": {
6389 - "version": "9.0.5",
6390 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
6391 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
6392 - "dev": true,
6393 - "dependencies": {
6394 - "brace-expansion": "^2.0.1"
6395 - },
6396 - "engines": {
6397 - "node": ">=16 || 14 >=14.17"
6398 - },
6399 - "funding": {
6400 - "url": "https://github.com/sponsors/isaacs"
6401 - }
6402 - },
7458 "node_modules/global-dirs": {
7459 "version": "3.0.1",
7460 "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz",
@@ -6428,19 +7483,19 @@
7483 }
7484 },
7485 "node_modules/globby": {
6431 - "version": "11.1.0",
6432 - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
6433 - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
7486 + "version": "14.0.2",
7487 + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz",
7488 + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==",
7489 "dependencies": {
6435 - "array-union": "^2.1.0",
6436 - "dir-glob": "^3.0.1",
6437 - "fast-glob": "^3.2.9",
6438 - "ignore": "^5.2.0",
6439 - "merge2": "^1.4.1",
6440 - "slash": "^3.0.0"
7490 + "@sindresorhus/merge-streams": "^2.1.0",
7491 + "fast-glob": "^3.3.2",
7492 + "ignore": "^5.2.4",
7493 + "path-type": "^5.0.0",
7494 + "slash": "^5.1.0",
7495 + "unicorn-magic": "^0.1.0"
7496 },
7497 "engines": {
6443 - "node": ">=10"
7498 + "node": ">=18"
7499 },
7500 "funding": {
7501 "url": "https://github.com/sponsors/sindresorhus"
@@ -6546,9 +7601,9 @@
7601 }
7602 },
7603 "node_modules/hast-util-to-html": {
6549 - "version": "9.0.2",
6550 - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.2.tgz",
6551 - "integrity": "sha512-RP5wNpj5nm1Z8cloDv4Sl4RS8jH5HYa0v93YB6Wb4poEzgMo/dAAL0KcT4974dCjcNG5pkLqTImeFHHCwwfY3g==",
7604 + "version": "9.0.3",
7605 + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz",
7606 + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==",
7607 "dependencies": {
7608 "@types/hast": "^3.0.0",
7609 "@types/unist": "^3.0.0",
@@ -6606,6 +7661,12 @@
7661 "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
7662 "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
7663 },
7664 + "node_modules/hosted-git-info": {
7665 + "version": "2.8.9",
7666 + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
7667 + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
7668 + "dev": true
7669 + },
7670 "node_modules/html-encoding-sniffer": {
7671 "version": "4.0.0",
7672 "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -6899,7 +7960,6 @@
7960 "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
7961 "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
7962 "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
6902 - "dev": true,
7963 "dependencies": {
7964 "once": "^1.3.0",
7965 "wrappy": "1"
@@ -6908,8 +7968,7 @@
7968 "node_modules/inherits": {
7969 "version": "2.0.4",
7970 "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
6911 - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
6912 - "dev": true
7971 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
7972 },
7973 "node_modules/ini": {
7974 "version": "2.0.0",
@@ -6920,6 +7979,12 @@
7979 "node": ">=10"
7980 }
7981 },
7982 + "node_modules/is-arrayish": {
7983 + "version": "0.2.1",
7984 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
7985 + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
7986 + "dev": true
7987 + },
7988 "node_modules/is-binary-path": {
7989 "version": "2.1.0",
7990 "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -6931,10 +7996,25 @@
7996 "node": ">=8"
7997 }
7998 },
6934 - "node_modules/is-ci": {
6935 - "version": "3.0.1",
6936 - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
6937 - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
7999 + "node_modules/is-builtin-module": {
8000 + "version": "3.2.1",
8001 + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
8002 + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
8003 + "dev": true,
8004 + "dependencies": {
8005 + "builtin-modules": "^3.3.0"
8006 + },
8007 + "engines": {
8008 + "node": ">=6"
8009 + },
8010 + "funding": {
8011 + "url": "https://github.com/sponsors/sindresorhus"
8012 + }
8013 + },
8014 + "node_modules/is-ci": {
8015 + "version": "3.0.1",
8016 + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
8017 + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
8018 "dev": true,
8019 "dependencies": {
8020 "ci-info": "^3.2.0"
@@ -6943,6 +8023,21 @@
8023 "is-ci": "bin.js"
8024 }
8025 },
8026 + "node_modules/is-ci/node_modules/ci-info": {
8027 + "version": "3.9.0",
8028 + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
8029 + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
8030 + "dev": true,
8031 + "funding": [
8032 + {
8033 + "type": "github",
8034 + "url": "https://github.com/sponsors/sibiraj-s"
8035 + }
8036 + ],
8037 + "engines": {
8038 + "node": ">=8"
8039 + }
8040 + },
8041 "node_modules/is-core-module": {
8042 "version": "2.15.1",
8043 "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
@@ -7199,9 +8294,9 @@
8294 }
8295 },
8296 "node_modules/jose": {
7202 - "version": "5.9.0",
7203 - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.0.tgz",
7204 - "integrity": "sha512-i7z1nVNeS4rvKGp6SePhUPJ1TQUBR7p8u4aBxxdEaMiRKO5fCfcT/mSMeOsqrb40NxPhoHhTyq5CcC4imLBFcg==",
8297 + "version": "5.9.2",
8298 + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.2.tgz",
8299 + "integrity": "sha512-ILI2xx/I57b20sd7rHZvgiiQrmp2mcotwsAH+5ajbpFQbrYVQdNHYlQhoA5cFb78CgtBOxtC05TeA+mcgkuCqQ==",
8300 "funding": {
8301 "url": "https://github.com/sponsors/panva"
8302 }
@@ -7263,6 +8358,15 @@
8358 "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
8359 "dev": true
8360 },
8361 + "node_modules/jsdoc-type-pratt-parser": {
8362 + "version": "4.1.0",
8363 + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
8364 + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
8365 + "dev": true,
8366 + "engines": {
8367 + "node": ">=12.0.0"
8368 + }
8369 + },
8370 "node_modules/jsdom": {
8371 "version": "25.0.0",
8372 "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.0.tgz",
@@ -7370,6 +8474,53 @@
8474 "node": ">=6"
8475 }
8476 },
8477 + "node_modules/jsonc-eslint-parser": {
8478 + "version": "2.4.0",
8479 + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz",
8480 + "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==",
8481 + "dev": true,
8482 + "dependencies": {
8483 + "acorn": "^8.5.0",
8484 + "eslint-visitor-keys": "^3.0.0",
8485 + "espree": "^9.0.0",
8486 + "semver": "^7.3.5"
8487 + },
8488 + "engines": {
8489 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
8490 + },
8491 + "funding": {
8492 + "url": "https://github.com/sponsors/ota-meshi"
8493 + }
8494 + },
8495 + "node_modules/jsonc-eslint-parser/node_modules/eslint-visitor-keys": {
8496 + "version": "3.4.3",
8497 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
8498 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
8499 + "dev": true,
8500 + "engines": {
8501 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
8502 + },
8503 + "funding": {
8504 + "url": "https://opencollective.com/eslint"
8505 + }
8506 + },
8507 + "node_modules/jsonc-eslint-parser/node_modules/espree": {
8508 + "version": "9.6.1",
8509 + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
8510 + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
8511 + "dev": true,
8512 + "dependencies": {
8513 + "acorn": "^8.9.0",
8514 + "acorn-jsx": "^5.3.2",
8515 + "eslint-visitor-keys": "^3.4.1"
8516 + },
8517 + "engines": {
8518 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
8519 + },
8520 + "funding": {
8521 + "url": "https://opencollective.com/eslint"
8522 + }
8523 + },
8524 "node_modules/jsonfile": {
8525 "version": "6.1.0",
8526 "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -7941,6 +9092,16 @@
9092 "node": ">=8"
9093 }
9094 },
9095 + "node_modules/longest-streak": {
9096 + "version": "3.1.0",
9097 + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
9098 + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
9099 + "dev": true,
9100 + "funding": {
9101 + "type": "github",
9102 + "url": "https://github.com/sponsors/wooorm"
9103 + }
9104 + },
9105 "node_modules/loupe": {
9106 "version": "3.1.1",
9107 "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz",
@@ -7996,6 +9157,183 @@
9157 "markdown-it": "bin/markdown-it.mjs"
9158 }
9159 },
9160 + "node_modules/markdown-table": {
9161 + "version": "3.0.3",
9162 + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
9163 + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==",
9164 + "dev": true,
9165 + "funding": {
9166 + "type": "github",
9167 + "url": "https://github.com/sponsors/wooorm"
9168 + }
9169 + },
9170 + "node_modules/mdast-util-find-and-replace": {
9171 + "version": "3.0.1",
9172 + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz",
9173 + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==",
9174 + "dev": true,
9175 + "dependencies": {
9176 + "@types/mdast": "^4.0.0",
9177 + "escape-string-regexp": "^5.0.0",
9178 + "unist-util-is": "^6.0.0",
9179 + "unist-util-visit-parents": "^6.0.0"
9180 + },
9181 + "funding": {
9182 + "type": "opencollective",
9183 + "url": "https://opencollective.com/unified"
9184 + }
9185 + },
9186 + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
9187 + "version": "5.0.0",
9188 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
9189 + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
9190 + "dev": true,
9191 + "engines": {
9192 + "node": ">=12"
9193 + },
9194 + "funding": {
9195 + "url": "https://github.com/sponsors/sindresorhus"
9196 + }
9197 + },
9198 + "node_modules/mdast-util-from-markdown": {
9199 + "version": "2.0.1",
9200 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz",
9201 + "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==",
9202 + "dev": true,
9203 + "dependencies": {
9204 + "@types/mdast": "^4.0.0",
9205 + "@types/unist": "^3.0.0",
9206 + "decode-named-character-reference": "^1.0.0",
9207 + "devlop": "^1.0.0",
9208 + "mdast-util-to-string": "^4.0.0",
9209 + "micromark": "^4.0.0",
9210 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
9211 + "micromark-util-decode-string": "^2.0.0",
9212 + "micromark-util-normalize-identifier": "^2.0.0",
9213 + "micromark-util-symbol": "^2.0.0",
9214 + "micromark-util-types": "^2.0.0",
9215 + "unist-util-stringify-position": "^4.0.0"
9216 + },
9217 + "funding": {
9218 + "type": "opencollective",
9219 + "url": "https://opencollective.com/unified"
9220 + }
9221 + },
9222 + "node_modules/mdast-util-gfm": {
9223 + "version": "3.0.0",
9224 + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz",
9225 + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==",
9226 + "dev": true,
9227 + "dependencies": {
9228 + "mdast-util-from-markdown": "^2.0.0",
9229 + "mdast-util-gfm-autolink-literal": "^2.0.0",
9230 + "mdast-util-gfm-footnote": "^2.0.0",
9231 + "mdast-util-gfm-strikethrough": "^2.0.0",
9232 + "mdast-util-gfm-table": "^2.0.0",
9233 + "mdast-util-gfm-task-list-item": "^2.0.0",
9234 + "mdast-util-to-markdown": "^2.0.0"
9235 + },
9236 + "funding": {
9237 + "type": "opencollective",
9238 + "url": "https://opencollective.com/unified"
9239 + }
9240 + },
9241 + "node_modules/mdast-util-gfm-autolink-literal": {
9242 + "version": "2.0.1",
9243 + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
9244 + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
9245 + "dev": true,
9246 + "dependencies": {
9247 + "@types/mdast": "^4.0.0",
9248 + "ccount": "^2.0.0",
9249 + "devlop": "^1.0.0",
9250 + "mdast-util-find-and-replace": "^3.0.0",
9251 + "micromark-util-character": "^2.0.0"
9252 + },
9253 + "funding": {
9254 + "type": "opencollective",
9255 + "url": "https://opencollective.com/unified"
9256 + }
9257 + },
9258 + "node_modules/mdast-util-gfm-footnote": {
9259 + "version": "2.0.0",
9260 + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz",
9261 + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==",
9262 + "dev": true,
9263 + "dependencies": {
9264 + "@types/mdast": "^4.0.0",
9265 + "devlop": "^1.1.0",
9266 + "mdast-util-from-markdown": "^2.0.0",
9267 + "mdast-util-to-markdown": "^2.0.0",
9268 + "micromark-util-normalize-identifier": "^2.0.0"
9269 + },
9270 + "funding": {
9271 + "type": "opencollective",
9272 + "url": "https://opencollective.com/unified"
9273 + }
9274 + },
9275 + "node_modules/mdast-util-gfm-strikethrough": {
9276 + "version": "2.0.0",
9277 + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
9278 + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
9279 + "dev": true,
9280 + "dependencies": {
9281 + "@types/mdast": "^4.0.0",
9282 + "mdast-util-from-markdown": "^2.0.0",
9283 + "mdast-util-to-markdown": "^2.0.0"
9284 + },
9285 + "funding": {
9286 + "type": "opencollective",
9287 + "url": "https://opencollective.com/unified"
9288 + }
9289 + },
9290 + "node_modules/mdast-util-gfm-table": {
9291 + "version": "2.0.0",
9292 + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
9293 + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
9294 + "dev": true,
9295 + "dependencies": {
9296 + "@types/mdast": "^4.0.0",
9297 + "devlop": "^1.0.0",
9298 + "markdown-table": "^3.0.0",
9299 + "mdast-util-from-markdown": "^2.0.0",
9300 + "mdast-util-to-markdown": "^2.0.0"
9301 + },
9302 + "funding": {
9303 + "type": "opencollective",
9304 + "url": "https://opencollective.com/unified"
9305 + }
9306 + },
9307 + "node_modules/mdast-util-gfm-task-list-item": {
9308 + "version": "2.0.0",
9309 + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
9310 + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
9311 + "dev": true,
9312 + "dependencies": {
9313 + "@types/mdast": "^4.0.0",
9314 + "devlop": "^1.0.0",
9315 + "mdast-util-from-markdown": "^2.0.0",
9316 + "mdast-util-to-markdown": "^2.0.0"
9317 + },
9318 + "funding": {
9319 + "type": "opencollective",
9320 + "url": "https://opencollective.com/unified"
9321 + }
9322 + },
9323 + "node_modules/mdast-util-phrasing": {
9324 + "version": "4.1.0",
9325 + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
9326 + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
9327 + "dev": true,
9328 + "dependencies": {
9329 + "@types/mdast": "^4.0.0",
9330 + "unist-util-is": "^6.0.0"
9331 + },
9332 + "funding": {
9333 + "type": "opencollective",
9334 + "url": "https://opencollective.com/unified"
9335 + }
9336 + },
9337 "node_modules/mdast-util-to-hast": {
9338 "version": "13.2.0",
9339 "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
@@ -8016,6 +9354,39 @@
9354 "url": "https://opencollective.com/unified"
9355 }
9356 },
9357 + "node_modules/mdast-util-to-markdown": {
9358 + "version": "2.1.0",
9359 + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
9360 + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
9361 + "dev": true,
9362 + "dependencies": {
9363 + "@types/mdast": "^4.0.0",
9364 + "@types/unist": "^3.0.0",
9365 + "longest-streak": "^3.0.0",
9366 + "mdast-util-phrasing": "^4.0.0",
9367 + "mdast-util-to-string": "^4.0.0",
9368 + "micromark-util-decode-string": "^2.0.0",
9369 + "unist-util-visit": "^5.0.0",
9370 + "zwitch": "^2.0.0"
9371 + },
9372 + "funding": {
9373 + "type": "opencollective",
9374 + "url": "https://opencollective.com/unified"
9375 + }
9376 + },
9377 + "node_modules/mdast-util-to-string": {
9378 + "version": "4.0.0",
9379 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
9380 + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
9381 + "dev": true,
9382 + "dependencies": {
9383 + "@types/mdast": "^4.0.0"
9384 + },
9385 + "funding": {
9386 + "type": "opencollective",
9387 + "url": "https://opencollective.com/unified"
9388 + }
9389 + },
9390 "node_modules/mdn-data": {
9391 "version": "2.0.30",
9392 "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -8058,19 +9429,468 @@
9429 "node": ">= 8"
9430 }
9431 },
8061 - "node_modules/methods": {
8062 - "version": "1.1.2",
8063 - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
8064 - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
9432 + "node_modules/methods": {
9433 + "version": "1.1.2",
9434 + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
9435 + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
9436 + "dev": true,
9437 + "engines": {
9438 + "node": ">= 0.6"
9439 + }
9440 + },
9441 + "node_modules/micromark": {
9442 + "version": "4.0.0",
9443 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
9444 + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
9445 + "dev": true,
9446 + "funding": [
9447 + {
9448 + "type": "GitHub Sponsors",
9449 + "url": "https://github.com/sponsors/unifiedjs"
9450 + },
9451 + {
9452 + "type": "OpenCollective",
9453 + "url": "https://opencollective.com/unified"
9454 + }
9455 + ],
9456 + "dependencies": {
9457 + "@types/debug": "^4.0.0",
9458 + "debug": "^4.0.0",
9459 + "decode-named-character-reference": "^1.0.0",
9460 + "devlop": "^1.0.0",
9461 + "micromark-core-commonmark": "^2.0.0",
9462 + "micromark-factory-space": "^2.0.0",
9463 + "micromark-util-character": "^2.0.0",
9464 + "micromark-util-chunked": "^2.0.0",
9465 + "micromark-util-combine-extensions": "^2.0.0",
9466 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
9467 + "micromark-util-encode": "^2.0.0",
9468 + "micromark-util-normalize-identifier": "^2.0.0",
9469 + "micromark-util-resolve-all": "^2.0.0",
9470 + "micromark-util-sanitize-uri": "^2.0.0",
9471 + "micromark-util-subtokenize": "^2.0.0",
9472 + "micromark-util-symbol": "^2.0.0",
9473 + "micromark-util-types": "^2.0.0"
9474 + }
9475 + },
9476 + "node_modules/micromark-core-commonmark": {
9477 + "version": "2.0.1",
9478 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz",
9479 + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==",
9480 + "dev": true,
9481 + "funding": [
9482 + {
9483 + "type": "GitHub Sponsors",
9484 + "url": "https://github.com/sponsors/unifiedjs"
9485 + },
9486 + {
9487 + "type": "OpenCollective",
9488 + "url": "https://opencollective.com/unified"
9489 + }
9490 + ],
9491 + "dependencies": {
9492 + "decode-named-character-reference": "^1.0.0",
9493 + "devlop": "^1.0.0",
9494 + "micromark-factory-destination": "^2.0.0",
9495 + "micromark-factory-label": "^2.0.0",
9496 + "micromark-factory-space": "^2.0.0",
9497 + "micromark-factory-title": "^2.0.0",
9498 + "micromark-factory-whitespace": "^2.0.0",
9499 + "micromark-util-character": "^2.0.0",
9500 + "micromark-util-chunked": "^2.0.0",
9501 + "micromark-util-classify-character": "^2.0.0",
9502 + "micromark-util-html-tag-name": "^2.0.0",
9503 + "micromark-util-normalize-identifier": "^2.0.0",
9504 + "micromark-util-resolve-all": "^2.0.0",
9505 + "micromark-util-subtokenize": "^2.0.0",
9506 + "micromark-util-symbol": "^2.0.0",
9507 + "micromark-util-types": "^2.0.0"
9508 + }
9509 + },
9510 + "node_modules/micromark-extension-gfm": {
9511 + "version": "3.0.0",
9512 + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
9513 + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
9514 + "dev": true,
9515 + "dependencies": {
9516 + "micromark-extension-gfm-autolink-literal": "^2.0.0",
9517 + "micromark-extension-gfm-footnote": "^2.0.0",
9518 + "micromark-extension-gfm-strikethrough": "^2.0.0",
9519 + "micromark-extension-gfm-table": "^2.0.0",
9520 + "micromark-extension-gfm-tagfilter": "^2.0.0",
9521 + "micromark-extension-gfm-task-list-item": "^2.0.0",
9522 + "micromark-util-combine-extensions": "^2.0.0",
9523 + "micromark-util-types": "^2.0.0"
9524 + },
9525 + "funding": {
9526 + "type": "opencollective",
9527 + "url": "https://opencollective.com/unified"
9528 + }
9529 + },
9530 + "node_modules/micromark-extension-gfm-autolink-literal": {
9531 + "version": "2.1.0",
9532 + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
9533 + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
9534 + "dev": true,
9535 + "dependencies": {
9536 + "micromark-util-character": "^2.0.0",
9537 + "micromark-util-sanitize-uri": "^2.0.0",
9538 + "micromark-util-symbol": "^2.0.0",
9539 + "micromark-util-types": "^2.0.0"
9540 + },
9541 + "funding": {
9542 + "type": "opencollective",
9543 + "url": "https://opencollective.com/unified"
9544 + }
9545 + },
9546 + "node_modules/micromark-extension-gfm-footnote": {
9547 + "version": "2.1.0",
9548 + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
9549 + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
9550 + "dev": true,
9551 + "dependencies": {
9552 + "devlop": "^1.0.0",
9553 + "micromark-core-commonmark": "^2.0.0",
9554 + "micromark-factory-space": "^2.0.0",
9555 + "micromark-util-character": "^2.0.0",
9556 + "micromark-util-normalize-identifier": "^2.0.0",
9557 + "micromark-util-sanitize-uri": "^2.0.0",
9558 + "micromark-util-symbol": "^2.0.0",
9559 + "micromark-util-types": "^2.0.0"
9560 + },
9561 + "funding": {
9562 + "type": "opencollective",
9563 + "url": "https://opencollective.com/unified"
9564 + }
9565 + },
9566 + "node_modules/micromark-extension-gfm-strikethrough": {
9567 + "version": "2.1.0",
9568 + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
9569 + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
9570 + "dev": true,
9571 + "dependencies": {
9572 + "devlop": "^1.0.0",
9573 + "micromark-util-chunked": "^2.0.0",
9574 + "micromark-util-classify-character": "^2.0.0",
9575 + "micromark-util-resolve-all": "^2.0.0",
9576 + "micromark-util-symbol": "^2.0.0",
9577 + "micromark-util-types": "^2.0.0"
9578 + },
9579 + "funding": {
9580 + "type": "opencollective",
9581 + "url": "https://opencollective.com/unified"
9582 + }
9583 + },
9584 + "node_modules/micromark-extension-gfm-table": {
9585 + "version": "2.1.0",
9586 + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
9587 + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
9588 + "dev": true,
9589 + "dependencies": {
9590 + "devlop": "^1.0.0",
9591 + "micromark-factory-space": "^2.0.0",
9592 + "micromark-util-character": "^2.0.0",
9593 + "micromark-util-symbol": "^2.0.0",
9594 + "micromark-util-types": "^2.0.0"
9595 + },
9596 + "funding": {
9597 + "type": "opencollective",
9598 + "url": "https://opencollective.com/unified"
9599 + }
9600 + },
9601 + "node_modules/micromark-extension-gfm-tagfilter": {
9602 + "version": "2.0.0",
9603 + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
9604 + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
9605 + "dev": true,
9606 + "dependencies": {
9607 + "micromark-util-types": "^2.0.0"
9608 + },
9609 + "funding": {
9610 + "type": "opencollective",
9611 + "url": "https://opencollective.com/unified"
9612 + }
9613 + },
9614 + "node_modules/micromark-extension-gfm-task-list-item": {
9615 + "version": "2.1.0",
9616 + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
9617 + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
9618 + "dev": true,
9619 + "dependencies": {
9620 + "devlop": "^1.0.0",
9621 + "micromark-factory-space": "^2.0.0",
9622 + "micromark-util-character": "^2.0.0",
9623 + "micromark-util-symbol": "^2.0.0",
9624 + "micromark-util-types": "^2.0.0"
9625 + },
9626 + "funding": {
9627 + "type": "opencollective",
9628 + "url": "https://opencollective.com/unified"
9629 + }
9630 + },
9631 + "node_modules/micromark-factory-destination": {
9632 + "version": "2.0.0",
9633 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
9634 + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==",
9635 + "dev": true,
9636 + "funding": [
9637 + {
9638 + "type": "GitHub Sponsors",
9639 + "url": "https://github.com/sponsors/unifiedjs"
9640 + },
9641 + {
9642 + "type": "OpenCollective",
9643 + "url": "https://opencollective.com/unified"
9644 + }
9645 + ],
9646 + "dependencies": {
9647 + "micromark-util-character": "^2.0.0",
9648 + "micromark-util-symbol": "^2.0.0",
9649 + "micromark-util-types": "^2.0.0"
9650 + }
9651 + },
9652 + "node_modules/micromark-factory-label": {
9653 + "version": "2.0.0",
9654 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz",
9655 + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==",
9656 + "dev": true,
9657 + "funding": [
9658 + {
9659 + "type": "GitHub Sponsors",
9660 + "url": "https://github.com/sponsors/unifiedjs"
9661 + },
9662 + {
9663 + "type": "OpenCollective",
9664 + "url": "https://opencollective.com/unified"
9665 + }
9666 + ],
9667 + "dependencies": {
9668 + "devlop": "^1.0.0",
9669 + "micromark-util-character": "^2.0.0",
9670 + "micromark-util-symbol": "^2.0.0",
9671 + "micromark-util-types": "^2.0.0"
9672 + }
9673 + },
9674 + "node_modules/micromark-factory-space": {
9675 + "version": "2.0.0",
9676 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz",
9677 + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==",
9678 + "dev": true,
9679 + "funding": [
9680 + {
9681 + "type": "GitHub Sponsors",
9682 + "url": "https://github.com/sponsors/unifiedjs"
9683 + },
9684 + {
9685 + "type": "OpenCollective",
9686 + "url": "https://opencollective.com/unified"
9687 + }
9688 + ],
9689 + "dependencies": {
9690 + "micromark-util-character": "^2.0.0",
9691 + "micromark-util-types": "^2.0.0"
9692 + }
9693 + },
9694 + "node_modules/micromark-factory-title": {
9695 + "version": "2.0.0",
9696 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz",
9697 + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==",
9698 + "dev": true,
9699 + "funding": [
9700 + {
9701 + "type": "GitHub Sponsors",
9702 + "url": "https://github.com/sponsors/unifiedjs"
9703 + },
9704 + {
9705 + "type": "OpenCollective",
9706 + "url": "https://opencollective.com/unified"
9707 + }
9708 + ],
9709 + "dependencies": {
9710 + "micromark-factory-space": "^2.0.0",
9711 + "micromark-util-character": "^2.0.0",
9712 + "micromark-util-symbol": "^2.0.0",
9713 + "micromark-util-types": "^2.0.0"
9714 + }
9715 + },
9716 + "node_modules/micromark-factory-whitespace": {
9717 + "version": "2.0.0",
9718 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz",
9719 + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==",
9720 + "dev": true,
9721 + "funding": [
9722 + {
9723 + "type": "GitHub Sponsors",
9724 + "url": "https://github.com/sponsors/unifiedjs"
9725 + },
9726 + {
9727 + "type": "OpenCollective",
9728 + "url": "https://opencollective.com/unified"
9729 + }
9730 + ],
9731 + "dependencies": {
9732 + "micromark-factory-space": "^2.0.0",
9733 + "micromark-util-character": "^2.0.0",
9734 + "micromark-util-symbol": "^2.0.0",
9735 + "micromark-util-types": "^2.0.0"
9736 + }
9737 + },
9738 + "node_modules/micromark-util-character": {
9739 + "version": "2.1.0",
9740 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz",
9741 + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==",
9742 + "funding": [
9743 + {
9744 + "type": "GitHub Sponsors",
9745 + "url": "https://github.com/sponsors/unifiedjs"
9746 + },
9747 + {
9748 + "type": "OpenCollective",
9749 + "url": "https://opencollective.com/unified"
9750 + }
9751 + ],
9752 + "dependencies": {
9753 + "micromark-util-symbol": "^2.0.0",
9754 + "micromark-util-types": "^2.0.0"
9755 + }
9756 + },
9757 + "node_modules/micromark-util-chunked": {
9758 + "version": "2.0.0",
9759 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz",
9760 + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==",
9761 + "dev": true,
9762 + "funding": [
9763 + {
9764 + "type": "GitHub Sponsors",
9765 + "url": "https://github.com/sponsors/unifiedjs"
9766 + },
9767 + {
9768 + "type": "OpenCollective",
9769 + "url": "https://opencollective.com/unified"
9770 + }
9771 + ],
9772 + "dependencies": {
9773 + "micromark-util-symbol": "^2.0.0"
9774 + }
9775 + },
9776 + "node_modules/micromark-util-classify-character": {
9777 + "version": "2.0.0",
9778 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz",
9779 + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==",
9780 + "dev": true,
9781 + "funding": [
9782 + {
9783 + "type": "GitHub Sponsors",
9784 + "url": "https://github.com/sponsors/unifiedjs"
9785 + },
9786 + {
9787 + "type": "OpenCollective",
9788 + "url": "https://opencollective.com/unified"
9789 + }
9790 + ],
9791 + "dependencies": {
9792 + "micromark-util-character": "^2.0.0",
9793 + "micromark-util-symbol": "^2.0.0",
9794 + "micromark-util-types": "^2.0.0"
9795 + }
9796 + },
9797 + "node_modules/micromark-util-combine-extensions": {
9798 + "version": "2.0.0",
9799 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz",
9800 + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==",
9801 + "dev": true,
9802 + "funding": [
9803 + {
9804 + "type": "GitHub Sponsors",
9805 + "url": "https://github.com/sponsors/unifiedjs"
9806 + },
9807 + {
9808 + "type": "OpenCollective",
9809 + "url": "https://opencollective.com/unified"
9810 + }
9811 + ],
9812 + "dependencies": {
9813 + "micromark-util-chunked": "^2.0.0",
9814 + "micromark-util-types": "^2.0.0"
9815 + }
9816 + },
9817 + "node_modules/micromark-util-decode-numeric-character-reference": {
9818 + "version": "2.0.1",
9819 + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz",
9820 + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==",
9821 + "dev": true,
9822 + "funding": [
9823 + {
9824 + "type": "GitHub Sponsors",
9825 + "url": "https://github.com/sponsors/unifiedjs"
9826 + },
9827 + {
9828 + "type": "OpenCollective",
9829 + "url": "https://opencollective.com/unified"
9830 + }
9831 + ],
9832 + "dependencies": {
9833 + "micromark-util-symbol": "^2.0.0"
9834 + }
9835 + },
9836 + "node_modules/micromark-util-decode-string": {
9837 + "version": "2.0.0",
9838 + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz",
9839 + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==",
9840 + "dev": true,
9841 + "funding": [
9842 + {
9843 + "type": "GitHub Sponsors",
9844 + "url": "https://github.com/sponsors/unifiedjs"
9845 + },
9846 + {
9847 + "type": "OpenCollective",
9848 + "url": "https://opencollective.com/unified"
9849 + }
9850 + ],
9851 + "dependencies": {
9852 + "decode-named-character-reference": "^1.0.0",
9853 + "micromark-util-character": "^2.0.0",
9854 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
9855 + "micromark-util-symbol": "^2.0.0"
9856 + }
9857 + },
9858 + "node_modules/micromark-util-encode": {
9859 + "version": "2.0.0",
9860 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz",
9861 + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==",
9862 + "funding": [
9863 + {
9864 + "type": "GitHub Sponsors",
9865 + "url": "https://github.com/sponsors/unifiedjs"
9866 + },
9867 + {
9868 + "type": "OpenCollective",
9869 + "url": "https://opencollective.com/unified"
9870 + }
9871 + ]
9872 + },
9873 + "node_modules/micromark-util-html-tag-name": {
9874 + "version": "2.0.0",
9875 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz",
9876 + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==",
9877 "dev": true,
8066 - "engines": {
8067 - "node": ">= 0.6"
8068 - }
9878 + "funding": [
9879 + {
9880 + "type": "GitHub Sponsors",
9881 + "url": "https://github.com/sponsors/unifiedjs"
9882 + },
9883 + {
9884 + "type": "OpenCollective",
9885 + "url": "https://opencollective.com/unified"
9886 + }
9887 + ]
9888 },
8070 - "node_modules/micromark-util-character": {
8071 - "version": "2.1.0",
8072 - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz",
8073 - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==",
9889 + "node_modules/micromark-util-normalize-identifier": {
9890 + "version": "2.0.0",
9891 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz",
9892 + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==",
9893 + "dev": true,
9894 "funding": [
9895 {
9896 "type": "GitHub Sponsors",
@@ -8082,14 +9902,14 @@
9902 }
9903 ],
9904 "dependencies": {
8085 - "micromark-util-symbol": "^2.0.0",
8086 - "micromark-util-types": "^2.0.0"
9905 + "micromark-util-symbol": "^2.0.0"
9906 }
9907 },
8089 - "node_modules/micromark-util-encode": {
9908 + "node_modules/micromark-util-resolve-all": {
9909 "version": "2.0.0",
8091 - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz",
8092 - "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==",
9910 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz",
9911 + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==",
9912 + "dev": true,
9913 "funding": [
9914 {
9915 "type": "GitHub Sponsors",
@@ -8099,7 +9919,10 @@
9919 "type": "OpenCollective",
9920 "url": "https://opencollective.com/unified"
9921 }
8102 - ]
9922 + ],
9923 + "dependencies": {
9924 + "micromark-util-types": "^2.0.0"
9925 + }
9926 },
9927 "node_modules/micromark-util-sanitize-uri": {
9928 "version": "2.0.0",
@@ -8121,6 +9944,28 @@
9944 "micromark-util-symbol": "^2.0.0"
9945 }
9946 },
9947 + "node_modules/micromark-util-subtokenize": {
9948 + "version": "2.0.1",
9949 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz",
9950 + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==",
9951 + "dev": true,
9952 + "funding": [
9953 + {
9954 + "type": "GitHub Sponsors",
9955 + "url": "https://github.com/sponsors/unifiedjs"
9956 + },
9957 + {
9958 + "type": "OpenCollective",
9959 + "url": "https://opencollective.com/unified"
9960 + }
9961 + ],
9962 + "dependencies": {
9963 + "devlop": "^1.0.0",
9964 + "micromark-util-chunked": "^2.0.0",
9965 + "micromark-util-symbol": "^2.0.0",
9966 + "micromark-util-types": "^2.0.0"
9967 + }
9968 + },
9969 "node_modules/micromark-util-symbol": {
9970 "version": "2.0.0",
9971 "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz",
@@ -8163,6 +10008,17 @@
10008 "node": ">=8.6"
10009 }
10010 },
10011 + "node_modules/micromatch/node_modules/picomatch": {
10012 + "version": "2.3.1",
10013 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
10014 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
10015 + "engines": {
10016 + "node": ">=8.6"
10017 + },
10018 + "funding": {
10019 + "url": "https://github.com/sponsors/jonschlinkert"
10020 + }
10021 + },
10022 "node_modules/mime-db": {
10023 "version": "1.52.0",
10024 "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -8191,10 +10047,20 @@
10047 "node": ">=6"
10048 }
10049 },
10050 + "node_modules/min-indent": {
10051 + "version": "1.0.1",
10052 + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
10053 + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
10054 + "dev": true,
10055 + "engines": {
10056 + "node": ">=4"
10057 + }
10058 + },
10059 "node_modules/minimatch": {
8195 - "version": "9.0.3",
8196 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
8197 - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
10060 + "version": "9.0.5",
10061 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
10062 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
10063 + "dev": true,
10064 "dependencies": {
10065 "brace-expansion": "^2.0.1"
10066 },
@@ -8369,6 +10235,12 @@
10235 "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
10236 "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
10237 },
10238 + "node_modules/natural-compare-lite": {
10239 + "version": "1.4.0",
10240 + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
10241 + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
10242 + "dev": true
10243 + },
10244 "node_modules/negotiator": {
10245 "version": "0.6.3",
10246 "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -8403,6 +10275,27 @@
10275 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
10276 }
10277 },
10278 + "node_modules/normalize-package-data": {
10279 + "version": "2.5.0",
10280 + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
10281 + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
10282 + "dev": true,
10283 + "dependencies": {
10284 + "hosted-git-info": "^2.1.4",
10285 + "resolve": "^1.10.0",
10286 + "semver": "2 || 3 || 4 || 5",
10287 + "validate-npm-package-license": "^3.0.1"
10288 + }
10289 + },
10290 + "node_modules/normalize-package-data/node_modules/semver": {
10291 + "version": "5.7.2",
10292 + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
10293 + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
10294 + "dev": true,
10295 + "bin": {
10296 + "semver": "bin/semver"
10297 + }
10298 + },
10299 "node_modules/normalize-path": {
10300 "version": "3.0.0",
10301 "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -8430,9 +10323,9 @@
10323 }
10324 },
10325 "node_modules/npm-run-all2": {
8433 - "version": "6.2.2",
8434 - "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-6.2.2.tgz",
8435 - "integrity": "sha512-Q+alQAGIW7ZhKcxLt8GcSi3h3ryheD6xnmXahkMRVM5LYmajcUrSITm8h+OPC9RYWMV2GR0Q1ntTUCfxaNoOJw==",
10326 + "version": "6.2.3",
10327 + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-6.2.3.tgz",
10328 + "integrity": "sha512-5RsxC7jEc/RjxOYBVdEfrJf5FsJ0pHA7jr2/OxrThXknajETCTYjigOCG3iaGjdYIKEQlDuCG0ir0T1HTva8pg==",
10329 "dev": true,
10330 "dependencies": {
10331 "ansi-styles": "^6.2.1",
@@ -8669,20 +10562,20 @@
10562 }
10563 },
10564 "node_modules/ofetch": {
8672 - "version": "1.3.4",
8673 - "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.3.4.tgz",
8674 - "integrity": "sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw==",
10565 + "version": "1.4.0",
10566 + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.0.tgz",
10567 + "integrity": "sha512-MuHgsEhU6zGeX+EMh+8mSMrYTnsqJQQrpM00Q6QHMKNqQ0bKy0B43tk8tL1wg+CnsSTy1kg4Ir2T5Ig6rD+dfQ==",
10568 "dev": true,
10569 "dependencies": {
10570 "destr": "^2.0.3",
8678 - "node-fetch-native": "^1.6.3",
8679 - "ufo": "^1.5.3"
10571 + "node-fetch-native": "^1.6.4",
10572 + "ufo": "^1.5.4"
10573 }
10574 },
10575 "node_modules/ohash": {
8683 - "version": "1.1.3",
8684 - "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz",
8685 - "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw=="
10576 + "version": "1.1.4",
10577 + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz",
10578 + "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="
10579 },
10580 "node_modules/on-finished": {
10581 "version": "2.4.1",
@@ -8700,7 +10593,6 @@
10593 "version": "1.4.0",
10594 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
10595 "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
8703 - "dev": true,
10596 "dependencies": {
10597 "wrappy": "1"
10598 }
@@ -8818,6 +10710,15 @@
10710 "url": "https://github.com/sponsors/sindresorhus"
10711 }
10712 },
10713 + "node_modules/p-try": {
10714 + "version": "2.2.0",
10715 + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
10716 + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
10717 + "dev": true,
10718 + "engines": {
10719 + "node": ">=6"
10720 + }
10721 + },
10722 "node_modules/package-json-from-dist": {
10723 "version": "1.0.0",
10724 "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
@@ -8841,6 +10742,52 @@
10742 "node": ">=6"
10743 }
10744 },
10745 + "node_modules/parse-gitignore": {
10746 + "version": "2.0.0",
10747 + "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-2.0.0.tgz",
10748 + "integrity": "sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==",
10749 + "dev": true,
10750 + "engines": {
10751 + "node": ">=14"
10752 + }
10753 + },
10754 + "node_modules/parse-imports": {
10755 + "version": "2.2.1",
10756 + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz",
10757 + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==",
10758 + "dev": true,
10759 + "dependencies": {
10760 + "es-module-lexer": "^1.5.3",
10761 + "slashes": "^3.0.12"
10762 + },
10763 + "engines": {
10764 + "node": ">= 18"
10765 + }

This file is too large to show in full.

frontend/package.json
+34 -40
@@ -1,14 +1,19 @@
1 {
2 "name": "copilot",
3 + "type": "module",
4 "version": "1.0.0",
5 "private": true,
5 - "type": "module",
6 + "engines": {
7 + "node": ">=18.0.0"
8 + },
9 "scripts": {
10 "dev": "vite --host 0.0.0.0",
11 "dev:debug": "DEBUG=vite:* vite",
12 "build": "run-p type-check \"build-only {@}\" --",
13 "build-only": "vite build",
14 "build-analyzer": "vite-bundle-visualizer -t sunburst",
15 + "build-check": "run-s libs-reload build lint type-check format test:e2e test:unit",
16 + "pre-merge": "run-s lint type-check format",
17 "preview": "vite build && echo '' && vite preview --port 4173 --host",
18 "preview-only": "vite preview --port 4173 --host",
19 "test:unit": "vitest",
@@ -36,9 +41,9 @@
41 "@fontsource/lexend": "^5.1.0",
42 "@fontsource/public-sans": "^5.1.0",
43 "@popperjs/core": "^2.11.8",
39 - "@shikijs/markdown-it": "^1.17.6",
40 - "@vueuse/components": "^11.0.3",
41 - "@vueuse/core": "^11.0.3",
44 + "@shikijs/markdown-it": "^1.18.0",
45 + "@vueuse/components": "^11.1.0",
46 + "@vueuse/core": "^11.1.0",
47 "apexcharts": "^3.53.0",
48 "axios": "^1.7.7",
49 "bytes": "^3.1.2",
@@ -49,18 +54,18 @@
54 "echarts": "^5.5.1",
55 "file-saver": "^2.0.5",
56 "html-entities": "^2.5.2",
52 - "jose": "^5.9.0",
57 + "jose": "^5.9.2",
58 "js-md5": "^0.8.3",
59 "lodash": "^4.17.21",
60 "mitt": "^3.0.1",
61 "naive-ui": "^2.39.0",
62 "password-validator": "^5.3.0",
63 "pinia": "^2.2.2",
59 - "pinia-plugin-persistedstate": "^4.0.1",
64 + "pinia-plugin-persistedstate": "^4.0.2",
65 "secure-ls": "^2.0.0",
61 - "shiki": "^1.17.6",
66 + "shiki": "^1.18.0",
67 "validator": "^13.12.0",
63 - "vue": "^3.5.5",
68 + "vue": "^3.5.7",
69 "vue-advanced-cropper": "^2.8.9",
70 "vue-highlight-words": "^3.0.1",
71 "vue-i18n": "^10.0.1",
@@ -70,7 +75,11 @@
75 "vue3-marquee": "^4.2.2",
76 "vuedraggable": "^4.1.0"
77 },
78 + "optionalDependencies": {
79 + "@rollup/rollup-linux-x64-gnu": "^4.22.4"
80 + },
81 "devDependencies": {
82 + "@antfu/eslint-config": "^3.7.1",
83 "@clack/prompts": "^0.7.0",
84 "@iconify/vue": "^4.1.2",
85 "@tsconfig/node20": "^20.1.4",
@@ -80,61 +89,46 @@
89 "@types/inquirer": "^9.0.7",
90 "@types/jsdom": "^21.1.7",
91 "@types/lodash": "^4.17.7",
83 - "@types/node": "^22.5.4",
84 - "@types/validator": "^13.12.1",
85 - "@vitejs/plugin-vue": "^5.1.3",
92 + "@types/node": "^22.5.5",
93 + "@types/validator": "^13.12.2",
94 + "@vitejs/plugin-vue": "^5.1.4",
95 "@vitejs/plugin-vue-jsx": "^4.0.1",
87 - "@vue/eslint-config-prettier": "^9.0.0",
88 - "@vue/eslint-config-typescript": "^13.0.0",
96 "@vue/test-utils": "^2.4.6",
97 "@vue/tsconfig": "^0.5.1",
98 "autoprefixer": "^10.4.20",
99 "cypress": "^13.14.2",
93 - "eslint": "^9.10.0",
94 - "eslint-plugin-cypress": "^3.5.0",
95 - "eslint-plugin-vue": "^9.28.0",
100 + "eslint": "^9.11.0",
101 "flourite": "^1.3.0",
102 "fs-extra": "^11.2.0",
103 "globals": "^15.9.0",
104 "jsdom": "^25.0.0",
105 "json5": "^2.2.3",
101 - "npm-run-all2": "^6.2.2",
106 + "npm-run-all2": "^6.2.3",
107 "picocolors": "^1.1.0",
103 - "postcss": "^8.4.45",
108 + "postcss": "^8.4.47",
109 "prettier": "^3.3.3",
105 - "sass": "^1.78.0",
106 - "start-server-and-test": "^2.0.7",
110 + "sass": "^1.79.3",
111 + "start-server-and-test": "^2.0.8",
112 "tailwind-config-viewer": "^2.0.4",
108 - "tailwindcss": "^3.4.11",
109 - "taze": "^0.16.8",
113 + "tailwindcss": "^3.4.12",
114 + "taze": "^0.16.9",
115 "type-fest": "^4.26.1",
116 "typescript": "~5.5.4",
117 "unplugin-vue-components": "^0.27.4",
113 - "vite": "^5.4.5",
114 - "vite-bundle-analyzer": "^0.10.6",
118 + "vite": "^5.4.7",
119 + "vite-bundle-analyzer": "^0.11.1",
120 "vite-bundle-visualizer": "^1.2.1",
121 "vite-plugin-vue-devtools": "^7.4.5",
122 "vite-svg-loader": "^5.1.0",
123 "vitest": "^2.1.1",
124 "vue-tsc": "^2.1.6"
125 },
121 - "optionalDependencies": {
122 - "@rollup/rollup-linux-x64-gnu": "^4.21.3"
123 - },
126 "overrides": {
125 - "@vue/eslint-config-typescript": {
126 - "eslint": "^9.10.0"
127 - },
128 - "@typescript-eslint/type-utils": {
129 - "eslint": "^9.10.0"
130 - },
131 - "@typescript-eslint/utils": {
132 - "eslint": "^9.10.0"
127 + "@typescript-eslint/eslint-plugin": {
128 + "eslint": "9.11.0"
129 },
134 - "micromatch": "^4.0.8",
135 - "path-to-regexp": "^8.1.0"
136 - },
137 - "engines": {
138 - "node": ">=18.0.0"
130 + "@typescript-eslint/parser": {
131 + "eslint": "9.11.0"
132 + }
133 }
134 }
frontend/playground.excalidraw new
+443
@@ -0,0 +1,443 @@
1 +{
2 + "type": "excalidraw",
3 + "version": 2,
4 + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
5 + "elements": [
6 + {
7 + "id": "7Fo6axMzOAl0YNLNXgEYP",
8 + "type": "rectangle",
9 + "x": 440,
10 + "y": 420,
11 + "width": 840,
12 + "height": 160,
13 + "angle": 0,
14 + "strokeColor": "#1e1e1e",
15 + "backgroundColor": "transparent",
16 + "fillStyle": "solid",
17 + "strokeWidth": 2,
18 + "strokeStyle": "solid",
19 + "roughness": 0,
20 + "opacity": 100,
21 + "groupIds": [],
22 + "frameId": null,
23 + "roundness": {
24 + "type": 3
25 + },
26 + "seed": 1942671124,
27 + "version": 167,
28 + "versionNonce": 528672660,
29 + "isDeleted": false,
30 + "boundElements": null,
31 + "updated": 1726739423410,
32 + "link": null,
33 + "locked": false
34 + },
35 + {
36 + "id": "36I5MO5QmLj0aEBGyca_L",
37 + "type": "text",
38 + "x": 440,
39 + "y": 160,
40 + "width": 778.125,
41 + "height": 211.2,
42 + "angle": 0,
43 + "strokeColor": "#1e1e1e",
44 + "backgroundColor": "transparent",
45 + "fillStyle": "solid",
46 + "strokeWidth": 2,
47 + "strokeStyle": "solid",
48 + "roughness": 0,
49 + "opacity": 100,
50 + "groupIds": [],
51 + "frameId": null,
52 + "roundness": null,
53 + "seed": 676167980,
54 + "version": 54,
55 + "versionNonce": 382397460,
56 + "isDeleted": false,
57 + "boundElements": null,
58 + "updated": 1726739651262,
59 + "link": null,
60 + "locked": false,
61 + "text": "{\n \"id\": 3,\n \"case_id\": 19,\n \"bucket_name\": \"copilot-cases\",\n \"object_key\": \"19/085_sca_policy_results (1) (1).csv\",\n \"file_name\": \"085_sca_policy_results (1) (1).csv\",\n \"content_type\": \"text/csv\",\n \"file_size\": 401516,\n \"upload_time\": \"2024-09-16T14:10:45\",\n \"file_hash\": \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"\n}",
62 + "fontSize": 16,
63 + "fontFamily": 3,
64 + "textAlign": "left",
65 + "verticalAlign": "top",
66 + "baseline": 207,
67 + "containerId": null,
68 + "originalText": "{\n \"id\": 3,\n \"case_id\": 19,\n \"bucket_name\": \"copilot-cases\",\n \"object_key\": \"19/085_sca_policy_results (1) (1).csv\",\n \"file_name\": \"085_sca_policy_results (1) (1).csv\",\n \"content_type\": \"text/csv\",\n \"file_size\": 401516,\n \"upload_time\": \"2024-09-16T14:10:45\",\n \"file_hash\": \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"\n}",
69 + "lineHeight": 1.2
70 + },
71 + {
72 + "id": "i2MHZShrXG15f8_KpJHUa",
73 + "type": "text",
74 + "x": 460,
75 + "y": 440,
76 + "width": 28.125,
77 + "height": 19.2,
78 + "angle": 0,
79 + "strokeColor": "#1e1e1e",
80 + "backgroundColor": "transparent",
81 + "fillStyle": "solid",
82 + "strokeWidth": 2,
83 + "strokeStyle": "solid",
84 + "roughness": 0,
85 + "opacity": 100,
86 + "groupIds": [],
87 + "frameId": null,
88 + "roundness": null,
89 + "seed": 1252874900,
90 + "version": 8,
91 + "versionNonce": 1692366228,
92 + "isDeleted": false,
93 + "boundElements": null,
94 + "updated": 1726739399346,
95 + "link": null,
96 + "locked": false,
97 + "text": "#id",
98 + "fontSize": 16,
99 + "fontFamily": 3,
100 + "textAlign": "left",
101 + "verticalAlign": "top",
102 + "baseline": 15,
103 + "containerId": null,
104 + "originalText": "#id",
105 + "lineHeight": 1.2
106 + },
107 + {
108 + "id": "RV9oLEMrjlWKdxG7pwgAe",
109 + "type": "text",
110 + "x": 520,
111 + "y": 440,
112 + "width": 56.25,
113 + "height": 19.2,
114 + "angle": 0,
115 + "strokeColor": "#1e1e1e",
116 + "backgroundColor": "transparent",
117 + "fillStyle": "solid",
118 + "strokeWidth": 2,
119 + "strokeStyle": "solid",
120 + "roughness": 0,
121 + "opacity": 100,
122 + "groupIds": [],
123 + "frameId": null,
124 + "roundness": null,
125 + "seed": 2094536620,
126 + "version": 28,
127 + "versionNonce": 1125859092,
128 + "isDeleted": false,
129 + "boundElements": null,
130 + "updated": 1726739611746,
131 + "link": null,
132 + "locked": false,
133 + "text": "bucket",
134 + "fontSize": 16,
135 + "fontFamily": 3,
136 + "textAlign": "left",
137 + "verticalAlign": "top",
138 + "baseline": 15,
139 + "containerId": null,
140 + "originalText": "bucket",
141 + "lineHeight": 1.2
142 + },
143 + {
144 + "id": "oGzleD2uOanLNkXxoMEPo",
145 + "type": "text",
146 + "x": 460,
147 + "y": 480,
148 + "width": 84.375,
149 + "height": 19.2,
150 + "angle": 0,
151 + "strokeColor": "#1e1e1e",
152 + "backgroundColor": "transparent",
153 + "fillStyle": "solid",
154 + "strokeWidth": 2,
155 + "strokeStyle": "solid",
156 + "roughness": 0,
157 + "opacity": 100,
158 + "groupIds": [],
159 + "frameId": null,
160 + "roundness": null,
161 + "seed": 1364832940,
162 + "version": 25,
163 + "versionNonce": 683650348,
164 + "isDeleted": false,
165 + "boundElements": null,
166 + "updated": 1726739512763,
167 + "link": null,
168 + "locked": false,
169 + "text": "file_name",
170 + "fontSize": 16,
171 + "fontFamily": 3,
172 + "textAlign": "left",
173 + "verticalAlign": "top",
174 + "baseline": 15,
175 + "containerId": null,
176 + "originalText": "file_name",
177 + "lineHeight": 1.2
178 + },
179 + {
180 + "id": "rDKnphsatsGrmf3fpf1cV",
181 + "type": "line",
182 + "x": 440,
183 + "y": 520,
184 + "width": 840,
185 + "height": 0,
186 + "angle": 0,
187 + "strokeColor": "#1e1e1e",
188 + "backgroundColor": "transparent",
189 + "fillStyle": "solid",
190 + "strokeWidth": 2,
191 + "strokeStyle": "solid",
192 + "roughness": 0,
193 + "opacity": 100,
194 + "groupIds": [],
195 + "frameId": null,
196 + "roundness": {
197 + "type": 2
198 + },
199 + "seed": 1266635308,
200 + "version": 28,
201 + "versionNonce": 543171988,
202 + "isDeleted": false,
203 + "boundElements": null,
204 + "updated": 1726739407013,
205 + "link": null,
206 + "locked": false,
207 + "points": [
208 + [
209 + 0,
210 + 0
211 + ],
212 + [
213 + 840,
214 + 0
215 + ]
216 + ],
217 + "lastCommittedPoint": null,
218 + "startBinding": null,
219 + "endBinding": null,
220 + "startArrowhead": null,
221 + "endArrowhead": null
222 + },
223 + {
224 + "id": "wI1C-_mmlekQJDgJXUWh-",
225 + "type": "rectangle",
226 + "x": 1140,
227 + "y": 540,
228 + "width": 120,
229 + "height": 29.2,
230 + "angle": 0,
231 + "strokeColor": "#1e1e1e",
232 + "backgroundColor": "transparent",
233 + "fillStyle": "solid",
234 + "strokeWidth": 2,
235 + "strokeStyle": "solid",
236 + "roughness": 0,
237 + "opacity": 100,
238 + "groupIds": [],
239 + "frameId": null,
240 + "roundness": {
241 + "type": 3
242 + },
243 + "seed": 1839046676,
244 + "version": 22,
245 + "versionNonce": 634539436,
246 + "isDeleted": false,
247 + "boundElements": [
248 + {
249 + "type": "text",
250 + "id": "10oLnLmiy98yqT-l5-bnP"
251 + }
252 + ],
253 + "updated": 1726739458908,
254 + "link": null,
255 + "locked": false
256 + },
257 + {
258 + "id": "10oLnLmiy98yqT-l5-bnP",
259 + "type": "text",
260 + "x": 1171.875,
261 + "y": 545,
262 + "width": 56.25,
263 + "height": 19.2,
264 + "angle": 0,
265 + "strokeColor": "#1e1e1e",
266 + "backgroundColor": "transparent",
267 + "fillStyle": "solid",
268 + "strokeWidth": 2,
269 + "strokeStyle": "solid",
270 + "roughness": 0,
271 + "opacity": 100,
272 + "groupIds": [],
273 + "frameId": null,
274 + "roundness": null,
275 + "seed": 1412661524,
276 + "version": 22,
277 + "versionNonce": 1292332,
278 + "isDeleted": false,
279 + "boundElements": null,
280 + "updated": 1726739458908,
281 + "link": null,
282 + "locked": false,
283 + "text": "delete",
284 + "fontSize": 16,
285 + "fontFamily": 3,
286 + "textAlign": "center",
287 + "verticalAlign": "middle",
288 + "baseline": 15,
289 + "containerId": "wI1C-_mmlekQJDgJXUWh-",
290 + "originalText": "delete",
291 + "lineHeight": 1.2
292 + },
293 + {
294 + "id": "v0F2yt8m1v5viu5ZRzoJ8",
295 + "type": "text",
296 + "x": 1140,
297 + "y": 440,
298 + "width": 103.125,
299 + "height": 19.2,
300 + "angle": 0,
301 + "strokeColor": "#1e1e1e",
302 + "backgroundColor": "transparent",
303 + "fillStyle": "solid",
304 + "strokeWidth": 2,
305 + "strokeStyle": "solid",
306 + "roughness": 0,
307 + "opacity": 100,
308 + "groupIds": [],
309 + "frameId": null,
310 + "roundness": null,
311 + "seed": 435175444,
312 + "version": 42,
313 + "versionNonce": 483035156,
314 + "isDeleted": false,
315 + "boundElements": null,
316 + "updated": 1726739608363,
317 + "link": null,
318 + "locked": false,
319 + "text": "upload_time",
320 + "fontSize": 16,
321 + "fontFamily": 3,
322 + "textAlign": "left",
323 + "verticalAlign": "top",
324 + "baseline": 15,
325 + "containerId": null,
326 + "originalText": "upload_time",
327 + "lineHeight": 1.2
328 + },
329 + {
330 + "id": "bEDavW5JTgpbaW0h7ISKK",
331 + "type": "text",
332 + "x": 560,
333 + "y": 480,
334 + "width": 84.375,
335 + "height": 19.2,
336 + "angle": 0,
337 + "strokeColor": "#1e1e1e",
338 + "backgroundColor": "transparent",
339 + "fillStyle": "solid",
340 + "strokeWidth": 2,
341 + "strokeStyle": "solid",
342 + "roughness": 0,
343 + "opacity": 100,
344 + "groupIds": [],
345 + "frameId": null,
346 + "roundness": null,
347 + "seed": 259937556,
348 + "version": 31,
349 + "versionNonce": 1471891116,
350 + "isDeleted": false,
351 + "boundElements": null,
352 + "updated": 1726739517230,
353 + "link": null,
354 + "locked": false,
355 + "text": "file_hash",
356 + "fontSize": 16,
357 + "fontFamily": 3,
358 + "textAlign": "left",
359 + "verticalAlign": "top",
360 + "baseline": 15,
361 + "containerId": null,
362 + "originalText": "file_hash",
363 + "lineHeight": 1.2
364 + },
365 + {
366 + "id": "JyCZfLdDkRbsznPGG0hIm",
367 + "type": "text",
368 + "x": 600,
369 + "y": 540,
370 + "width": 84.375,
371 + "height": 19.2,
372 + "angle": 0,
373 + "strokeColor": "#1e1e1e",
374 + "backgroundColor": "transparent",
375 + "fillStyle": "solid",
376 + "strokeWidth": 2,
377 + "strokeStyle": "solid",
378 + "roughness": 0,
379 + "opacity": 100,
380 + "groupIds": [],
381 + "frameId": null,
382 + "roundness": null,
383 + "seed": 235304980,
384 + "version": 68,
385 + "versionNonce": 1453589396,
386 + "isDeleted": false,
387 + "boundElements": null,
388 + "updated": 1726739620062,
389 + "link": null,
390 + "locked": false,
391 + "text": "file_size",
392 + "fontSize": 16,
393 + "fontFamily": 3,
394 + "textAlign": "left",
395 + "verticalAlign": "top",
396 + "baseline": 15,
397 + "containerId": null,
398 + "originalText": "file_size",
399 + "lineHeight": 1.2
400 + },
401 + {
402 + "id": "2q1BqKBXk22KA1zzteQpE",
403 + "type": "text",
404 + "x": 460,
405 + "y": 540,
406 + "width": 112.5,
407 + "height": 19.2,
408 + "angle": 0,
409 + "strokeColor": "#1e1e1e",
410 + "backgroundColor": "transparent",
411 + "fillStyle": "solid",
412 + "strokeWidth": 2,
413 + "strokeStyle": "solid",
414 + "roughness": 0,
415 + "opacity": 100,
416 + "groupIds": [],
417 + "frameId": null,
418 + "roundness": null,
419 + "seed": 1377385492,
420 + "version": 65,
421 + "versionNonce": 1912696212,
422 + "isDeleted": false,
423 + "boundElements": null,
424 + "updated": 1726739618846,
425 + "link": null,
426 + "locked": false,
427 + "text": "content_type",
428 + "fontSize": 16,
429 + "fontFamily": 3,
430 + "textAlign": "left",
431 + "verticalAlign": "top",
432 + "baseline": 15,
433 + "containerId": null,
434 + "originalText": "content_type",
435 + "lineHeight": 1.2
436 + }
437 + ],
438 + "appState": {
439 + "gridSize": 20,
440 + "viewBackgroundColor": "#ffffff"
441 + },
442 + "files": {}
443 +}
frontend/scripts/tokens-tool.js
+27 -20
@@ -1,9 +1,9 @@
1 +import os from "node:os"
2 +import path from "node:path"
3 import process from "node:process"
2 -import _ from "lodash"
4 +import { cancel, intro, isCancel, outro, select, spinner, text } from "@clack/prompts"
5 import fs from "fs-extra"
4 -import path from "node:path"
5 -import os from "node:os"
6 -import { intro, outro, select, spinner, isCancel, cancel, text } from "@clack/prompts"
6 +import _ from "lodash"
7
8 const GLOBAL_KEYS = ["border-radius", "line-heights", "font-sizes", "font-families"]
9 const TYPO_KEYS = ["typo"]
@@ -46,10 +46,13 @@ function getValue(origin, val) {
46 }
47
48 /**
49 + * Sanitizes a token or type name based on the provided mapping.
50 + *
51 + * This function converts a token name to a type name or vice versa based on the direction specified. It uses a predefined map to find the corresponding sanitized name.
52 *
50 - * @param {string} name
51 - * @param {"token" | "type"} from
52 - * @returns {string}
53 + * @param {string} name - The name of the token or type to be sanitized.
54 + * @param {"token" | "type"} from - Indicates the current type of the name (`"token"` or `"type"`) to determine the direction of the conversion.
55 + * @returns {string} The sanitized name, converted to the opposite type. If no mapping is found, returns the original name.
56 */
57 function tokenNameSanitize(name, from) {
58 const to = from === "token" ? "type" : "token"
@@ -62,12 +65,15 @@ function tokenNameSanitize(name, from) {
65 }
66
67 /**
68 + * Imports tokens from a JSON file and processes them into a project file format.
69 *
66 - * @param {string} tokensPath
67 - * @returns {string}
70 + * This function reads a JSON file containing token definitions, normalizes paths, and organizes tokens into a project file structure. It handles global tokens, typography tokens, and set tokens, and writes the processed data to a specified design token path.
71 + *
72 + * @param {string} tokensPath - The path to the JSON file containing the tokens to be imported. The path can be relative or use `~` to refer to the home directory.
73 + * @returns {string} The path to the design token file where the processed tokens have been written.
74 */
75 async function importTokens(tokensPath) {
70 - const filePath = path.normalize(tokensPath.trim().replace("~/", os.homedir() + "/"))
76 + const filePath = path.normalize(tokensPath.trim().replace("~/", `${os.homedir()}/`))
77 const tokens = await fs.readJSON(filePath)
78
79 const projectFile = {}
@@ -83,7 +89,7 @@ async function importTokens(tokensPath) {
89 const kIndex = k.indexOf(gk)
90 if (kIndex !== -1) {
91 const gkParsed = tokenNameSanitize(_.camelCase(gk), "type")
86 - const name = _.camelCase(k.replace(gk + "-", ""))
92 + const name = _.camelCase(k.replace(`${gk}-`, ""))
93
94 _.set(projectFile, `${gkParsed}.${name}`, globalTokens[k].value)
95 }
@@ -103,7 +109,7 @@ async function importTokens(tokensPath) {
109 if (prop.indexOf("{") === 0) {
110 const prefix = tokenNameSanitize(k, "token")
111 const ref = prop
106 - .replace(_.kebabCase(prefix) + "-", "")
112 + .replace(`${_.kebabCase(prefix)}-`, "")
113 .replace("{", "")
114 .replace("}", "")
115 value[k] = `{${k}.${_.camelCase(ref)}}`
@@ -124,7 +130,7 @@ async function importTokens(tokensPath) {
130 const kIndex = k.indexOf(sk)
131 if (kIndex !== -1) {
132 const skParsed = tokenNameSanitize(_.camelCase(sk), "type")
127 - const name = _.camelCase(k.replace(sk + "-", ""))
133 + const name = _.camelCase(k.replace(`${sk}-`, ""))
134 let value = group[k].value
135
136 if (value.indexOf("{") === 0) {
@@ -147,8 +153,9 @@ async function importTokens(tokensPath) {
153 }
154
155 /**
156 + * Exports tokens from the design system and writes them to a JSON file.
157 *
151 - * @returns {string}
158 + * @returns {string} The path to the exported JSON file.
159 */
160 async function exportTokens() {
161 const tokens = await fs.readJSON(DESIGN_TOKEN_PATH)
@@ -172,11 +179,11 @@ async function exportTokens() {
179 const type = tokenNameSanitize(group.key, "token")
180
181 for (const name in group.value) {
175 - const tokenName = _.kebabCase(type + "-" + name)
182 + const tokenName = _.kebabCase(`${type}-${name}`)
183
184 exportFile.global[tokenName] = {
185 value: group.value[name],
179 - type: type
186 + type
187 }
188 }
189 }
@@ -192,12 +199,12 @@ async function exportTokens() {
199
200 exportFile.global[globalName] = {
201 value: set[setName][name],
195 - type: type
202 + type
203 }
204
205 exportFile[setName][tokenName] = {
206 value: `{${globalName}}`,
200 - type: type
207 + type
208 }
209 }
210 }
@@ -218,7 +225,7 @@ async function exportTokens() {
225 const ref = prop.replace("{", "").replace("}", "")
226 const path = _.split(ref, ".")[1]
227 const prefix = tokenNameSanitize(k, "token")
221 - newValue[k] = "{" + _.kebabCase(`${prefix}-${_.kebabCase(path)}`) + "}"
228 + newValue[k] = `{${_.kebabCase(`${prefix}-${_.kebabCase(path)}`)}}`
229 } else {
230 newValue[k] = prop
231 }
@@ -227,7 +234,7 @@ async function exportTokens() {
234 // sanitize lineHeight for figma
235 if (value.fontSize && tokens?.lineHeight?.base) {
236 newValue.lineHeight = Math.round(
230 - parseInt(getValue(tokens, value.fontSize)) * parseFloat(tokens.lineHeight.base)
237 + Number.parseInt(getValue(tokens, value.fontSize)) * Number.parseFloat(tokens.lineHeight.base)
238 ).toString()
239 }
240
frontend/src/App.vue
+9 -9
@@ -4,10 +4,10 @@
4 :is="layoutComponent"
5 :class="[`theme-${themeName}`, `layout-${layoutComponentName}`, themeName, { 'opacity-0': loading }]"
6 >
7 - <RouterView v-slot="{ Component }">
7 + <RouterView v-slot="{ Component: RouterComponent }">
8 <transition :name="`router-${routerTransition}`" mode="out-in" appear>
9 <component
10 - :is="Component"
10 + :is="RouterComponent"
11 :key="forceRefresh"
12 :class="[`theme-${themeName}`, `layout-${layoutComponentName}`, themeName]"
13 />
@@ -21,16 +21,16 @@
21 </template>
22
23 <script lang="ts" setup>
24 -import { computed, onBeforeMount, ref, watch, type Component } from "vue"
25 -import { useMainStore } from "@/stores/main"
26 -import { useAuthStore } from "@/stores/auth"
27 -import { useThemeStore } from "@/stores/theme"
28 -import HorizontalNav from "@/layouts/HorizontalNav/index.vue"
24 +import type { Layout, RouterTransition, ThemeName } from "@/types/theme.d"
25 +import SearchDialog from "@/components/common/SearchDialog.vue"
26 import Blank from "@/layouts/Blank/index.vue"
27 import Provider from "@/layouts/common/Provider.vue"
28 import SplashScreen from "@/layouts/common/SplashScreen.vue"
32 -import SearchDialog from "@/components/common/SearchDialog.vue"
33 -import { Layout, RouterTransition, type ThemeName } from "@/types/theme.d"
29 +import HorizontalNav from "@/layouts/HorizontalNav/index.vue"
30 +import { useAuthStore } from "@/stores/auth"
31 +import { useMainStore } from "@/stores/main"
32 +import { useThemeStore } from "@/stores/theme"
33 +import { type Component, computed, onBeforeMount, ref, watch } from "vue"
34 import { type RouteLocationNormalized, useRoute, useRouter } from "vue-router"
35 import "@/assets/scss/index.scss"
36
frontend/src/api/endpoints/activeResponse.ts
+3 -3
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 import type { ActiveResponseDetails, SupportedActiveResponse } from "@/types/activeResponse.d"
2 +import type { FlaskBaseResponse } from "@/types/flask.d"
3 +import { HttpClient } from "../httpClient"
4
5 export type InvokeRequestAction = "block" | "unblock"
6
@@ -14,7 +14,7 @@ export interface InvokeRequest {
14 export default {
15 getSupported(agentId?: string) {
16 return HttpClient.get<FlaskBaseResponse & { supported_active_responses: SupportedActiveResponse[] }>(
17 - `/active_response/supported${agentId ? "/" + agentId : ""}`
17 + `/active_response/supported${agentId ? `/${agentId}` : ""}`
18 )
19 },
20 getDetails(activeResponseName: string) {
frontend/src/api/endpoints/agents.ts
+8 -6
@@ -1,13 +1,13 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 import type {
2 Agent,
3 + AgentSca,
4 AgentVulnerabilities,
6 - OutdatedWazuhAgents,
5 OutdatedVelociraptorAgents,
8 - AgentSca,
6 + OutdatedWazuhAgents,
7 ScaPolicyResult
8 } from "@/types/agents.d"
9 +import type { FlaskBaseResponse } from "@/types/flask.d"
10 +import { HttpClient } from "../httpClient"
11
12 export interface AgentPayload {
13 velociraptor_id: string
@@ -17,7 +17,7 @@ export type VulnerabilitySeverityType = "Low" | "Medium" | "High" | "Critical" |
17
18 export default {
19 getAgents(agentId?: string) {
20 - return HttpClient.get<FlaskBaseResponse & { agents: Agent[] }>(`/agents${agentId ? "/" + agentId : ""}`)
20 + return HttpClient.get<FlaskBaseResponse & { agents: Agent[] }>(`/agents${agentId ? `/${agentId}` : ""}`)
21 },
22 markCritical(agentId: string) {
23 return HttpClient.post<FlaskBaseResponse>(`/agents/${agentId}/critical`)
@@ -59,7 +59,9 @@ export default {
59 )
60 },
61 scaResultsDownload(agentId: string | number, policyId: string) {
62 - return HttpClient.get<string>(`/agents/${agentId}/csv/sca/${policyId}`)
62 + return HttpClient.get<Blob>(`/agents/${agentId}/csv/sca/${policyId}`, {
63 + responseType: "blob"
64 + })
65 },
66 updateAgent(agentId: string, payload: AgentPayload) {
67 return HttpClient.put<FlaskBaseResponse>(
frontend/src/api/endpoints/alerts.ts
+3 -3
@@ -1,13 +1,13 @@
1 -import { HttpClient } from "../httpClient"
2 -import type { FlaskBaseResponse } from "@/types/flask.d"
1 import type {
4 - AlertSourceContent,
2 AlertsByHost,
3 AlertsByRule,
4 AlertsByRulePerHost,
5 + AlertSourceContent,
6 AlertsSummary,
7 WazuhRuleExclude
8 } from "@/types/alerts.d"
9 +import type { FlaskBaseResponse } from "@/types/flask.d"
10 +import { HttpClient } from "../httpClient"
11
12 export type AlertsQueryTimeRange = `${number}${"h" | "d" | "w"}`
13
frontend/src/api/endpoints/artifacts.ts
+4 -4
@@ -1,7 +1,7 @@
1 -import { HttpClient } from "../httpClient"
2 -import type { FlaskBaseResponse } from "@/types/flask.d"
1 import type { Artifact, CollectResult, CommandResult, QuarantineResult, Recommendation } from "@/types/artifacts.d"
2 import type { OsTypesFull, OsTypesLower } from "@/types/common.d"
3 +import type { FlaskBaseResponse } from "@/types/flask.d"
4 +import { HttpClient } from "../httpClient"
5
6 export interface ArtifactsQuery {
7 os?: OsTypesLower
@@ -38,10 +38,10 @@ export default {
38 let url = "/artifacts"
39
40 if (filters?.os) {
41 - url = "/artifacts/" + filters.os
41 + url = `/artifacts/${filters.os}`
42 }
43 if (filters?.hostname) {
44 - url = "/artifacts/hostname/" + filters.hostname
44 + url = `/artifacts/hostname/${filters.hostname}`
45 }
46
47 return HttpClient.get<FlaskBaseResponse & { artifacts: Artifact[] }>(url)
frontend/src/api/endpoints/askSocfortress.ts
+1 -1
@@ -1,5 +1,5 @@
1 -import { HttpClient } from "../httpClient"
1 import type { FlaskBaseResponse } from "@/types/flask.d"
2 +import { HttpClient } from "../httpClient"
3
4 export default {
5 create(indexName: string, alertId: string) {
frontend/src/api/endpoints/auth.ts
+2 -2
@@ -1,7 +1,7 @@
1 -import type { FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 import type { AuthUser, LoginPayload, RegisterPayload } from "@/types/auth.d"
2 +import type { FlaskBaseResponse } from "@/types/flask.d"
3 import jsonToFormData from "@ajoelp/json-to-formdata"
4 +import { HttpClient } from "../httpClient"
5
6 export default {
7 login(payload: LoginPayload) {
frontend/src/api/endpoints/cloudSecurityAssessment.ts
+2 -2
@@ -1,5 +1,3 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 import type {
2 ScoutSuiteAwsReportPayload,
3 ScoutSuiteAzureReportPayload,
@@ -7,6 +5,8 @@ import type {
5 ScoutSuiteReport,
6 ScoutSuiteReportPayload
7 } from "@/types/cloudSecurityAssessment.d"
8 +import type { FlaskBaseResponse } from "@/types/flask.d"
9 +import { HttpClient } from "../httpClient"
10
11 export default {
12 getAvailableScoutSuiteReports() {
frontend/src/api/endpoints/connectors.ts
+1 -1
@@ -1,5 +1,5 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
1 import type { Connector, ConnectorRequestPayload } from "@/types/connectors.d"
2 +import type { FlaskBaseResponse } from "@/types/flask.d"
3 import { HttpClient } from "../httpClient"
4
5 export default {
frontend/src/api/endpoints/customers.ts
+4 -4
@@ -1,5 +1,4 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { Agent } from "@/types/agents.d"
2 import type {
3 Customer,
4 CustomerAgentHealth,
@@ -8,7 +7,8 @@ import type {
7 CustomerProvision,
8 CustomerProvisioningDefaultSettings
9 } from "@/types/customers.d"
11 -import type { Agent } from "@/types/agents.d"
10 +import type { FlaskBaseResponse } from "@/types/flask.d"
11 +import { HttpClient } from "../httpClient"
12
13 export interface CustomerAgentsHealthcheckQuery {
14 minutes?: number
@@ -29,7 +29,7 @@ export default {
29 // #region Customer
30 getCustomers(code?: string) {
31 return HttpClient.get<FlaskBaseResponse & { customers?: Customer[]; customer?: Customer }>(
32 - `/customers${code ? "/" + code : ""}`
32 + `/customers${code ? `/${code}` : ""}`
33 )
34 },
35 createCustomer(customer: Customer) {
frontend/src/api/endpoints/flow.ts
+1 -1
@@ -1,6 +1,6 @@
1 -import { HttpClient } from "../httpClient"
1 import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { CollectResult, FlowResult } from "@/types/flow.d"
3 +import { HttpClient } from "../httpClient"
4
5 export default {
6 getAllByAgent(hostname: string) {
frontend/src/api/endpoints/graylog.ts
+5 -5
@@ -1,13 +1,13 @@
1 -import { HttpClient } from "../httpClient"
1 import type { FlaskBaseResponse } from "@/types/flask.d"
3 -import type { Message } from "@/types/graylog/messages.d"
2 import type { Alerts, AlertsQuery } from "@/types/graylog/alerts.d"
3 import type { EventDefinition } from "@/types/graylog/event-definition.d"
6 -import type { Stream } from "@/types/graylog/stream.d"
7 -import type { ConfiguredInput, RunningInput } from "@/types/graylog/inputs.d"
8 -import type { PipelineRule, Pipeline, PipelineFull } from "@/types/graylog/pipelines.d"
4 import type { GraylogIndex } from "@/types/graylog/indices"
5 +import type { ConfiguredInput, RunningInput } from "@/types/graylog/inputs.d"
6 +import type { Message } from "@/types/graylog/messages.d"
7 import type { ThroughputMetric } from "@/types/graylog/metrics.d"
8 +import type { Pipeline, PipelineFull, PipelineRule } from "@/types/graylog/pipelines.d"
9 +import type { Stream } from "@/types/graylog/stream.d"
10 +import { HttpClient } from "../httpClient"
11
12 export default {
13 // #region Messages
frontend/src/api/endpoints/healthchecks.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { InfluxDBAlert } from "@/types/healthchecks.d"
3 +import { HttpClient } from "../httpClient"
4
5 export default {
6 getHealthchecks() {
frontend/src/api/endpoints/incidentManagement.ts
+74 -16
@@ -1,6 +1,4 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
3 -import type { SourceConfiguration, SourceName } from "@/types/incidentManagement/sources.d"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type {
3 Alert,
4 AlertComment,
@@ -10,8 +8,11 @@ import type {
8 AlertTag,
9 AlertTimeline
10 } from "@/types/incidentManagement/alerts.d"
13 -import type { Case, CasePayload } from "@/types/incidentManagement/cases.d"
11 +import type { Case, CaseDataStore, CasePayload, CaseStatus } from "@/types/incidentManagement/cases.d"
12 import type { IncidentNotification, IncidentNotificationPayload } from "@/types/incidentManagement/notifications.d"
13 +import type { SourceConfiguration, SourceName } from "@/types/incidentManagement/sources.d"
14 +import type { KeysOfUnion, UnionToIntersection } from "type-fest"
15 +import { HttpClient } from "../httpClient"
16
17 export type AlertsFilter =
18 | { status: AlertStatus }
@@ -19,15 +20,25 @@ export type AlertsFilter =
20 | { assignedTo: string }
21 | { tag: string }
22 | { title: string }
23 + | { customerCode: string }
24 + | { source: string }
25 +
26 +export type AlertsFilterTypes = KeysOfUnion<AlertsFilter>
27
28 export interface AlertsQuery {
29 page: number
30 pageSize: number
31 sort: "asc" | "desc"
27 - filters: AlertsFilter
32 + filters: Partial<UnionToIntersection<AlertsFilter>>
33 }
34
30 -export type CasesFilter = { status: AlertStatus } | { assignedTo: string } | { hostname: string }
35 +export type CasesFilter =
36 + | { status: CaseStatus }
37 + | { assignedTo: string }
38 + | { hostname: string }
39 + | { customerCode: string }
40 +
41 +export type CasesFilterTypes = KeysOfUnion<CasesFilter>
42
43 export type AlertCommentPayload = Omit<AlertComment, "id">
44
@@ -84,21 +95,27 @@ export default {
95 getAlertsList(args: Partial<AlertsQuery>, signal?: AbortSignal) {
96 let url = `/incidents/db_operations/alerts`
97
87 - if (args?.filters && "status" in args.filters) {
98 + if (args?.filters?.status) {
99 url = `/incidents/db_operations/alerts/status/${args.filters.status}`
100 }
90 - if (args?.filters && "assetName" in args.filters) {
101 + if (args?.filters?.assetName) {
102 url = `/incidents/db_operations/alerts/asset/${args.filters.assetName}`
103 }
93 - if (args?.filters && "assignedTo" in args.filters) {
104 + if (args?.filters?.assignedTo) {
105 url = `/incidents/db_operations/alerts/assigned-to/${args.filters.assignedTo}`
106 }
96 - if (args?.filters && "tag" in args.filters) {
107 + if (args?.filters?.tag) {
108 url = `/incidents/db_operations/alert/tag/${args.filters.tag}`
109 }
99 - if (args?.filters && "title" in args.filters) {
110 + if (args?.filters?.title) {
111 url = `/incidents/db_operations/alerts/title/${args.filters.title}`
112 }
113 + if (args?.filters?.customerCode) {
114 + url = `/incidents/db_operations/alerts/customer/${args.filters.customerCode}`
115 + }
116 + if (args?.filters?.source) {
117 + url = `/incidents/db_operations/alerts/source/${args.filters.source}`
118 + }
119
120 return HttpClient.get<
121 FlaskBaseResponse & { alerts: Alert[]; closed: number; in_progress: number; open: number; total: number }
@@ -174,16 +191,19 @@ export default {
191 // #endregion
192
193 // #region Cases
177 - getCasesList(filters?: CasesFilter) {
194 + getCasesList(filters?: Partial<UnionToIntersection<CasesFilter>>) {
195 let url = `/incidents/db_operations/cases`
196
180 - if (filters && "status" in filters) {
197 + if (filters?.status) {
198 url = `/incidents/db_operations/case/status/${filters.status}`
199 }
183 - if (filters && "assignedTo" in filters) {
200 + if (filters?.assignedTo) {
201 url = `/incidents/db_operations/case/assigned-to/${filters.assignedTo}`
202 }
186 - if (filters && "hostname" in filters) {
203 + if (filters?.customerCode) {
204 + url = `/incidents/db_operations/case/customer/${filters.customerCode}`
205 + }
206 + if (filters?.hostname) {
207 url = `/agents/${filters.hostname}/cases`
208 }
209
@@ -212,7 +232,7 @@ export default {
232 }
233 )
234 },
215 - updateCaseStatus(caseId: number, status: AlertStatus) {
235 + updateCaseStatus(caseId: number, status: CaseStatus) {
236 return HttpClient.put<FlaskBaseResponse>(`/incidents/db_operations/case/status`, {
237 case_id: caseId,
238 status
@@ -227,6 +247,44 @@ export default {
247 deleteCase(caseId: number) {
248 return HttpClient.delete<FlaskBaseResponse>(`/incidents/db_operations/case/${caseId}`)
249 },
250 + exportCases(customerCode?: string) {
251 + let url = `/incidents/report/generate-report`
252 +
253 + if (customerCode) {
254 + url = `/incidents/report/generate-report/${customerCode}`
255 + }
256 +
257 + return HttpClient.post<Blob>(url, {
258 + responseType: "blob"
259 + })
260 + },
261 + getCaseDataStoreFiles(caseId: number) {
262 + return HttpClient.get<FlaskBaseResponse & { case_data_store: CaseDataStore[] }>(
263 + `/incidents/db_operations/case/data-store/${caseId}`
264 + )
265 + },
266 + downloadCaseDataStoreFile(caseId: number, fileName: string) {
267 + return HttpClient.get<Blob>(`/incidents/db_operations/case/data-store/download/${caseId}/${fileName}`, {
268 + responseType: "blob"
269 + })
270 + },
271 + uploadCaseDataStoreFile(caseId: number, file: File) {
272 + const form = new FormData()
273 + form.append("file", new Blob([file], { type: file.type }), file.name)
274 +
275 + return HttpClient.post<FlaskBaseResponse & { case_data_store: CaseDataStore }>(
276 + `/incidents/db_operations/case/data-store/upload`,
277 + form,
278 + {
279 + params: {
280 + case_id: caseId
281 + }
282 + }
283 + )
284 + },
285 + deleteCaseDataStoreFile(caseId: number, fileName: string) {
286 + return HttpClient.delete<FlaskBaseResponse>(`/incidents/db_operations/case/data-store/${caseId}/${fileName}`)
287 + },
288 // #endregion
289
290 // #region Notification
frontend/src/api/endpoints/indices.ts
+1 -1
@@ -1,6 +1,6 @@
1 import type { FlaskBaseResponse } from "@/types/flask.d"
2 +import type { ClusterHealth, IndexAllocation, IndexShard, IndexStats } from "@/types/indices.d"
3 import { HttpClient } from "../httpClient"
3 -import type { ClusterHealth, IndexStats, IndexAllocation, IndexShard } from "@/types/indices.d"
4
5 export default {
6 getAllocation() {
frontend/src/api/endpoints/integrations.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { AvailableIntegration, CustomerIntegration } from "@/types/integrations.d"
3 +import { HttpClient } from "../httpClient"
4
5 export interface NewIntegration {
6 customer_code: string
frontend/src/api/endpoints/license.ts
+2 -2
@@ -1,5 +1,4 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type {
3 CheckoutPayload,
4 License,
@@ -8,6 +7,7 @@ import type {
7 LicenseKey,
8 SubscriptionFeature
9 } from "@/types/license.d"
10 +import { HttpClient } from "../httpClient"
11
12 export interface NewLicensePayload {
13 name: string
frontend/src/api/endpoints/logs.ts
+8 -7
@@ -1,24 +1,25 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { Log, LogsQuery, LogsQueryTimeRange } from "@/types/logs.d"
3 +import type { UnionToIntersection } from "type-fest"
4 +import { HttpClient } from "../httpClient"
5
6 export default {
6 - getLogs(query?: LogsQuery) {
7 + getLogs(query?: Partial<UnionToIntersection<LogsQuery>>) {
8 let method: "get" | "post" = "get"
9 let url = "logs"
9 - let body: { time_range: LogsQueryTimeRange } | undefined = undefined
10 + let body: { time_range: LogsQueryTimeRange } | undefined
11
11 - if (query && "userId" in query) {
12 + if (query?.userId) {
13 method = "get"
14 url = `/logs/${query.userId}`
15 body = undefined
15 - } else if (query && "timeRange" in query) {
16 + } else if (query?.timeRange) {
17 method = "post"
18 url = `/logs/timerange`
19 body = {
20 time_range: query.timeRange
21 }
21 - } else if (query && "eventType" in query) {
22 + } else if (query?.eventType) {
23 method = "post"
24 url = `/logs/${query.eventType}`
25 body = undefined
frontend/src/api/endpoints/monitoringAlerts.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { AvailableMonitoringAlert, CustomProvisionPriority, MonitoringAlert } from "@/types/monitoringAlerts.d"
3 +import { HttpClient } from "../httpClient"
4
5 export interface ProvisionsMonitoringAlertParams {
6 searchWithinLast: number
frontend/src/api/endpoints/networkConnectors.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { CustomerNetworkConnector, NetworkConnector } from "@/types/networkConnectors.d"
3 +import { HttpClient } from "../httpClient"
4
5 export interface NewNetworkConnector {
6 customer_code: string
frontend/src/api/endpoints/reporting.ts
+1 -1
@@ -1,6 +1,6 @@
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { Dashboard, Org, Panel, PanelLink } from "@/types/reporting.d"
3 import { HttpClient } from "../httpClient"
3 -import type { FlaskBaseResponse } from "@/types/flask.d"
4
5 export type PanelsLinksTimeUnit = "minutes" | "hours" | "days"
6
frontend/src/api/endpoints/scheduler.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { Job } from "@/types/scheduler.d"
3 +import { HttpClient } from "../httpClient"
4
5 export interface UpdateJobPayload {
6 /** minutes */
frontend/src/api/endpoints/sigma.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { SigmaQuery, SigmaRuleLevels, SigmaTimeInterval } from "@/types/sigma.d"
3 +import { HttpClient } from "../httpClient"
4
5 export default {
6 /** client-side pagination (1k+ items) */
frontend/src/api/endpoints/soc.ts
+4 -4
@@ -1,10 +1,10 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { SocAlert, SocAlertCaseResponse } from "@/types/soc/alert.d"
4 -import type { SocCase, SocCaseExt } from "@/types/soc/case.d"
3 import type { SocAlertAsset, SocCaseAsset, SocCaseAssetsState } from "@/types/soc/asset.d"
4 +import type { SocCase, SocCaseExt } from "@/types/soc/case.d"
5 import type { SocNewNote, SocNote } from "@/types/soc/note.d"
6 import type { SocUser } from "@/types/soc/user.d"
7 +import { HttpClient } from "../httpClient"
8
9 export interface CasesFilter {
10 olderThan: number
@@ -137,7 +137,7 @@ export default {
137 older_than: payload?.olderThan || 1,
138 time_unit: payload?.unit || "days"
139 }
140 - /*eslint no-mixed-spaces-and-tabs: "off"*/
140 + /* eslint no-mixed-spaces-and-tabs: "off" */
141 }
142 : undefined
143 )
frontend/src/api/endpoints/stackProvisioning.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { AvailableContentPack } from "@/types/stackProvisioning.d"
3 +import { HttpClient } from "../httpClient"
4
5 export default {
6 getAvailableContentPacks() {
frontend/src/api/endpoints/threatIntel.ts
+1 -1
@@ -1,6 +1,6 @@
1 -import { HttpClient } from "../httpClient"
1 import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { EpssScore, EvaluationData, ThreatIntelResponse } from "@/types/threatIntel.d"
3 +import { HttpClient } from "../httpClient"
4
5 export default {
6 create(iocValue: string) {
frontend/src/api/endpoints/webVulnerabilityAssessment.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { type FlaskBaseResponse } from "@/types/flask.d"
2 -import { HttpClient } from "../httpClient"
1 +import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { NucleiReport, ScanHostPayload } from "@/types/webVulnerabilityAssessment.d"
3 +import { HttpClient } from "../httpClient"
4
5 export default {
6 getAllReports() {
frontend/src/api/httpClient.ts
+1 -1
@@ -37,7 +37,7 @@ HttpClient.interceptors.response.use(
37 response => response,
38 error => {
39 if (error.response && error.response.status === 401) {
40 - if (window.location.pathname.indexOf("login") === -1) {
40 + if (!window.location.pathname.includes("login")) {
41 window.location.href = "/logout"
42 }
43 /*
frontend/src/api/index.ts
+14 -14
@@ -1,29 +1,29 @@
1 +import activeResponse from "./endpoints/activeResponse"
2 import agents from "./endpoints/agents"
3 import alerts from "./endpoints/alerts"
4 import artifacts from "./endpoints/artifacts"
5 +import askSocfortress from "./endpoints/askSocfortress"
6 import auth from "./endpoints/auth"
7 +import cloudSecurityAssessment from "./endpoints/cloudSecurityAssessment"
8 import connectors from "./endpoints/connectors"
6 -import graylog from "./endpoints/graylog"
7 -import indices from "./endpoints/indices"
8 -import soc from "./endpoints/soc"
9 -import healthchecks from "./endpoints/healthchecks"
10 -import threatIntel from "./endpoints/threatIntel"
11 -import askSocfortress from "./endpoints/askSocfortress"
9 import customers from "./endpoints/customers"
13 -import logs from "./endpoints/logs"
10 import flow from "./endpoints/flow"
11 +import graylog from "./endpoints/graylog"
12 +import healthchecks from "./endpoints/healthchecks"
13 +import incidentManagement from "./endpoints/incidentManagement"
14 +import indices from "./endpoints/indices"
15 import integrations from "./endpoints/integrations"
16 +import license from "./endpoints/license"
17 +import logs from "./endpoints/logs"
18 import monitoringAlerts from "./endpoints/monitoringAlerts"
17 -import activeResponse from "./endpoints/activeResponse"
18 -import stackProvisioning from "./endpoints/stackProvisioning"
19 +import networkConnectors from "./endpoints/networkConnectors"
20 import reporting from "./endpoints/reporting"
20 -import license from "./endpoints/license"
21 import scheduler from "./endpoints/scheduler"
22 -import networkConnectors from "./endpoints/networkConnectors"
23 -import cloudSecurityAssessment from "./endpoints/cloudSecurityAssessment"
24 -import webVulnerabilityAssessment from "./endpoints/webVulnerabilityAssessment"
25 -import incidentManagement from "./endpoints/incidentManagement"
22 import sigma from "./endpoints/sigma"
23 +import soc from "./endpoints/soc"
24 +import stackProvisioning from "./endpoints/stackProvisioning"
25 +import threatIntel from "./endpoints/threatIntel"
26 +import webVulnerabilityAssessment from "./endpoints/webVulnerabilityAssessment"
27
28 export default {
29 agents,
frontend/src/components/__tests__/SampleComponent.spec.ts
+3 -3
@@ -1,9 +1,9 @@
1 -import { describe, it, expect } from "vitest"
2 -
1 import { mount } from "@vue/test-utils"
2 +
3 +import { describe, expect, it } from "vitest"
4 import TestScope from "../common/TestScope.vue"
5
6 -describe("Sample Component", () => {
6 +describe("sample Component", () => {
7 it("renders properly", () => {
8 const wrapper = mount(TestScope, { props: { msg: "Hello Vitest" } })
9 expect(wrapper.text()).toContain("Hello Vitest")
frontend/src/components/activeResponse/ActiveResponseActions.vue
+18 -17
@@ -1,7 +1,9 @@
1 <template>
2 <div class="active-response-actions flex gap-2 justify-end">
3 - <n-button type="success" secondary :size="size" @click="showInvokeForm = true" :loading="loadingInvoke">
4 - <template #icon><Icon :name="InvokeIcon"></Icon></template>
3 + <n-button type="success" secondary :size :loading="loadingInvoke" @click="showInvokeForm = true">
4 + <template #icon>
5 + <Icon :name="InvokeIcon"></Icon>
6 + </template>
7 Invoke Action
8 </n-button>
9
@@ -16,15 +18,15 @@
18 segmented
19 >
20 <ActiveResponseInvokeForm
19 - :activeResponse="activeResponse"
20 - :agentId="agentId"
21 + :active-response="activeResponse"
22 + :agent-id="agentId"
23 @mounted="activeResponseInvokeFormCTX = $event"
24 @submitted="close()"
23 - @startLoading="loadingInvoke = true"
24 - @stopLoading="loadingInvoke = false"
25 + @start-loading="loadingInvoke = true"
26 + @stop-loading="loadingInvoke = false"
27 >
28 <template #additionalActions>
27 - <n-button @click="close()" secondary>Close</n-button>
29 + <n-button secondary @click="close()">Close</n-button>
30 </template>
31 </ActiveResponseInvokeForm>
32 </n-modal>
@@ -32,18 +34,12 @@
34 </template>
35
36 <script setup lang="ts">
35 -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"
37 import type { SupportedActiveResponse } from "@/types/activeResponse.d"
40 -import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
38 import type { Size } from "naive-ui/es/button/src/interface"
42 -
43 -const emit = defineEmits<{
44 - (e: "startLoading"): void
45 - (e: "stopLoading"): void
46 -}>()
39 +import Icon from "@/components/common/Icon.vue"
40 +import { NButton, NModal } from "naive-ui"
41 +import { computed, ref, watch } from "vue"
42 +import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
43
44 const { activeResponse, size, agentId } = defineProps<{
45 activeResponse: SupportedActiveResponse
@@ -51,6 +47,11 @@ const { activeResponse, size, agentId } = defineProps<{
47 size?: Size
48 }>()
49
50 +const emit = defineEmits<{
51 + (e: "startLoading"): void
52 + (e: "stopLoading"): void
53 +}>()
54 +
55 const InvokeIcon = "solar:playback-speed-outline"
56 const showInvokeForm = ref(false)
57 const loadingInvoke = ref(false)
frontend/src/components/activeResponse/ActiveResponseAgent.vue
+6 -6
@@ -6,14 +6,14 @@
6 <ActiveResponseItem
7 v-for="activeResponse of activeResponseList"
8 :key="activeResponse.name"
9 - :activeResponse="activeResponse"
9 + :active-response="activeResponse"
10 :embedded="embedded"
11 :agent-id="agent.agent_id"
12 class="item-appear item-appear-bottom item-appear-005 mb-2"
13 />
14 </template>
15 <template v-else>
16 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingActiveResponse" />
16 + <n-empty v-if="!loadingActiveResponse" description="No items found" class="justify-center h-48" />
17 </template>
18 </div>
19 </n-spin>
@@ -21,12 +21,12 @@
21 </template>
22
23 <script setup lang="ts">
24 -import { ref, onBeforeMount } from "vue"
25 -import { useMessage, NSpin, NEmpty } from "naive-ui"
24 +import type { SupportedActiveResponse } from "@/types/activeResponse.d"
25 +import type { Agent } from "@/types/agents.d"
26 import Api from "@/api"
27 +import { NEmpty, NSpin, useMessage } from "naive-ui"
28 +import { onBeforeMount, ref } from "vue"
29 import ActiveResponseItem from "./ActiveResponseItem.vue"
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
+6 -5
@@ -7,23 +7,24 @@
7 </Suspense>
8 </template>
9 <template v-else>
10 - <n-empty description="No description found" class="justify-center h-48" v-if="!loadingActiveResponse" />
10 + <n-empty v-if="!loadingActiveResponse" description="No description found" class="justify-center h-48" />
11 </template>
12 </n-spin>
13 </div>
14 </template>
15
16 <script setup lang="ts">
17 -import { ref, onBeforeMount, defineAsyncComponent } from "vue"
18 -import { useMessage, NSpin, NEmpty } from "naive-ui"
19 -import Api from "@/api"
17 import type { ActiveResponseDetails, SupportedActiveResponse } from "@/types/activeResponse.d"
21 -const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
18 +import Api from "@/api"
19 +import { NEmpty, NSpin, useMessage } from "naive-ui"
20 +import { defineAsyncComponent, onBeforeMount, ref } from "vue"
21
22 const { activeResponse } = defineProps<{
23 activeResponse: SupportedActiveResponse
24 }>()
25
26 +const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
27 +
28 const message = useMessage()
29 const loadingActiveResponse = ref(false)
30 const activeResponseDetails = ref<ActiveResponseDetails>()
frontend/src/components/activeResponse/ActiveResponseInvokeForm.vue
+16 -16
@@ -2,7 +2,7 @@
2 <div class="active-response-invoke-form flex flex-col justify-between grow">
3 <div class="form-box">
4 <n-spin v-model:show="loading">
5 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
5 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
6 <div class="grid gap-6 grid-auto-fit-200">
7 <n-form-item label="Action" path="action">
8 <n-select v-model:value="form.action" :options="invokeActionOptions" />
@@ -22,36 +22,41 @@
22 <div class="flex gap-3">
23 <slot name="additionalActions"></slot>
24 </div>
25 - <n-button type="primary" :disabled="!isValid" @click="validate()" :loading="loading">Submit</n-button>
25 + <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">Submit</n-button>
26 </div>
27 </div>
28 </template>
29
30 <script setup lang="ts">
31 -import { computed, onMounted, ref, watch } from "vue"
31 +import type { InvokeRequest, InvokeRequestAction } from "@/api/endpoints/activeResponse"
32 +import type { SupportedActiveResponse } from "@/types/activeResponse.d"
33 +import Api from "@/api"
34 import {
35 + type FormInst,
36 + type FormItemRule,
37 + type FormRules,
38 + type FormValidationError,
39 NButton,
40 NForm,
41 NFormItem,
42 NInput,
43 NSelect,
44 NSpin,
39 - useMessage,
40 - type FormItemRule,
41 - type FormRules,
42 - type FormInst,
43 - type FormValidationError
45 + useMessage
46 } from "naive-ui"
45 -import Api from "@/api"
47 import isIP from "validator/es/lib/isIP"
47 -import type { InvokeRequest, InvokeRequestAction } from "@/api/endpoints/activeResponse"
48 -import type { SupportedActiveResponse } from "@/types/activeResponse.d"
48 +import { computed, onMounted, ref, watch } from "vue"
49
50 interface InvokeForm {
51 action: null | InvokeRequestAction
52 ip: string
53 }
54
55 +const { activeResponse, agentId } = defineProps<{
56 + activeResponse: SupportedActiveResponse
57 + agentId?: string | number
58 +}>()
59 +
60 const emit = defineEmits<{
61 (e: "submitted"): void
62 (e: "startLoading"): void
@@ -64,11 +69,6 @@ const emit = defineEmits<{
69 ): void
70 }>()
71
67 -const { activeResponse, agentId } = defineProps<{
68 - activeResponse: SupportedActiveResponse
69 - agentId?: string | number
70 -}>()
71 -
72 const message = useMessage()
73 const form = ref<InvokeForm>(getClearForm())
74 const formRef = ref<FormInst | null>(null)
frontend/src/components/activeResponse/ActiveResponseItem.vue
+17 -13
@@ -2,9 +2,13 @@
2 <div class="active-response-item" :class="{ embedded }">
3 <div class="px-4 py-3 flex flex-col gap-2">
4 <div class="header-box flex justify-between items-center">
5 - <div class="title">{{ activeResponse.name }}</div>
5 + <div class="title">
6 + {{ activeResponse.name }}
7 + </div>
8 <n-button size="small" @click.stop="showDetails = true">
7 - <template #icon><Icon :name="InfoIcon"></Icon></template>
9 + <template #icon>
10 + <Icon :name="InfoIcon"></Icon>
11 + </template>
12 </n-button>
13 </div>
14 <div class="main-box flex items-center gap-3">
@@ -14,21 +18,21 @@
18 </div>
19 </div>
20 <ActiveResponseActions
17 - class="actions-box"
21 v-if="!hideActions"
19 - :agentId="agentId"
20 - :activeResponse="activeResponse"
22 + class="actions-box"
23 + :agent-id="agentId"
24 + :active-response="activeResponse"
25 @start-loading="loading = true"
26 @stop-loading="loading = false"
27 />
28 </div>
29 <div class="footer-box flex justify-between items-center gap-4">
30 <ActiveResponseActions
27 - class="actions-box"
31 v-if="!hideActions"
29 - :agentId="agentId"
30 - :activeResponse="activeResponse"
31 - :size="'small'"
32 + class="actions-box"
33 + :agent-id="agentId"
34 + :active-response="activeResponse"
35 + size="small"
36 @start-loading="loading = true"
37 @stop-loading="loading = false"
38 />
@@ -43,18 +47,18 @@
47 :bordered="false"
48 segmented
49 >
46 - <ActiveResponseDetails :activeResponse="activeResponse" />
50 + <ActiveResponseDetails :active-response="activeResponse" />
51 </n-modal>
52 </div>
53 </template>
54
55 <script setup lang="ts">
52 -import { ref, toRefs } from "vue"
56 import type { SupportedActiveResponse } from "@/types/activeResponse.d"
57 +import Icon from "@/components/common/Icon.vue"
58 +import { NButton, NModal } from "naive-ui"
59 +import { ref, toRefs } from "vue"
60 import ActiveResponseActions from "./ActiveResponseActions.vue"
61 import ActiveResponseDetails from "./ActiveResponseDetails.vue"
56 -import { NButton, NModal } from "naive-ui"
57 -import Icon from "@/components/common/Icon.vue"
62
63 const props = defineProps<{
64 activeResponse: SupportedActiveResponse
frontend/src/components/activeResponse/ActiveResponseWizard.vue
+15 -16
@@ -36,7 +36,7 @@
36 <ActiveResponseItem
37 v-for="activeResponse of activeResponseFiltered"
38 :key="activeResponse.name"
39 - :activeResponse="activeResponse"
39 + :active-response="activeResponse"
40 embedded
41 hide-actions
42 class="mb-2 cursor-pointer"
@@ -45,9 +45,9 @@
45 </template>
46 <template v-else>
47 <n-empty
48 + v-if="!loadingActiveResponse"
49 description="No items found"
50 class="justify-center h-48"
50 - v-if="!loadingActiveResponse"
51 />
52 </template>
53 </div>
@@ -56,14 +56,14 @@
56 <div v-else-if="current === 3" class="px-7 grow flex flex-col pb-7" style="min-height: 401px">
57 <ActiveResponseInvokeForm
58 v-if="selectedActiveResponse"
59 - :activeResponse="selectedActiveResponse"
59 + :active-response="selectedActiveResponse"
60 @mounted="activeResponseInvokeFormCTX = $event"
61 @submitted="reset()"
62 - @startLoading="loadingActiveResponseInvoke = true"
63 - @stopLoading="loadingActiveResponseInvoke = false"
62 + @start-loading="loadingActiveResponseInvoke = true"
63 + @stop-loading="loadingActiveResponseInvoke = false"
64 >
65 <template #additionalActions>
66 - <n-button @click="prev()" :disabled="loadingActiveResponseInvoke">
66 + <n-button :disabled="loadingActiveResponseInvoke" @click="prev()">
67 <template #icon>
68 <Icon :name="ArrowLeftIcon"></Icon>
69 </template>
@@ -76,12 +76,12 @@
76 </div>
77 </div>
78
79 - <div class="flex justify-between gap-4 p-7 pt-4" v-if="current !== 3">
79 + <div v-if="current !== 3" class="flex justify-between gap-4 p-7 pt-4">
80 <div class="flex gap-4">
81 <slot name="additionalActions"></slot>
82 </div>
83 <div class="flex gap-4">
84 - <n-button @click="prev()" v-if="isPrevStepEnabled">
84 + <n-button v-if="isPrevStepEnabled" @click="prev()">
85 <template #icon>
86 <Icon :name="ArrowLeftIcon"></Icon>
87 </template>
@@ -94,16 +94,15 @@
94 </template>
95
96 <script setup lang="ts">
97 -import { computed, onMounted, ref, watch } from "vue"
98 -import { NSteps, NStep, useMessage, NScrollbar, NButton, NEmpty, NSpin, type StepsProps } from "naive-ui"
99 -import Icon from "@/components/common/Icon.vue"
100 -import Api from "@/api"
101 -import { onBeforeMount } from "vue"
97 import type { SupportedActiveResponse } from "@/types/activeResponse.d"
103 -import ActiveResponseItem from "./ActiveResponseItem.vue"
104 -import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
105 -import { iconFromOs } from "@/utils"
98 import type { OsTypesLower } from "@/types/common.d"
99 +import Api from "@/api"
100 +import Icon from "@/components/common/Icon.vue"
101 +import { iconFromOs } from "@/utils"
102 +import { NButton, NEmpty, NScrollbar, NSpin, NStep, NSteps, type StepsProps, useMessage } from "naive-ui"
103 +import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
104 +import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
105 +import ActiveResponseItem from "./ActiveResponseItem.vue"
106
107 const emit = defineEmits<{
108 (e: "update:loading", value: boolean): void
frontend/src/components/activeResponse/ActiveResponseWizardButton.vue
+8 -6
@@ -1,6 +1,8 @@
1 <template>
2 - <n-button :size="size" :type="type" @click="showInvokeWizard = true" :loading="loading">
3 - <template #icon><Icon :name="InvokeIcon"></Icon></template>
2 + <n-button :size="size" :type="type" :loading="loading" @click="showInvokeWizard = true">
3 + <template #icon>
4 + <Icon :name="InvokeIcon"></Icon>
5 + </template>
6 Active Response
7 </n-button>
8
@@ -14,16 +16,16 @@
16 content-class="flex flex-col !p-0"
17 segmented
18 >
17 - <ActiveResponseWizard @mounted="activeResponseWizardCTX = $event" v-model:loading="loading" />
19 + <ActiveResponseWizard v-model:loading="loading" @mounted="activeResponseWizardCTX = $event" />
20 </n-modal>
21 </template>
22
23 <script setup lang="ts">
22 -import { ref, watch } from "vue"
23 -import { NButton, NModal } from "naive-ui"
24 +import type { Size, Type } from "naive-ui/es/button/src/interface"
25 import Icon from "@/components/common/Icon.vue"
26 +import { NButton, NModal } from "naive-ui"
27 +import { ref, watch } from "vue"
28 import ActiveResponseWizard from "./ActiveResponseWizard.vue"
26 -import type { Size, Type } from "naive-ui/es/button/src/interface"
29
30 const { type, size } = defineProps<{
31 size?: Size
frontend/src/components/agents/AgentCard.vue
+18 -13
@@ -12,7 +12,9 @@
12 <n-tooltip>
13 {{ `${isOnline ? "online" : "last seen"} - ${formatLastSeen}` }}
14 <template #trigger>
15 - <div class="hostname" :class="{ online: isOnline }">{{ agent.hostname }}</div>
15 + <div class="hostname" :class="{ online: isOnline }">
16 + {{ agent.hostname }}
17 + </div>
18 </template>
19 </n-tooltip>
20 <div class="critical" :class="{ active: agent.critical_asset }">
@@ -32,7 +34,7 @@
34 </template>
35 </n-tooltip>
36 </div>
35 - <div class="quarantined" v-show="agent.quarantined">
37 + <div v-show="agent.quarantined" class="quarantined">
38 <n-tooltip>
39 Quarantined
40 <template #trigger>
@@ -47,10 +49,12 @@
49 <div class="os" :title="agent.os">
50 {{ agent.os }}
51 </div>
50 - <div class="ip-address" :title="agent.ip_address">{{ agent.ip_address }}</div>
52 + <div class="ip-address" :title="agent.ip_address">
53 + {{ agent.ip_address }}
54 + </div>
55 </div>
56
53 - <div class="agent-actions" v-if="showActions">
57 + <div v-if="showActions" class="agent-actions">
58 <div class="box">
59 <n-tooltip>
60 Delete
@@ -70,23 +74,24 @@
74 </template>
75
76 <script setup lang="ts">
73 -import { computed, ref, toRefs } from "vue"
74 -import { AgentStatus, type Agent } from "@/types/agents.d"
75 -import dayjs from "@/utils/dayjs"
76 -import { handleDeleteAgent, toggleAgentCritical } from "./utils"
77 -import { NTooltip, NButton, NSpin, NCard, useMessage, useDialog } from "naive-ui"
77 import Icon from "@/components/common/Icon.vue"
78 import { useSettingsStore } from "@/stores/settings"
80 -
81 -const emit = defineEmits<{
82 - (e: "delete"): void
83 -}>()
79 +import { type Agent, AgentStatus } from "@/types/agents.d"
80 +import dayjs from "@/utils/dayjs"
81 +import { NButton, NCard, NSpin, NTooltip, useDialog, useMessage } from "naive-ui"
82 +import { computed, ref, toRefs } from "vue"
83 +import { handleDeleteAgent, toggleAgentCritical } from "./utils"
84
85 const props = defineProps<{
86 agent: Agent
87 showActions?: boolean
88 bgSecondary?: boolean
89 }>()
90 +
91 +const emit = defineEmits<{
92 + (e: "delete"): void
93 +}>()
94 +
95 const { agent, showActions, bgSecondary } = toRefs(props)
96
97 const QuarantinedIcon = "ph:seal-warning-light"
frontend/src/components/agents/AgentCases.vue
+7 -7
@@ -13,25 +13,25 @@
13 <SocCaseItem
14 v-for="item of casesList"
15 :key="item"
16 - :caseId="item"
17 - @deleted="getData()"
16 + :case-id="item"
17 class="item-appear item-appear-bottom item-appear-005"
18 + @deleted="getData()"
19 />
20 </template>
21 <template v-else>
22 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
22 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
23 </template>
24 </div>
25 </n-spin>
26 </template>
27
28 <script setup lang="ts">
29 -import { ref, onBeforeMount, toRefs, onBeforeUnmount } from "vue"
30 -import { useMessage, NSpin, NEmpty } from "naive-ui"
31 -import SocCaseItem from "@/components/soc/SocCases/SocCaseItem.vue"
32 -import Api from "@/api"
29 import type { Agent } from "@/types/agents.d"
30 +import Api from "@/api"
31 +import SocCaseItem from "@/components/soc/SocCases/SocCaseItem.vue"
32 import axios from "axios"
33 +import { NEmpty, NSpin, useMessage } from "naive-ui"
34 +import { onBeforeMount, onBeforeUnmount, ref, toRefs } from "vue"
35
36 const props = defineProps<{
37 agent: Agent
frontend/src/components/agents/AgentToolbar.vue
+19 -18
@@ -3,16 +3,16 @@
3 <div class="wrapper flex flex-col gap-6 py-3 px-4">
4 <div class="flex flex-col gap-2">
5 <div class="agent-search flex gap-3">
6 - <n-input placeholder="Search for an agent" clearable v-model:value="textFilter">
6 + <n-input v-model:value="textFilter" placeholder="Search for an agent" clearable>
7 <template #prefix>
8 <Icon :name="SearchIcon" />
9 </template>
10 </n-input>
11 - <n-button @click="emit('sync')" :loading="syncing">Sync</n-button>
11 + <n-button :loading="syncing" @click="emit('sync')">Sync</n-button>
12 </div>
13 <div class="search-info">
14 <strong v-if="agentsFilteredLength !== agentsLength">{{ agentsFilteredLength }}</strong>
15 - <span class="mh-5" v-if="agentsFilteredLength !== agentsLength">/</span>
15 + <span v-if="agentsFilteredLength !== agentsLength" class="mh-5">/</span>
16 <strong class="font-mono">{{ agentsLength }}</strong>
17 Agents
18 </div>
@@ -20,32 +20,32 @@
20
21 <div class="agents-list flex grow flex-col overflow-hidden">
22 <n-scrollbar>
23 - <div class="agents-critical-list" v-if="agentsCritical?.length">
23 + <div v-if="agentsCritical?.length" class="agents-critical-list">
24 <div class="title">
25 Critical Assets
26 <small class="text-secondary-color font-mono">({{ agentsCritical.length }})</small>
27 </div>
28 <div class="list">
29 <div
30 - class="item"
30 v-for="agent in agentsCritical"
31 :key="agent.agent_id"
32 + class="item"
33 @click="emit('click', agent)"
34 >
35 {{ agent.hostname }}
36 </div>
37 </div>
38 </div>
39 - <div class="agents-online-list" v-if="agentsOnline?.length">
39 + <div v-if="agentsOnline?.length" class="agents-online-list">
40 <div class="title">
41 Online Agents
42 <small class="text-secondary-color font-mono">({{ agentsOnline.length }})</small>
43 </div>
44 <div class="list">
45 <div
46 - class="item"
46 v-for="agent in agentsOnline"
47 :key="agent.agent_id"
48 + class="item"
49 @click="emit('click', agent)"
50 >
51 {{ agent.hostname }}
@@ -59,18 +59,10 @@
59 </template>
60
61 <script setup lang="ts">
62 -import { computed, toRefs } from "vue"
63 -import { type Agent } from "@/types/agents.d"
64 -import { NInput, NButton, NCard, NScrollbar } from "naive-ui"
62 +import type { Agent } from "@/types/agents.d"
63 import Icon from "@/components/common/Icon.vue"
66 -
67 -const SearchIcon = "carbon:search"
68 -
69 -const emit = defineEmits<{
70 - (e: "sync"): void
71 - (e: "update:modelValue", value: string): void
72 - (e: "click", value: Agent): void
73 -}>()
64 +import { NButton, NCard, NInput, NScrollbar } from "naive-ui"
65 +import { computed, toRefs } from "vue"
66
67 const props = defineProps<{
68 modelValue: string
@@ -80,6 +72,15 @@ const props = defineProps<{
72 agentsCritical?: Agent[]
73 agentsOnline?: Agent[]
74 }>()
75 +
76 +const emit = defineEmits<{
77 + (e: "sync"): void
78 + (e: "update:modelValue", value: string): void
79 + (e: "click", value: Agent): void
80 +}>()
81 +
82 +const SearchIcon = "carbon:search"
83 +
84 const { modelValue, syncing, agentsLength, agentsFilteredLength, agentsCritical, agentsOnline } = toRefs(props)
85
86 const textFilter = computed<string>({
frontend/src/components/agents/AgentVelociraptorIdForm.vue
+8 -7
@@ -13,11 +13,11 @@
13 >
14 <template #suffix>
15 <Icon
16 + v-if="!loading"
17 :name="CloseIcon"
18 :size="13"
19 class="cursor-pointer"
20 @click="editing = false"
20 - v-if="!loading"
21 />
22 </template>
23 </n-input>
@@ -29,23 +29,24 @@
29 </template>
30
31 <script setup lang="ts">
32 -import { onBeforeMount, ref, toRefs } from "vue"
32 +import type { Agent } from "@/types/agents.d"
33 import Api from "@/api"
34 -import { useMessage, NInput, NButton, NInputGroup } from "naive-ui"
34 import Icon from "@/components/common/Icon.vue"
36 -import type { Agent } from "@/types/agents.d"
37 -
38 -const velociraptorId = defineModel<string>("velociraptorId", { default: "" })
35 +import { NButton, NInput, NInputGroup, useMessage } from "naive-ui"
36 +import { onBeforeMount, ref, toRefs } from "vue"
37
38 const props = defineProps<{
39 agent: Agent
40 }>()
43 -const { agent } = toRefs(props)
41
42 const emit = defineEmits<{
43 (e: "updated", value: string): void
44 }>()
45
46 +const velociraptorId = defineModel<string>("velociraptorId", { default: "" })
47 +
48 +const { agent } = toRefs(props)
49 +
50 const LoadingIcon = "eos-icons:loading"
51 const EditIcon = "uil:edit-alt"
52 const CloseIcon = "carbon:close-filled"
frontend/src/components/agents/OverviewSection.vue
+13 -10
@@ -2,7 +2,9 @@
2 <div class="overview-section">
3 <div class="property-group gap-2 grid grid-auto-fit-250">
4 <KVCard v-for="item of propsSanitized" :key="item.key">
5 - <template #key>{{ item.key }}</template>
5 + <template #key>
6 + {{ item.key }}
7 + </template>
8 <template #value>
9 <template v-if="item.key === 'customer_code' && item.val !== '-'">
10 <code class="cursor-pointer text-primary-color" @click="gotoCustomer({ code: item.val })">
@@ -11,7 +13,7 @@
13 </code>
14 </template>
15 <template v-else-if="item.key === 'velociraptor_id'">
14 - <AgentVelociraptorIdForm v-model:velociraptorId="item.val" :agent @updated="emit('updated')" />
16 + <AgentVelociraptorIdForm v-model:velociraptor-id="item.val" :agent @updated="emit('updated')" />
17 </template>
18 <template v-else>
19 {{ item.val ?? "-" }}
@@ -23,22 +25,23 @@
25 </template>
26
27 <script setup lang="ts">
26 -import { computed, toRefs } from "vue"
27 -import { formatDate } from "@/utils"
28 -import { type Agent } from "@/types/agents.d"
29 -import { useSettingsStore } from "@/stores/settings"
30 -import KVCard from "@/components/common/KVCard.vue"
28 +import type { Agent } from "@/types/agents.d"
29 import Icon from "@/components/common/Icon.vue"
30 +import KVCard from "@/components/common/KVCard.vue"
31 import { useGoto } from "@/composables/useGoto"
32 +import { useSettingsStore } from "@/stores/settings"
33 +import { formatDate } from "@/utils"
34 +import { computed, toRefs } from "vue"
35 import AgentVelociraptorIdForm from "./AgentVelociraptorIdForm.vue"
36
37 +const props = defineProps<{
38 + agent: Agent
39 +}>()
40 +
41 const emit = defineEmits<{
42 (e: "updated"): void
43 }>()
44
39 -const props = defineProps<{
40 - agent: Agent
41 -}>()
45 const { agent } = toRefs(props)
46
47 const LinkIcon = "carbon:launch"
frontend/src/components/agents/agentFlow/AgentFlowCollectList.vue
+5 -5
@@ -19,19 +19,19 @@
19 />
20 </template>
21 <template v-else>
22 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
22 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
23 </template>
24 </div>
25 </n-spin>
26 </template>
27
28 <script setup lang="ts">
29 -import { ref, onBeforeMount } from "vue"
30 -import { useMessage, NSpin, NEmpty } from "naive-ui"
31 -import CollectItem from "@/components/artifacts/CollectItem.vue"
32 -import Api from "@/api"
29 import type { CollectResult, FlowResult } from "@/types/flow.d"
30 +import Api from "@/api"
31 +import CollectItem from "@/components/artifacts/CollectItem.vue"
32 +import { NEmpty, NSpin, useMessage } from "naive-ui"
33 import { nanoid } from "nanoid"
34 +import { onBeforeMount, ref } from "vue"
35
36 const { flow } = defineProps<{
37 flow: FlowResult
frontend/src/components/agents/agentFlow/AgentFlowItem.vue
+37 -22
@@ -32,15 +32,21 @@
32 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
33 <Badge type="splitted" color="primary">
34 <template #label>State</template>
35 - <template #value>{{ flow.state || "-" }}</template>
35 + <template #value>
36 + {{ flow.state || "-" }}
37 + </template>
38 </Badge>
39 <Badge type="splitted" color="primary">
40 <template #label>Status</template>
39 - <template #value>{{ flow.status || "-" }}</template>
41 + <template #value>
42 + {{ flow.status || "-" }}
43 + </template>
44 </Badge>
45 <Badge type="splitted" color="primary">
46 <template #label>Exec. time</template>
43 - <template #value>{{ executionDuration }}</template>
47 + <template #value>
48 + {{ executionDuration }}
49 + </template>
50 </Badge>
51 <Badge :type="flow.dirty ? 'active' : 'muted'">
52 <template #iconRight>
@@ -57,7 +63,9 @@
63 </div>
64 </div>
65 <div class="footer-box">
60 - <div class="time">{{ formatDate(flow.start_time, dFormats.datetimesec) }}</div>
66 + <div class="time">
67 + {{ formatDate(flow.start_time, dFormats.datetimesec) }}
68 + </div>
69 </div>
70
71 <n-modal
@@ -65,16 +73,20 @@
73 preset="card"
74 content-class="!p-0"
75 :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
68 - :title="'Agent Flow: ' + flow.session_id"
76 + :title="`Agent Flow: ${flow.session_id}`"
77 :bordered="false"
78 segmented
79 >
80 <n-tabs type="line" animated :tabs-padding="24">
81 <n-tab-pane name="Info" tab="Info" display-directive="show">
74 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4" v-if="properties">
82 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
83 <KVCard v-for="(value, key) of properties" :key="key">
76 - <template #key>{{ key }}</template>
77 - <template #value>{{ value === "" ? "-" : value ?? "-" }}</template>
84 + <template #key>
85 + {{ key }}
86 + </template>
87 + <template #value>
88 + {{ value === "" ? "-" : value ?? "-" }}
89 + </template>
90 </KVCard>
91 </div>
92 </n-tab-pane>
@@ -99,17 +111,21 @@
111 </div>
112 </n-tab-pane>
113 <n-tab-pane name="Logs" tab="Logs" display-directive="show:lazy">
102 - <div class="p-7 pt-4" v-if="flow.logs.length">
114 + <div v-if="flow.logs.length" class="p-7 pt-4">
115 <ul>
104 - <li v-for="log of flow.logs" :key="log">{{ log }}</li>
116 + <li v-for="log of flow.logs" :key="log">
117 + {{ log }}
118 + </li>
119 </ul>
120 </div>
121 <n-empty v-else description="No items found" class="justify-center h-48" />
122 </n-tab-pane>
123 <n-tab-pane name="Uploaded files" tab="Uploaded files" display-directive="show:lazy">
110 - <div class="p-7 pt-4" v-if="flow.uploaded_files.length">
124 + <div v-if="flow.uploaded_files.length" class="p-7 pt-4">
125 <ul>
112 - <li v-for="file of flow.uploaded_files" :key="file">{{ file }}</li>
126 + <li v-for="file of flow.uploaded_files" :key="file">
127 + {{ file }}
128 + </li>
129 </ul>
130 </div>
131 <n-empty v-else description="No items found" class="justify-center h-48" />
@@ -125,7 +141,7 @@
141 class="mb-2 item-appear item-appear-bottom item-appear-005"
142 />
143 </template>
128 - <n-empty description="No items found" class="justify-center h-48" v-else />
144 + <n-empty v-else description="No items found" class="justify-center h-48" />
145 </div>
146 </n-tab-pane>
147 <n-tab-pane name="Request" tab="Request" display-directive="show:lazy">
@@ -133,7 +149,7 @@
149 <SimpleJsonViewer
150 class="vuesjv-override"
151 :model-value="flow.request"
136 - :initialExpandedDepth="2"
152 + :initial-expanded-depth="2"
153 />
154 </div>
155 </n-tab-pane>
@@ -151,25 +167,24 @@
167 </template>
168
169 <script setup lang="ts">
154 -import { computed, defineAsyncComponent, ref } from "vue"
155 -import { NPopover, NModal, NTabs, NTabPane, NEmpty, NScrollbar, NInput } from "naive-ui"
156 -import { useSettingsStore } from "@/stores/settings"
157 -import dayjs from "@/utils/dayjs"
158 -import { formatDate } from "@/utils"
170 import type { FlowResult } from "@/types/flow.d"
171 +import Badge from "@/components/common/Badge.vue"
172 import Icon from "@/components/common/Icon.vue"
173 import KVCard from "@/components/common/KVCard.vue"
174 +import { useSettingsStore } from "@/stores/settings"
175 +import { formatDate } from "@/utils"
176 +import dayjs from "@/utils/dayjs"
177 import _pick from "lodash/pick"
163 -import Badge from "@/components/common/Badge.vue"
178 +import { NEmpty, NInput, NModal, NPopover, NScrollbar, NTabPane, NTabs } from "naive-ui"
179 +import { computed, defineAsyncComponent, ref } from "vue"
180 import { SimpleJsonViewer } from "vue-sjv"
181 import "@/assets/scss/vuesjv-override.scss"
182
183 +const { flow, embedded } = defineProps<{ flow: FlowResult; embedded?: boolean }>()
184 const AgentFlowTimeline = defineAsyncComponent(() => import("./AgentFlowTimeline.vue"))
185 const AgentFlowQueryStat = defineAsyncComponent(() => import("./AgentFlowQueryStat.vue"))
186 const AgentFlowCollectList = defineAsyncComponent(() => import("./AgentFlowCollectList.vue"))
187
171 -const { flow, embedded } = defineProps<{ flow: FlowResult; embedded?: boolean }>()
172 -
188 const TimeIcon = "carbon:time"
189 const InfoIcon = "carbon:information"
190 const DisabledIcon = "carbon:subtract"
frontend/src/components/agents/agentFlow/AgentFlowList.vue
+5 -5
@@ -19,20 +19,20 @@
19 />
20 </template>
21 <template v-else>
22 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
22 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
23 </template>
24 </div>
25 </n-spin>
26 </template>
27
28 <script setup lang="ts">
29 -import { ref, onBeforeMount, toRefs } from "vue"
30 -import { useMessage, NSpin, NEmpty } from "naive-ui"
31 -import AgentFlowItem from "./AgentFlowItem.vue"
32 -import Api from "@/api"
29 import type { Agent } from "@/types/agents.d"
30 import type { FlowResult } from "@/types/flow.d"
31 +import Api from "@/api"
32 +import { NEmpty, NSpin, useMessage } from "naive-ui"
33 import { nanoid } from "nanoid"
34 +import { onBeforeMount, ref, toRefs } from "vue"
35 +import AgentFlowItem from "./AgentFlowItem.vue"
36
37 interface FlowResultExt extends FlowResult {
38 id?: string
frontend/src/components/agents/agentFlow/AgentFlowQueryStat.vue
+21 -13
@@ -25,17 +25,21 @@
25 {{ artifact }}
26 </span>
27 </div>
28 - <div class="error-message" v-if="stat.error_message">
28 + <div v-if="stat.error_message" class="error-message">
29 {{ stat.error_message }}
30 </div>
31 <div class="badges-box flex flex-wrap items-center gap-3 mt-1">
32 <Badge type="splitted" color="primary">
33 <template #label>Status</template>
34 - <template #value>{{ stat.status || "-" }}</template>
34 + <template #value>
35 + {{ stat.status || "-" }}
36 + </template>
37 </Badge>
38 <Badge type="splitted" color="primary">
39 <template #label>Duration</template>
38 - <template #value>{{ duration }}</template>
40 + <template #value>
41 + {{ duration }}
42 + </template>
43 </Badge>
44 </div>
45 </div>
@@ -55,16 +59,20 @@
59 preset="card"
60 content-class="!p-0"
61 :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
58 - :title="`Agent Query Stat ${stat.Artifact ? ': ' + stat.Artifact : ''}`"
62 + :title="`Agent Query Stat ${stat.Artifact ? `: ${stat.Artifact}` : ''}`"
63 :bordered="false"
64 segmented
65 >
66 <n-tabs type="line" animated :tabs-padding="24">
67 <n-tab-pane name="Info" tab="Info" display-directive="show">
64 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4" v-if="properties">
68 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
69 <KVCard v-for="(value, key) of properties" :key="key">
66 - <template #key>{{ key }}</template>
67 - <template #value>{{ value === "" ? "-" : value ?? "-" }}</template>
70 + <template #key>
71 + {{ key }}
72 + </template>
73 + <template #value>
74 + {{ value === "" ? "-" : value ?? "-" }}
75 + </template>
76 </KVCard>
77 </div>
78 </n-tab-pane>
@@ -89,16 +97,16 @@
97 </template>
98
99 <script setup lang="ts">
92 -import { NModal, NTabs, NTabPane, NInput, NButton } from "naive-ui"
93 -import { useSettingsStore } from "@/stores/settings"
94 -import dayjs from "@/utils/dayjs"
95 -import { formatDate } from "@/utils"
100 import type { FlowQueryStat } from "@/types/flow.d"
101 +import Badge from "@/components/common/Badge.vue"
102 import Icon from "@/components/common/Icon.vue"
103 import KVCard from "@/components/common/KVCard.vue"
99 -import { computed, ref } from "vue"
104 +import { useSettingsStore } from "@/stores/settings"
105 +import { formatDate } from "@/utils"
106 +import dayjs from "@/utils/dayjs"
107 import _pick from "lodash/pick"
101 -import Badge from "@/components/common/Badge.vue"
108 +import { NButton, NInput, NModal, NTabPane, NTabs } from "naive-ui"
109 +import { computed, ref } from "vue"
110
111 const { stat, embedded } = defineProps<{ stat: FlowQueryStat; embedded?: boolean }>()
112
frontend/src/components/agents/agentFlow/AgentFlowTimeline.vue
+1 -1
@@ -12,10 +12,10 @@
12 </template>
13
14 <script setup lang="ts">
15 +import type { FlowResult } from "@/types/flow.d"
16 import { useSettingsStore } from "@/stores/settings"
17 import { formatDate } from "@/utils"
18 import { NTimeline, NTimelineItem } from "naive-ui"
18 -import type { FlowResult } from "@/types/flow.d"
19
20 const { flow } = defineProps<{ flow: FlowResult }>()
21
frontend/src/components/agents/sca/ScaItem.vue
+12 -10
@@ -8,7 +8,7 @@
8 <n-statistic label="Pass" :value="sca.pass" tabular-nums />
9 <n-statistic label="Fail" :value="sca.fail" tabular-nums />
10 <n-statistic label="Invalid" :value="sca.invalid" tabular-nums />
11 - <n-statistic label="Score" :value="sca.score + '%'" tabular-nums />
11 + <n-statistic label="Score" :value="`${sca.score}%`" tabular-nums />
12 </div>
13 </n-card>
14 </div>
@@ -28,9 +28,11 @@
28 </div>
29 </n-card>
30 </div>
31 - <div class="grid gap-2 grid-auto-fit-200 px-7" v-if="properties">
31 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 px-7">
32 <KVCard v-for="(value, key) of properties" :key="key">
33 - <template #key>{{ key }}</template>
33 + <template #key>
34 + {{ key }}
35 + </template>
36 <template #value>
37 <template v-if="value && key === 'references'">
38 <a
@@ -77,19 +79,19 @@
79 </template>
80
81 <script setup lang="ts">
80 -import { computed, defineAsyncComponent } from "vue"
81 -import { NTabs, NTabPane, NInput, NStatistic, NCard } from "naive-ui"
82 +import type { Agent, AgentSca } from "@/types/agents.d"
83 +import Icon from "@/components/common/Icon.vue"
84 +import KVCard from "@/components/common/KVCard.vue"
85 import { useSettingsStore } from "@/stores/settings"
86 import { formatDate } from "@/utils"
84 -import { type Agent, type AgentSca } from "@/types/agents.d"
85 -import KVCard from "@/components/common/KVCard.vue"
86 -import Icon from "@/components/common/Icon.vue"
87 import _pick from "lodash/pick"
88 -
89 -const ScaResults = defineAsyncComponent(() => import("./ScaResults.vue"))
88 +import { NCard, NInput, NStatistic, NTabPane, NTabs } from "naive-ui"
89 +import { computed, defineAsyncComponent } from "vue"
90
91 const { sca, agent } = defineProps<{ sca: AgentSca; agent: Agent }>()
92
93 +const ScaResults = defineAsyncComponent(() => import("./ScaResults.vue"))
94 +
95 const dFormats = useSettingsStore().dateFormat
96 const LinkIcon = "carbon:launch"
97
frontend/src/components/agents/sca/ScaResultItem.vue
+18 -8
@@ -15,7 +15,9 @@
15 </div>
16 <div class="main-box flex items-center gap-3">
17 <div class="content flex flex-col gap-1 grow">
18 - <div class="title">{{ data.title }}</div>
18 + <div class="title">
19 + {{ data.title }}
20 + </div>
21 <div class="description">$ {{ data.command }}</div>
22 </div>
23 </div>
@@ -28,22 +30,30 @@
30 "
31 class="uppercase"
32 >
31 - <template #label>{{ data.result }}</template>
33 + <template #label>
34 + {{ data.result }}
35 + </template>
36 </Badge>
37
38 <Badge type="splitted" color="primary">
39 <template #label>Compliance</template>
36 - <template #value>{{ data.compliance?.length || "-" }}</template>
40 + <template #value>
41 + {{ data.compliance?.length || "-" }}
42 + </template>
43 </Badge>
44
45 <Badge type="splitted" color="primary">
46 <template #label>Condition</template>
41 - <template #value>{{ data.condition || "-" }}</template>
47 + <template #value>
48 + {{ data.condition || "-" }}
49 + </template>
50 </Badge>
51
52 <Badge type="splitted" color="primary">
53 <template #label>Rules</template>
46 - <template #value>{{ data.rules?.length || "-" }}</template>
54 + <template #value>
55 + {{ data.rules?.length || "-" }}
56 + </template>
57 </Badge>
58 </div>
59 </div>
@@ -63,11 +73,11 @@
73 </template>
74
75 <script setup lang="ts">
66 -import Icon from "@/components/common/Icon.vue"
76 +import type { ScaPolicyResult } from "@/types/agents.d"
77 import Badge from "@/components/common/Badge.vue"
78 +import Icon from "@/components/common/Icon.vue"
79 +import { NButton, NModal } from "naive-ui"
80 import { ref } from "vue"
69 -import { NModal, NButton } from "naive-ui"
70 -import type { ScaPolicyResult } from "@/types/agents.d"
81 import ScaResultItemDetails from "./ScaResultItemDetails.vue"
82
83 const { data, embedded } = defineProps<{
frontend/src/components/agents/sca/ScaResultItemDetails.vue
+15 -11
@@ -30,18 +30,22 @@
30 <div class="px-7">
31 <n-card content-class="bg-secondary-color !p-0" class="overflow-hidden">
32 <div
33 - class="scrollbar-styled overflow-hidden code-bg-transparent"
33 v-shiki="{ lang: 'shell', decode: true }"
34 + class="scrollbar-styled overflow-hidden code-bg-transparent"
35 >
36 <pre v-html="data.command"></pre>
37 </div>
38 </n-card>
39 </div>
40
41 - <div class="grid gap-2 grid-auto-fit-200 px-7" v-if="properties">
41 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 px-7">
42 <KVCard v-for="(value, key) of properties" :key="key">
43 - <template #key>{{ key }}</template>
44 - <template #value>{{ value ?? "-" }}</template>
43 + <template #key>
44 + {{ key }}
45 + </template>
46 + <template #value>
47 + {{ value ?? "-" }}
48 + </template>
49 </KVCard>
50 </div>
51 </n-tab-pane>
@@ -108,11 +112,11 @@
112 <n-tab-pane name="Compliance" tab="Compliance" display-directive="show:lazy">
113 <div class="p-7 pt-4 flex flex-col gap-1">
114 <n-card
115 + v-for="item of data.compliance"
116 + :key="item.key"
117 content-class="bg-secondary-color flex flex-col gap-2"
118 class="overflow-hidden"
119 size="small"
114 - v-for="item of data.compliance"
115 - :key="item.key"
120 >
121 <div>{{ item.key }}</div>
122 <p>{{ item.value }}</p>
@@ -122,11 +126,11 @@
126 <n-tab-pane name="Rules" tab="Rules" display-directive="show:lazy">
127 <div class="p-7 pt-4 flex flex-col gap-1">
128 <n-card
129 + v-for="item of data.rules"
130 + :key="item.type + item.rule"
131 content-class="bg-secondary-color flex flex-col gap-2"
132 class="overflow-hidden"
133 size="small"
128 - v-for="item of data.rules"
129 - :key="item.type + item.rule"
134 >
135 <div>{{ item.type }}</div>
136 <p>{{ item.rule }}</p>
@@ -137,12 +141,12 @@
141 </template>
142
143 <script setup lang="ts">
144 +import type { ScaPolicyResult } from "@/types/agents.d"
145 +import KVCard from "@/components/common/KVCard.vue"
146 import vShiki from "@/directives/v-shiki"
147 import _pick from "lodash/pick"
142 -import KVCard from "@/components/common/KVCard.vue"
148 +import { NCard, NInput, NStatistic, NTabPane, NTabs } from "naive-ui"
149 import { computed } from "vue"
144 -import { NTabs, NTabPane, NStatistic, NInput, NCard } from "naive-ui"
145 -import type { ScaPolicyResult } from "@/types/agents.d"
150
151 const { data } = defineProps<{
152 data: ScaPolicyResult
frontend/src/components/agents/sca/ScaResults.vue
+7 -8
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-5">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -55,8 +55,8 @@
55 <div class="px-3">
56 <div class="text-secondary-color text-sm mb-1">Result:</div>
57 <n-select
58 - size="small"
58 v-model:value="resultFilter"
59 + size="small"
60 :options="resultOptions"
61 clearable
62 placeholder="All"
@@ -71,31 +71,30 @@
71 <ScaResultItem v-for="item of itemsPaginated" :key="item.id" :data="item" embedded />
72 </template>
73 <template v-else>
74 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
74 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
75 </template>
76 </div>
77 <div class="footer flex justify-end">
78 <n-pagination
79 + v-if="itemsPaginated.length > 3"
80 v-model:page="currentPage"
81 :page-size="pageSize"
82 :item-count="itemsFiltered.length"
83 :page-slot="6"
84 :simple="simpleMode"
84 - v-if="itemsPaginated.length > 3"
85 />
86 </div>
87 </n-spin>
88 </template>
89
90 <script setup lang="ts">
91 -import { ref, onBeforeMount, computed } from "vue"
92 -import { useMessage, NSpin, NPagination, NPopover, NButton, NSelect, NEmpty } from "naive-ui"
91 +import type { Agent, AgentSca, ScaPolicyResult } from "@/types/agents.d"
92 import Api from "@/api"
93 import Icon from "@/components/common/Icon.vue"
94 import { useResizeObserver } from "@vueuse/core"
95 +import { NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
96 +import { computed, onBeforeMount, ref, watch } from "vue"
97 import ScaResultItem from "./ScaResultItem.vue"
97 -import type { Agent, AgentSca, ScaPolicyResult } from "@/types/agents.d"
98 -import { watch } from "vue"
98
99 const { sca, agent } = defineProps<{ sca: AgentSca; agent: Agent }>()
100
frontend/src/components/agents/sca/ScaTable.vue
+17 -13
@@ -20,7 +20,9 @@
20 <n-tooltip trigger="hover">
21 <template #trigger>
22 <n-button size="small" @click="showScaDetails(item)">
23 - <template #icon><Icon :name="InfoIcon"></Icon></template>
23 + <template #icon>
24 + <Icon :name="InfoIcon"></Icon>
25 + </template>
26 </n-button>
27 </template>
28 Details
@@ -28,8 +30,10 @@
30
31 <n-tooltip trigger="hover">
32 <template #trigger>
31 - <n-button size="small" @click="scaDownload(item)" :loading="item.downloading">
32 - <template #icon><Icon :name="DownloadIcon"></Icon></template>
33 + <n-button size="small" :loading="item.downloading" @click="scaDownload(item)">
34 + <template #icon>
35 + <Icon :name="DownloadIcon"></Icon>
36 + </template>
37 </n-button>
38 </template>
39 Download CSV
@@ -44,11 +48,11 @@
48 {{ item.extract }}
49
50 <n-popover
51 + v-if="item.description !== item.extract"
52 placement="top-end"
53 content-class="max-w-96"
54 scrollable
55 to="body"
51 - v-if="item.description !== item.extract"
56 >
57 <template #trigger>
58 <span class="cursor-help underline">...</span>
@@ -77,7 +81,7 @@
81 </tbody>
82 </n-table>
83 </n-scrollbar>
80 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading && !scaList.length" />
84 + <n-empty v-if="!loading && !scaList.length" description="No items found" class="justify-center h-48" />
85
86 <n-modal
87 v-model:show="showDetails"
@@ -94,16 +98,16 @@
98 </template>
99
100 <script setup lang="ts">
97 -import { ref, onBeforeMount, toRefs } from "vue"
98 -import { NTooltip, NSpin, NEmpty, NScrollbar, NTable, NButton, NPopover, NModal, useMessage } from "naive-ui"
101 +import type { Agent, AgentSca } from "@/types/agents.d"
102 import Api from "@/api"
100 -import ScaItem from "./ScaItem.vue"
103 import Icon from "@/components/common/Icon.vue"
102 -import { saveAs } from "file-saver"
103 -import _truncate from "lodash/truncate"
104 import { useSettingsStore } from "@/stores/settings"
105 import { formatDate } from "@/utils"
106 -import { type Agent, type AgentSca } from "@/types/agents.d"
106 +import { saveAs } from "file-saver"
107 +import _truncate from "lodash/truncate"
108 +import { NButton, NEmpty, NModal, NPopover, NScrollbar, NSpin, NTable, NTooltip, useMessage } from "naive-ui"
109 +import { onBeforeMount, ref, toRefs } from "vue"
110 +import ScaItem from "./ScaItem.vue"
111
112 interface SCAExt extends AgentSca {
113 end_scan_text?: string
@@ -163,13 +167,13 @@ function showScaDetails(sca: SCAExt) {
167 function scaDownload(sca: SCAExt) {
168 sca.downloading = true
169
166 - const fileName = `sca-${sca.policy_id}_${new Date().getTime()}.csv`
170 + const fileName = `sca_policy:${sca.policy_id}_${formatDate(new Date(), dFormats.datetimesec)}.csv`
171
172 Api.agents
173 .scaResultsDownload(agent.value.agent_id, sca.policy_id)
174 .then(res => {
175 if (res.data) {
172 - saveAs(new Blob([res.data], { type: "text/csv;charset=utf-8" }), fileName)
176 + saveAs(res.data, fileName)
177 } else {
178 message.warning("An error occurred. Please try again later.")
179 }
frontend/src/components/agents/utils.ts
+4 -4
@@ -1,8 +1,8 @@
1 -import dayjs from "@/utils/dayjs"
2 -import Api from "@/api"
3 -import { type Agent } from "@/types/agents.d"
4 -import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
1 +import type { Agent } from "@/types/agents.d"
2 import type { DialogApiInjection } from "naive-ui/es/dialog/src/DialogProvider"
3 +import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
4 +import Api from "@/api"
5 +import dayjs from "@/utils/dayjs"
6 import { h } from "vue"
7
8 export function isAgentOnline(lastSeen: string) {
frontend/src/components/agents/vulnerabilities/VulnerabilitiesGrid.vue
+15 -9
@@ -16,27 +16,29 @@
16 </div>
17 <n-spin content-class="min-h-48" :show="loading">
18 <div class="group gap-4 grid grid-auto-fill-200">
19 - <VulnerabilityCard :vulnerability="item" v-for="item of vulnerabilities" :key="item.id" hide-tooltip />
19 + <VulnerabilityCard v-for="item of vulnerabilities" :key="item.id" :vulnerability="item" hide-tooltip />
20 </div>
21 <n-empty
22 + v-if="!loading && !vulnerabilities.length"
23 description="No vulnerabilities detected"
24 class="justify-center h-48"
24 - v-if="!loading && !vulnerabilities.length"
25 />
26 </n-spin>
27 </div>
28 </template>
29
30 <script setup lang="ts">
31 -import { ref, onBeforeMount, toRefs, watch, computed } from "vue"
32 -import { NButton, NSpin, NEmpty, NFormItem, NSelect, useMessage } from "naive-ui"
31 +import type { VulnerabilitySeverityType } from "@/api/endpoints/agents"
32 +import type { Agent, AgentVulnerabilities } from "@/types/agents.d"
33 import Api from "@/api"
34 -import VulnerabilityCard from "./VulnerabilityCard.vue"
35 -import { nanoid } from "nanoid"
34 +import { useSettingsStore } from "@/stores/settings"
35 +import { formatDate } from "@/utils"
36 import axios from "axios"
37 import { saveAs } from "file-saver"
38 -import type { VulnerabilitySeverityType } from "@/api/endpoints/agents"
39 -import { type Agent, type AgentVulnerabilities } from "@/types/agents.d"
38 +import { NButton, NEmpty, NFormItem, NSelect, NSpin, useMessage } from "naive-ui"
39 +import { nanoid } from "nanoid"
40 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
41 +import VulnerabilityCard from "./VulnerabilityCard.vue"
42
43 const props = defineProps<{
44 agent: Agent
@@ -46,6 +48,7 @@ const { agent } = toRefs(props)
48 let abortController: AbortController | null = null
49 const message = useMessage()
50 const loading = ref(false)
51 +const dFormats = useSettingsStore().dateFormat
52 const downloading = ref(false)
53 const severity = ref<VulnerabilitySeverityType>("Critical")
54 const vulnerabilitiesCache = ref<{ [key in VulnerabilitySeverityType | string]: AgentVulnerabilities[] }>({})
@@ -99,7 +102,10 @@ function getVulnerabilities(id: string) {
102 function vulnerabilitiesDownload(id: string) {
103 downloading.value = true
104
102 - const fileName = `agent-${id}_${severity.value.toLowerCase()}-vulnerabilities.csv`
105 + const fileName = `vulnerabilities_agent:${id}_severity:${severity.value.toLowerCase()}_${formatDate(
106 + new Date(),
107 + dFormats.datetimesec
108 + )}.csv`
109
110 Api.agents
111 .agentVulnerabilitiesDownload(id, severity.value)
frontend/src/components/agents/vulnerabilities/VulnerabilityCard.vue
+26 -18
@@ -12,7 +12,7 @@
12 <Icon :name="ClockIcon"></Icon>
13 <span>{{ detectionTime }}</span>
14 </div>
15 - <n-tooltip placement="top-start" v-else>
15 + <n-tooltip v-else placement="top-start">
16 <span>Detection time</span>
17 <template #trigger>
18 <div>
@@ -27,7 +27,7 @@
27 <Icon :name="CounterIcon"></Icon>
28 <span>{{ vulnerability.cve }}</span>
29 </div>
30 - <n-tooltip placement="top-start" v-else>
30 + <n-tooltip v-else placement="top-start">
31 <span>{{ `CVSS2: ${vulnerability.cvss2_score} - CVSS3: ${vulnerability.cvss3_score}` }}</span>
32 <template #trigger>
33 <div>
@@ -42,7 +42,7 @@
42 <Icon :name="VulnerabilityIcon"></Icon>
43 <span>{{ vulnerability.name }}</span>
44 </div>
45 - <n-tooltip placement="top-start" v-else>
45 + <n-tooltip v-else placement="top-start">
46 <span>{{ `Version: ${vulnerability.version}` }}</span>
47 <template #trigger>
48 <div>
@@ -55,10 +55,10 @@
55 </div>
56
57 <n-modal
58 + v-model:show="showDialog"
59 preset="card"
60 class="vulnerability-dialog"
61 :title="extract"
61 - v-model:show="showDialog"
62 content-class="!p-0"
63 style="width: 90vw; max-width: 1000px"
64 >
@@ -67,21 +67,27 @@
67 <div class="p-7 pt-4 pb-0">
68 <KVCard>
69 <template #key>title</template>
70 - <template #value>{{ vulnerability.title ?? "-" }}</template>
70 + <template #value>
71 + {{ vulnerability.title ?? "-" }}
72 + </template>
73 </KVCard>
74 </div>
73 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-2" v-if="vulnerabilitySanitized">
75 + <div v-if="vulnerabilitySanitized" class="grid gap-2 grid-auto-fit-200 p-7 pt-2">
76 <KVCard v-for="item of vulnerabilitySanitized" :key="item.label">
75 - <template #key>{{ item.label }}</template>
76 - <template #value>{{ item.value ?? "-" }}</template>
77 + <template #key>
78 + {{ item.label }}
79 + </template>
80 + <template #value>
81 + {{ item.value ?? "-" }}
82 + </template>
83 </KVCard>
84 </div>
85 </n-tab-pane>
86 <n-tab-pane name="External references" tab="External references" display-directive="show">
87 <div class="p-7 pt-2">
88 <ul>
83 - <li v-for="ref of vulnerability.external_references" :key="ref">
84 - <a :href="ref" target="_blank">{{ ref }}</a>
89 + <li v-for="refItem of vulnerability.external_references" :key="refItem">
90 + <a :href="refItem" target="_blank">{{ refItem }}</a>
91 </li>
92 </ul>
93 </div>
@@ -97,22 +103,23 @@
103 </template>
104
105 <script setup lang="ts">
100 -import { computed, defineAsyncComponent, ref, toRefs } from "vue"
101 -import { type AgentVulnerabilities } from "@/types/agents.d"
102 -import dayjs from "@/utils/dayjs"
103 -import { cloneDeep } from "lodash"
104 -import { NModal, NTooltip, NTabs, NTabPane } from "naive-ui"
106 +import type { AgentVulnerabilities } from "@/types/agents.d"
107 import Icon from "@/components/common/Icon.vue"
108 import KVCard from "@/components/common/KVCard.vue"
109 import { useSettingsStore } from "@/stores/settings"
110 +import dayjs from "@/utils/dayjs"
111 +import { cloneDeep } from "lodash"
112 import _truncate from "lodash/truncate"
109 -
110 -const ThreatIntelEpssScore = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelEpssScore.vue"))
113 +import { NModal, NTabPane, NTabs, NTooltip } from "naive-ui"
114 +import { computed, defineAsyncComponent, ref, toRefs } from "vue"
115
116 const props = defineProps<{
117 vulnerability: AgentVulnerabilities
118 hideTooltip?: boolean
119 }>()
120 +
121 +const ThreatIntelEpssScore = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelEpssScore.vue"))
122 +
123 const { vulnerability, hideTooltip } = toRefs(props)
124
125 const ClockIcon = "carbon:time"
@@ -136,12 +143,13 @@ const vulnerabilitySanitized = computed(() => {
143 }
144 }
145
139 - if (!["external_references", "id", "title"].includes(k))
146 + if (!["external_references", "id", "title"].includes(k)) {
147 newObj.push({
148 label: k,
149 // @ts-expect-error k is ok
150 value: obj[k]
151 })
152 + }
153 }
154 return newObj
155 })
frontend/src/components/alerts/Alert.vue
+43 -27
@@ -11,16 +11,22 @@
11 </div>
12 <div class="main-box flex justify-between gap-4">
13 <div class="content">
14 - <div class="rule-description">{{ alert._source.rule_description || alert._source.type }}</div>
15 - <div class="rule-groups">{{ alert._source.rule_groups }}</div>
14 + <div class="rule-description">
15 + {{ alert._source.rule_description || alert._source.type }}
16 + </div>
17 + <div class="rule-groups">
18 + {{ alert._source.rule_groups }}
19 + </div>
20
17 - <div class="badges-box flex flex-wrap items-center gap-3" v-if="alert._id">
21 + <div v-if="alert._id" class="badges-box flex flex-wrap items-center gap-3">
22 <Badge type="splitted" color="primary">
23 <template #iconLeft>
24 <Icon :name="TargetIcon" :size="13" class="!opacity-80"></Icon>
25 </template>
26 <template #label>Fired times</template>
23 - <template #value>{{ alert._source.rule_firedtimes }}</template>
27 + <template #value>
28 + {{ alert._source.rule_firedtimes }}
29 + </template>
30 </Badge>
31
32 <Badge :type="alert._source.rule_mail ? 'active' : 'muted'">
@@ -79,15 +85,21 @@
85 </Badge>
86 <Badge type="splitted" color="primary" class="hide-on-small">
87 <template #label>manager</template>
82 - <template #value>{{ alert._source.manager_name }}</template>
88 + <template #value>
89 + {{ alert._source.manager_name }}
90 + </template>
91 </Badge>
92 <Badge type="splitted" color="primary" class="hide-on-small">
93 <template #label>decoder</template>
86 - <template #value>{{ alert._source.decoder_name }}</template>
94 + <template #value>
95 + {{ alert._source.decoder_name }}
96 + </template>
97 </Badge>
98 <Badge type="splitted" color="primary" class="hide-on-small">
99 <template #label>source</template>
90 - <template #value>{{ alert._source.source }}</template>
100 + <template #value>
101 + {{ alert._source.source }}
102 + </template>
103 </Badge>
104 </div>
105 </div>
@@ -95,7 +107,7 @@
107 v-if="!hideActions"
108 class="actions-box"
109 :alert
98 - :socAlertField="socAlertCreationField"
110 + :soc-alert-field="socAlertCreationField"
111 @start-loading="loading = true"
112 @stop-loading="loading = false"
113 @updated-url="alert._source.alert_url = $event"
@@ -108,15 +120,17 @@
120 v-if="!hideActions"
121 class="actions-box"
122 :alert
111 - :socAlertField="socAlertCreationField"
112 - :size="'small'"
123 + :soc-alert-field="socAlertCreationField"
124 + size="small"
125 @start-loading="loading = true"
126 @stop-loading="loading = false"
127 @updated-url="alert._source.alert_url = $event"
128 @updated-id="alert._source.alert_id = $event"
129 @updated-ask-message="alert._source.ask_socfortress_message = $event"
130 />
119 - <div class="time">{{ formatDate(alert._source.timestamp_utc, dFormats.datetimesec) }}</div>
131 + <div class="time">
132 + {{ formatDate(alert._source.timestamp_utc, dFormats.datetimesec) }}
133 + </div>
134 </div>
135
136 <n-modal
@@ -129,13 +143,15 @@
143 segmented
144 >
145 <n-tabs type="line" animated :tabs-padding="24">
132 - <n-tab-pane name="Agent" tab="Agent" display-directive="show" v-if="alert._id">
133 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4" v-if="agentProperties">
146 + <n-tab-pane v-if="alert._id" name="Agent" tab="Agent" display-directive="show">
147 + <div v-if="agentProperties" class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
148 <KVCard v-for="(value, key) of agentProperties" :key="key">
135 - <template #key>{{ key }}</template>
149 + <template #key>
150 + {{ key }}
151 + </template>
152 <template #value>
153 <template v-if="key === 'agent_id'">
138 - <code class="cursor-pointer text-primary-color" @click="gotoAgent(value + '')">
154 + <code class="cursor-pointer text-primary-color" @click="gotoAgent(`${value}`)">
155 {{ value }}
156 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
157 </code>
@@ -157,9 +173,9 @@
173 </div>
174 </n-tab-pane>
175 <n-tab-pane
176 + v-if="alert._source.ask_socfortress_message"
177 name="SOCFortress Response"
178 tab="SOCFortress Response"
162 - v-if="alert._source.ask_socfortress_message"
179 display-directive="show"
180 >
181 <div class="p-7 pt-4">
@@ -175,15 +191,15 @@
191 />
192 </div>
193 </n-tab-pane>
178 - <n-tab-pane name="Message" tab="Message" v-if="alert._source.message" display-directive="show">
194 + <n-tab-pane v-if="alert._source.message" name="Message" tab="Message" display-directive="show">
195 <div class="p-7 pt-4">
196 <CodeSource :code="alert._source.message" :decode="false" />
197 </div>
198 </n-tab-pane>
199 <n-tab-pane
200 + v-if="alert._source.data_document"
201 name="Data document"
202 tab="Data document"
186 - v-if="alert._source.data_document"
203 display-directive="show"
204 >
205 <div class="p-7 pt-4">
@@ -210,22 +226,22 @@
226 </template>
227
228 <script setup lang="ts">
213 -import { computed, defineAsyncComponent, inject, ref, toRefs } from "vue"
214 -import { NPopover, NModal, NTabs, NTabPane, NInput } from "naive-ui"
215 -import { useSettingsStore } from "@/stores/settings"
216 -import { formatDate } from "@/utils"
217 -import Icon from "@/components/common/Icon.vue"
229 +import type { Alert } from "@/types/alerts.d"
230 +import type { SocAlertField } from "./type.d"
231 import Badge from "@/components/common/Badge.vue"
219 -import _pick from "lodash/pick"
232 +import Icon from "@/components/common/Icon.vue"
233 import KVCard from "@/components/common/KVCard.vue"
234 import { useGoto } from "@/composables/useGoto"
222 -import type { Alert } from "@/types/alerts.d"
223 -import type { SocAlertField } from "./type.d"
235 +import { useSettingsStore } from "@/stores/settings"
236 +import { formatDate } from "@/utils"
237 +import _pick from "lodash/pick"
238 +import { NInput, NModal, NPopover, NTabPane, NTabs } from "naive-ui"
239 +import { computed, defineAsyncComponent, inject, ref, toRefs } from "vue"
240
241 +const props = defineProps<{ alert: Alert; hideActions?: boolean }>()
242 const AlertActions = defineAsyncComponent(() => import("./AlertActions.vue"))
243 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
244
228 -const props = defineProps<{ alert: Alert; hideActions?: boolean }>()
245 const { alert, hideActions } = toRefs(props)
246
247 const InfoIcon = "carbon:information"
frontend/src/components/alerts/AlertActions.vue
+32 -22
@@ -1,43 +1,53 @@
1 <template>
2 <div class="alert-actions flex flex-col gap-2 justify-end">
3 - <n-button type="success" secondary :size="size" v-if="socAlertFieldValue" @click="gotoSocAlertUrl()">
4 - <template #icon><Icon :name="ViewIcon"></Icon></template>
3 + <n-button v-if="socAlertFieldValue" type="success" secondary :size="size" @click="gotoSocAlertUrl()">
4 + <template #icon>
5 + <Icon :name="ViewIcon"></Icon>
6 + </template>
7 View SOC Alert
8 </n-button>
9 <n-button
10 + v-if="!socAlertFieldValue"
11 :loading="loadingSocAlert"
12 type="warning"
13 secondary
14 :size="size"
15 @click="createAlert()"
13 - v-if="!socAlertFieldValue"
16 >
15 - <template #icon><Icon :name="DangerIcon"></Icon></template>
17 + <template #icon>
18 + <Icon :name="DangerIcon"></Icon>
19 + </template>
20 Create SOC Alert
21 </n-button>
18 - <n-button type="success" secondary :size="size" v-if="alertAskMessage" @click="showSocResponse = true">
19 - <template #icon><Icon :name="ViewIcon"></Icon></template>
22 + <n-button v-if="alertAskMessage" type="success" secondary :size="size" @click="showSocResponse = true">
23 + <template #icon>
24 + <Icon :name="ViewIcon"></Icon>
25 + </template>
26 View SOCFortress Response
27 </n-button>
28 <n-button
29 + v-if="isAskVisible"
30 :loading="loadingAskSoc"
31 type="warning"
32 secondary
33 :size="size"
34 @click="askSOCFortress()"
28 - v-if="isAskVisible"
35 >
30 - <template #icon><Icon :name="AskIcon"></Icon></template>
36 + <template #icon>
37 + <Icon :name="AskIcon"></Icon>
38 + </template>
39 Ask SOCFortress
40 </n-button>
41 <n-button
42 + v-if="isWazuhRulesVisible"
43 :loading="loadingWazuhRuleExclude"
44 secondary
45 :size="size"
46 @click="wazuhManagerRuleExclude()"
38 - v-if="isWazuhRulesVisible"
47 >
40 - <template #icon><Icon :name="RulesIcon"></Icon></template>
48 + <template #icon>
49 + <Icon :name="RulesIcon"></Icon>
50 + </template>
51 Exclude Rule in Wazuh
52 </n-button>
53
@@ -70,20 +80,26 @@
80 content-class="!p-0"
81 segmented
82 >
73 - <AlertWazuhRules :data="wazuhRuleData" v-if="wazuhRuleData" />
83 + <AlertWazuhRules v-if="wazuhRuleData" :data="wazuhRuleData" />
84 </n-modal>
85 </div>
86 </template>
87
88 <script setup lang="ts">
79 -import { computed, onBeforeMount, ref, watch } from "vue"
80 -import { NButton, NInput, NModal, useMessage } from "naive-ui"
81 -import AlertWazuhRules from "./AlertWazuhRules.vue"
82 -import Api from "@/api"
83 -import Icon from "@/components/common/Icon.vue"
89 import type { Alert, WazuhRuleExclude } from "@/types/alerts.d"
90 import type { SocAlertField } from "./type.d"
91 +import Api from "@/api"
92 +import Icon from "@/components/common/Icon.vue"
93 +import { NButton, NInput, NModal, useMessage } from "naive-ui"
94 +import { computed, onBeforeMount, ref, watch } from "vue"
95 import { useRouter } from "vue-router"
96 +import AlertWazuhRules from "./AlertWazuhRules.vue"
97 +
98 +const { alert, size, socAlertField } = defineProps<{
99 + alert: Alert
100 + size?: "tiny" | "small" | "medium" | "large"
101 + socAlertField: SocAlertField
102 +}>()
103
104 const emit = defineEmits<{
105 (e: "startLoading"): void
@@ -93,12 +109,6 @@ const emit = defineEmits<{
109 (e: "updatedAskMessage", value: string): void
110 }>()
111
96 -const { alert, size, socAlertField } = defineProps<{
97 - alert: Alert
98 - size?: "tiny" | "small" | "medium" | "large"
99 - socAlertField: SocAlertField
100 -}>()
101 -
112 const DangerIcon = "majesticons:exclamation-line"
113 const AskIcon = "majesticons:question-mark-circle-line"
114 const ViewIcon = "iconoir:eye-alt"
frontend/src/components/alerts/AlertWazuhRules.vue
+5 -4
@@ -23,16 +23,17 @@
23 </template>
24
25 <script setup lang="ts">
26 -import { computed, defineAsyncComponent, toRefs } from "vue"
27 -import { NTabs, NTabPane, NInput } from "naive-ui"
26 import type { WazuhRuleExclude } from "@/types/alerts.d"
27 +import { NInput, NTabPane, NTabs } from "naive-ui"
28 +import { computed, defineAsyncComponent, toRefs } from "vue"
29 +
30 +const props = defineProps<{ data: WazuhRuleExclude }>()
31
32 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
33
32 -const props = defineProps<{ data: WazuhRuleExclude }>()
34 const { data } = toRefs(props)
35
35 -const wazuh_rule = computed(() => data.value.wazuh_rule.replace(/\\\\/gim, "\\\\\\\\"))
36 +const wazuh_rule = computed(() => data.value.wazuh_rule.replace(/\\\\/g, "\\\\\\\\"))
37 </script>
38
39 <style lang="scss" scoped>
frontend/src/components/alerts/AlertsFilters.vue
+7 -7
@@ -15,7 +15,7 @@
15 class="basis-1/2"
16 >
17 <template #action>
18 - <n-button @click="clearFieldsHistory()" size="tiny" quaternary class="!w-full">
18 + <n-button size="tiny" quaternary class="!w-full" @click="clearFieldsHistory()">
19 <template #icon>
20 <Icon :name="ClearIcon"></Icon>
21 </template>
@@ -56,20 +56,20 @@
56 </template>
57
58 <script setup lang="ts">
59 -import { onBeforeMount, toRefs, watch, type VNodeChild, h } from "vue"
60 -import { NSelect, NButton, NInput, NInputGroup, NEmpty, NFormItem, type SelectOption } from "naive-ui"
59 +import type { AlertsQueryTimeRange, AlertsSummaryQuery } from "@/api/endpoints/alerts"
60 import Icon from "@/components/common/Icon.vue"
61 import { useStorage } from "@vueuse/core"
62 import _uniqBy from "lodash/uniqBy"
64 -import type { AlertsQueryTimeRange, AlertsSummaryQuery } from "@/api/endpoints/alerts"
63 +import { NButton, NEmpty, NFormItem, NInput, NInputGroup, NSelect, type SelectOption } from "naive-ui"
64 +import { h, onBeforeMount, toRefs, type VNodeChild, watch } from "vue"
65
66 const props = defineProps<{ filters: AlertsSummaryQuery }>()
67 -const { filters } = toRefs(props)
68 -
67 const emit = defineEmits<{
68 (e: "search"): void
69 }>()
70
71 +const { filters } = toRefs(props)
72 +
73 const ClearIcon = "mdi:broom"
74 const SearchIcon = "carbon:search"
75
@@ -108,7 +108,7 @@ function renderFieldTag({ option }: { option: SelectOption; handleClose: () => v
108 }
109
110 function renderFieldLabel(option: SelectOption): VNodeChild {
111 - if (option.type === "group") return option.label + "(Cool!)"
111 + if (option.type === "group") return `${option.label}(Cool!)`
112 return [
113 h(Icon, {
114 style: {
frontend/src/components/alerts/AlertsGraylogFilters.vue
+5 -5
@@ -21,19 +21,19 @@
21 </template>
22
23 <script setup lang="ts">
24 -import { onBeforeMount, toRefs } from "vue"
25 -import { NSelect, NButton, NFormItem } from "naive-ui"
24 +import type { AlertsQueryTimeRange, GraylogAlertsQuery } from "@/api/endpoints/alerts"
25 import Icon from "@/components/common/Icon.vue"
26 import { useStorage } from "@vueuse/core"
28 -import type { AlertsQueryTimeRange, GraylogAlertsQuery } from "@/api/endpoints/alerts"
27 +import { NButton, NFormItem, NSelect } from "naive-ui"
28 +import { onBeforeMount, toRefs } from "vue"
29
30 const props = defineProps<{ filters: Partial<GraylogAlertsQuery> }>()
31 -const { filters } = toRefs(props)
32 -
31 const emit = defineEmits<{
32 (e: "search"): void
33 }>()
34
35 +const { filters } = toRefs(props)
36 +
37 const SearchIcon = "carbon:search"
38
39 const timerangeOptions: { label: string; value: AlertsQueryTimeRange }[] = [
frontend/src/components/alerts/AlertsGraylogList.vue
+11 -10
@@ -42,12 +42,12 @@
42 <AlertsSummaryItem
43 v-for="alertsSummary of alertsSummaryList"
44 :key="alertsSummary.index_name"
45 - :alertsSummary="alertsSummary"
45 + :alerts-summary="alertsSummary"
46 class="item-appear item-appear-bottom item-appear-005"
47 />
48 </template>
49 <template v-else>
50 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
50 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
51 </template>
52 </div>
53 </n-spin>
@@ -72,17 +72,18 @@
72 // import { alerts_summary } from "./mock"
73 // import type { AlertsSummary } from "@/types/alerts.d"
74
75 -import { ref, onBeforeMount, computed, nextTick, onMounted, onBeforeUnmount, defineAsyncComponent, provide } from "vue"
76 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NDrawer, NDrawerContent } from "naive-ui"
77 -import Api from "@/api"
78 -const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
79 -import AlertsGraylogFilters from "./AlertsGraylogFilters.vue"
80 -import AlertsSummaryItem, { type AlertsSummaryExt } from "./AlertsSummary.vue"
81 -import Icon from "@/components/common/Icon.vue"
75 import type { GraylogAlertsQuery } from "@/api/endpoints/alerts"
76 import type { IndexStats } from "@/types/indices.d"
84 -import axios from "axios"
77 import type { SocAlertField } from "./type.d"
78 +import Api from "@/api"
79 +import Icon from "@/components/common/Icon.vue"
80 +import axios from "axios"
81 +import { NButton, NDrawer, NDrawerContent, NEmpty, NPopover, NSpin, useMessage } from "naive-ui"
82 +import { computed, defineAsyncComponent, nextTick, onBeforeMount, onBeforeUnmount, onMounted, provide, ref } from "vue"
83 +import AlertsGraylogFilters from "./AlertsGraylogFilters.vue"
84 +import AlertsSummaryItem, { type AlertsSummaryExt } from "./AlertsSummary.vue"
85 +
86 +const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
87
88 const message = useMessage()
89 const loading = ref(false)
frontend/src/components/alerts/AlertsList.vue
+20 -18
@@ -25,7 +25,7 @@
25 </n-popover>
26 </div>
27 <div class="actions flex gap-2 items-center">
28 - <n-button size="small" @click="showStatsDrawer = true" v-if="!isFilterPreselected">
28 + <n-button v-if="!isFilterPreselected" size="small" @click="showStatsDrawer = true">
29 <template #icon>
30 <Icon :name="StatsIcon" :size="14"></Icon>
31 </template>
@@ -48,12 +48,12 @@
48 <AlertsSummaryItem
49 v-for="alertsSummary of alertsSummaryList"
50 :key="alertsSummary.index_name"
51 - :alertsSummary="alertsSummary"
51 + :alerts-summary="alertsSummary"
52 class="item-appear item-appear-bottom item-appear-005"
53 />
54 </template>
55 <template v-else>
56 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
56 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
57 </template>
58 </div>
59 </n-spin>
@@ -83,12 +83,6 @@
83 <div v-if="!isFilterPreselected" class="mb-6">
84 <n-select
85 v-model:value="filterType"
86 - @update:value="
87 - () => {
88 - filters.agentHostname = undefined
89 - filters.indexName = undefined
90 - }
91 - "
86 :options="[
87 {
88 label: 'Filter by Agent Hostname',
@@ -101,6 +95,12 @@
95 ]"
96 placeholder="Filter by Agent or Index"
97 clearable
98 + @update:value="
99 + () => {
100 + filters.agentHostname = undefined
101 + filters.indexName = undefined
102 + }
103 + "
104 />
105 <n-select
106 v-if="filterType === 'agentHostname'"
@@ -134,20 +134,22 @@
134 // import { alerts_summary } from "./mock"
135 // import type { AlertsSummary } from "@/types/alerts.d"
136
137 -import { ref, onBeforeMount, toRefs, computed, nextTick, onMounted, onBeforeUnmount, defineAsyncComponent } from "vue"
138 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NDrawer, NDrawerContent, NSelect } from "naive-ui"
139 -import Api from "@/api"
140 -const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
141 -import AlertsStats, { type AlertsStatsCTX } from "./AlertsStats.vue"
142 -import AlertsFilters from "./AlertsFilters.vue"
143 -import AlertsSummaryItem, { type AlertsSummaryExt } from "./AlertsSummary.vue"
144 -import Icon from "@/components/common/Icon.vue"
137 import type { AlertsSummaryQuery } from "@/api/endpoints/alerts"
138 +import type { Agent } from "@/types/agents.d"
139 import type { IndexStats } from "@/types/indices.d"
140 +import Api from "@/api"
141 +import Icon from "@/components/common/Icon.vue"
142 import axios from "axios"
148 -import type { Agent } from "@/types/agents.d"
143 +import { NButton, NDrawer, NDrawerContent, NEmpty, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
144 +import { computed, defineAsyncComponent, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, toRefs } from "vue"
145 +import AlertsFilters from "./AlertsFilters.vue"
146 +import AlertsStats, { type AlertsStatsCTX } from "./AlertsStats.vue"
147 +import AlertsSummaryItem, { type AlertsSummaryExt } from "./AlertsSummary.vue"
148
149 const props = defineProps<{ agentHostname?: string; indexName?: string }>()
150 +
151 +const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
152 +
153 const { agentHostname, indexName } = toRefs(props)
154
155 const message = useMessage()
frontend/src/components/alerts/AlertsStats.vue
+12 -13
@@ -16,9 +16,9 @@
16 </template>
17 <template v-else>
18 <n-empty
19 + v-if="!loadingCountByHost"
20 description="No items found"
21 class="justify-center h-48"
21 - v-if="!loadingCountByHost"
22 />
23 </template>
24 </div>
@@ -39,9 +39,9 @@
39 </template>
40 <template v-else>
41 <n-empty
42 + v-if="!loadingCountByRule"
43 description="No items found"
44 class="justify-center h-48"
44 - v-if="!loadingCountByRule"
45 />
46 </template>
47 </div>
@@ -62,9 +62,9 @@
62 </template>
63 <template v-else>
64 <n-empty
65 + v-if="!loadingCountByRuleHost"
66 description="No items found"
67 class="justify-center h-48"
67 - v-if="!loadingCountByRuleHost"
68 />
69 </template>
70 </div>
@@ -75,29 +75,28 @@
75 </template>
76
77 <script setup lang="ts">
78 -import { ref, onBeforeMount, toRefs, onBeforeUnmount } from "vue"
79 -import { useMessage, NSpin, NEmpty, NTabs, NTabPane } from "naive-ui"
80 -import Api from "@/api"
81 -import AlertsStatsItem from "./AlertsStatsItem.vue"
82 -import type { AlertsByHost, AlertsByRule, AlertsByRulePerHost } from "@/types/alerts.d"
78 import type { AlertsSummaryQuery } from "@/api/endpoints/alerts"
79 +import type { AlertsByHost, AlertsByRule, AlertsByRulePerHost } from "@/types/alerts.d"
80 +import Api from "@/api"
81 import axios from "axios"
85 -import { onMounted } from "vue"
82 +import { NEmpty, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
83 +import { onBeforeMount, onBeforeUnmount, onMounted, ref, toRefs } from "vue"
84 +import AlertsStatsItem from "./AlertsStatsItem.vue"
85 // import { alerts_by_host, alerts_by_rule, alerts_by_rule_per_host } from "./mock"
86
87 const props = withDefaults(defineProps<{ filters?: AlertsSummaryQuery }>(), {
88 filters: () => ({})
89 })
90 +const emit = defineEmits<{
91 + (e: "mounted", value: AlertsStatsCTX): void
92 +}>()
93 +
94 const { filters } = toRefs(props)
95
96 export interface AlertsStatsCTX {
97 startSearch: () => void
98 }
99
97 -const emit = defineEmits<{
98 - (e: "mounted", value: AlertsStatsCTX): void
99 -}>()
100 -
100 const message = useMessage()
101 const countByHost = ref<AlertsByHost[]>([])
102 const countByRule = ref<AlertsByRule[]>([])
frontend/src/components/alerts/AlertsStatsItem.vue
+4 -2
@@ -2,13 +2,15 @@
2 <div class="alerts-stats-item flex items-stretch">
3 <div class="info grow flex flex-col gap-1 justify-center">
4 <div
5 - class="agent"
5 v-if="summary.agent_name"
6 + class="agent"
7 :class="{ 'text-secondary-color': summary.agent_name && summary.rule }"
8 >
9 {{ summary.agent_name }}
10 </div>
11 - <div class="rule" v-if="summary.rule">{{ summary.rule }}</div>
11 + <div v-if="summary.rule" class="rule">
12 + {{ summary.rule }}
13 + </div>
14 </div>
15 <div class="count font-mono flex items-center justify-center">
16 <span v-if="summary.number_of_alerts">
frontend/src/components/alerts/AlertsSummary.vue
+10 -10
@@ -2,8 +2,8 @@
2 <div class="alert-summary flex flex-col">
3 <div class="header-box flex justify-between gap-4">
4 <div class="id flex items-center gap-2" @click="gotoIndex(alertsSummary.index_name)">
5 - <IndexIcon :health="alertsSummary.indexStats?.health" color v-if="alertsSummary.indexStats?.health" />
6 - <Icon :name="PlaceholderIcon" v-else :size="18" />
5 + <IndexIcon v-if="alertsSummary.indexStats?.health" :health="alertsSummary.indexStats?.health" color />
6 + <Icon v-else :name="PlaceholderIcon" :size="18" />
7
8 {{ alertsSummary.index_name }}
9
@@ -12,9 +12,9 @@
12 <div class="total-alerts flex items-center flex-wrap justify-end">
13 <n-button
14 v-if="alertsSummary.alerts.length > 3 && showAllAlerts"
15 - @click="showAllAlerts = false"
15 class="show-less"
16 size="tiny"
17 + @click="showAllAlerts = false"
18 >
19 Show less
20 </n-button>
@@ -30,7 +30,7 @@
30 <Alert v-for="alert of alertsSummary.alerts" :key="alert._id" :alert="alert" />
31 </n-scrollbar>
32
33 - <div class="load-more" v-if="alertsSummary.alerts.length > 3" @click="showAllAlerts = true">
33 + <div v-if="alertsSummary.alerts.length > 3" class="load-more" @click="showAllAlerts = true">
34 <n-button size="small" text class="!w-full">
35 <template #icon>
36 <Icon :name="ExpandIcon"></Icon>
@@ -44,14 +44,14 @@
44 </template>
45
46 <script setup lang="ts">
47 -import { type AlertsSummary } from "@/types/alerts.d"
48 -import { NScrollbar, NButton } from "naive-ui"
49 -import Alert from "./Alert.vue"
50 -import IndexIcon from "@/components/indices/IndexIcon.vue"
51 -import Icon from "@/components/common/Icon.vue"
47 +import type { AlertsSummary } from "@/types/alerts.d"
48 import type { IndexStats } from "@/types/indices.d"
53 -import { ref } from "vue"
49 +import Icon from "@/components/common/Icon.vue"
50 +import IndexIcon from "@/components/indices/IndexIcon.vue"
51 import { useGoto } from "@/composables/useGoto"
52 +import { NButton, NScrollbar } from "naive-ui"
53 +import { ref } from "vue"
54 +import Alert from "./Alert.vue"
55
56 export interface AlertsSummaryExt extends AlertsSummary {
57 indexStats?: IndexStats
frontend/src/components/alerts/mock.ts
+1 -1
@@ -2102,4 +2102,4 @@ const alerts_by_rule_per_host = [
2102 }
2103 ]
2104
2105 -export { alerts_summary, alerts_by_host, alerts_by_rule, alerts_by_rule_per_host }
2105 +export { alerts_by_host, alerts_by_rule, alerts_by_rule_per_host, alerts_summary }
frontend/src/components/artifacts/ArtifactItem.vue
+3 -1
@@ -3,7 +3,9 @@
3 <div class="name text-secondary-color">
4 {{ artifact.name }}
5 </div>
6 - <div class="description">{{ artifact.description }}</div>
6 + <div class="description">
7 + {{ artifact.description }}
8 + </div>
9 </div>
10 </template>
11
frontend/src/components/artifacts/ArtifactRecommendation.vue
+18 -14
@@ -19,20 +19,20 @@
19 <div class="flex gap-3 items-center flex-wrap mb-4">
20 <div>Get Recommendations for OS:</div>
21 <n-select
22 - size="small"
22 v-model:value="selectedOs"
23 + size="small"
24 :options="osOptions"
25 class="!w-32"
26 placeholder="Select OS"
27 :disabled="loading"
28 />
29 <n-button
30 + v-if="!recommendations.length"
31 size="small"
32 type="primary"
33 :loading
33 - @click="getRecommendations()"
34 :disabled="!selectedOs"
35 - v-if="!recommendations.length"
35 + @click="getRecommendations()"
36 >
37 <template #icon>
38 <Icon :name="AiIcon" />
@@ -43,22 +43,26 @@
43 <n-spin :show="loading" class="min-h-48">
44 <div v-if="recommendations.length" class="flex flex-col gap-2">
45 <n-card
46 - content-class="bg-secondary-color flex flex-col gap-2 !p-0"
47 - class="overflow-hidden item-appear item-appear-bottom item-appear-005"
46 v-for="recommendation of recommendations"
47 :key="recommendation.name"
48 + content-class="bg-secondary-color flex flex-col gap-2 !p-0"
49 + class="overflow-hidden item-appear item-appear-bottom item-appear-005"
50 size="small"
51 >
52 <strong class="recommendation-name font-mono px-4 pt-3 pb-1">{{ recommendation.name }}</strong>
53 <n-divider class="!m-0" />
54 - <div class="recommendation-description px-4 pt-1">{{ recommendation.description }}</div>
55 - <p class="recommendation-explanation px-4 pb-3 pt-2">{{ recommendation.explanation }}</p>
54 + <div class="recommendation-description px-4 pt-1">
55 + {{ recommendation.description }}
56 + </div>
57 + <p class="recommendation-explanation px-4 pb-3 pt-2">
58 + {{ recommendation.explanation }}
59 + </p>
60 </n-card>
61 </div>
62 <n-empty
63 + v-if="!loading && !recommendations.length"
64 description="Recommendations not found"
65 class="justify-center h-48"
61 - v-if="!loading && !recommendations.length"
66 />
67 </n-spin>
68 </n-modal>
@@ -66,14 +70,14 @@
70 </template>
71
72 <script setup lang="ts">
69 -import { computed, ref } from "vue"
70 -import { useMessage, NModal, NSpin, NEmpty, NButton, NDivider, NSelect, NCard } from "naive-ui"
71 -import Icon from "@/components/common/Icon.vue"
72 -import Api from "@/api"
73 -import _uniqBy from "lodash/uniqBy"
74 -import type { OsTypesFull } from "@/types/common.d"
73 import type { Recommendation } from "@/types/artifacts.d"
74 +import type { OsTypesFull } from "@/types/common.d"
75 import type { Size } from "naive-ui/es/button/src/interface"
76 +import Api from "@/api"
77 +import Icon from "@/components/common/Icon.vue"
78 +import _uniqBy from "lodash/uniqBy"
79 +import { NButton, NCard, NDivider, NEmpty, NModal, NSelect, NSpin, useMessage } from "naive-ui"
80 +import { computed, ref } from "vue"
81
82 interface RecommendationStore {
83 os: OsTypesFull
frontend/src/components/artifacts/ArtifactsCollect.vue
+19 -17
@@ -1,7 +1,7 @@
1 <template>
2 <div class="artifacts-collect">
3 <div class="header flex justify-end items-start gap-2">
4 - <div class="info flex gap-5" v-if="isDirty">
4 + <div v-if="isDirty" class="info flex gap-5">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
7 <div class="bg-color border-radius">
@@ -21,7 +21,7 @@
21 </n-popover>
22 </div>
23 <div class="grow flex items-center justify-end gap-2 flex-wrap">
24 - <div class="grow basis-56" v-if="!hideHostnameField">
24 + <div v-if="!hideHostnameField" class="grow basis-56">
25 <n-select
26 v-model:value="filters.hostname"
27 :options="agentHostnameOptions"
@@ -45,7 +45,7 @@
45 :loading="loadingArtifacts"
46 />
47 </div>
48 - <div class="grow basis-56" v-if="!hideVelociraptorIdField">
48 + <div v-if="!hideVelociraptorIdField" class="grow basis-56">
49 <n-input
50 v-model:value="filters.velociraptor_id"
51 placeholder="Velociraptor ID"
@@ -57,11 +57,11 @@
57 <div>
58 <n-button
59 size="small"
60 - @click="getData()"
60 type="primary"
61 secondary
62 :loading="loading"
63 :disabled="!areFiltersValid"
64 + @click="getData()"
65 >
66 Submit
67 </n-button>
@@ -79,7 +79,7 @@
79 />
80 </template>
81 <template v-else>
82 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
82 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
83 </template>
84 </div>
85 </n-spin>
@@ -87,21 +87,15 @@
87 </template>
88
89 <script setup lang="ts">
90 -import { ref, onBeforeMount, toRefs, computed, nextTick } from "vue"
91 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NSelect, NInput } from "naive-ui"
92 -import Api from "@/api"
93 -import Icon from "@/components/common/Icon.vue"
94 -import CollectItem from "./CollectItem.vue"
95 -import type { Agent } from "@/types/agents.d"
90 import type { ArtifactsQuery, CollectRequest } from "@/api/endpoints/artifacts"
91 +import type { Agent } from "@/types/agents.d"
92 import type { Artifact, CollectResult } from "@/types/artifacts.d"
93 +import Api from "@/api"
94 +import Icon from "@/components/common/Icon.vue"
95 +import { NButton, NEmpty, NInput, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
96 import { nanoid } from "nanoid"
99 -// import { collectResult } from "./mock"
100 -
101 -const emit = defineEmits<{
102 - (e: "loaded-agents", value: Agent[]): void
103 - (e: "loaded-artifacts", value: Artifact[]): void
104 -}>()
97 +import { computed, nextTick, onBeforeMount, ref, toRefs } from "vue"
98 +import CollectItem from "./CollectItem.vue"
99
100 const props = defineProps<{
101 hostname?: string
@@ -112,6 +106,14 @@ const props = defineProps<{
106 hideHostnameField?: boolean
107 hideVelociraptorIdField?: boolean
108 }>()
109 +
110 +// import { collectResult } from "./mock"
111 +
112 +const emit = defineEmits<{
113 + (e: "loaded-agents", value: Agent[]): void
114 + (e: "loaded-artifacts", value: Artifact[]): void
115 +}>()
116 +
117 const { hostname, velociraptorId, agents, artifacts, artifactsFilter, hideHostnameField, hideVelociraptorIdField } =
118 toRefs(props)
119
frontend/src/components/artifacts/ArtifactsCommand.vue
+24 -22
@@ -3,7 +3,7 @@
3 <div class="header flex items-start gap-2">
4 <div class="flex flex-col gap-2 w-full">
5 <div class="grow flex items-center gap-2 flex-wrap">
6 - <div class="grow basis-56" v-if="!hideHostnameField">
6 + <div v-if="!hideHostnameField" class="grow basis-56">
7 <n-select
8 v-model:value="filters.hostname"
9 :options="agentHostnameOptions"
@@ -27,7 +27,7 @@
27 :loading="loadingArtifacts"
28 />
29 </div>
30 - <div class="grow basis-56" v-if="!hideVelociraptorIdField">
30 + <div v-if="!hideVelociraptorIdField" class="grow basis-56">
31 <n-input
32 v-model:value="filters.velociraptor_id"
33 placeholder="Velociraptor id"
@@ -52,7 +52,7 @@
52 </div>
53 <div class="grow flex items-center justify-end gap-2 flex-wrap-reverse">
54 <div class="badges-box flex gap-2 flex-wrap grow">
55 - <n-tooltip trigger="hover" v-if="commandTime">
55 + <n-tooltip v-if="commandTime" trigger="hover">
56 <template #trigger>
57 <Badge type="splitted" color="primary" hint-cursor>
58 <template #iconLeft>
@@ -62,9 +62,9 @@
62 <span class="flex">
63 {{ formatDate(commandTime, dFormats.timesec) }}
64
65 - <n-spin :size="12" v-if="loading" class="ml-2" />
65 + <n-spin v-if="loading" :size="12" class="ml-2" />
66
67 - {{ responseTime ? " / " + formatDate(responseTime, dFormats.timesec) : "" }}
67 + {{ responseTime ? ` / ${formatDate(responseTime, dFormats.timesec)}` : "" }}
68 </span>
69 </template>
70 </Badge>
@@ -72,7 +72,7 @@
72 Last request time / last response time
73 </n-tooltip>
74
75 - <Badge type="splitted" color="primary" v-if="diffTime">
75 + <Badge v-if="diffTime" type="splitted" color="primary">
76 <template #iconLeft>
77 <Icon :name="StopWatchIcon" :size="15"></Icon>
78 </template>
@@ -83,11 +83,11 @@
83 </div>
84 <n-button
85 size="small"
86 - @click="getData()"
86 type="primary"
87 secondary
88 :loading="loading"
89 :disabled="!areFiltersValid"
90 + @click="getData()"
91 >
92 Submit
93 </n-button>
@@ -105,7 +105,7 @@
105 />
106 </template>
107 <template v-else>
108 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
108 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
109 </template>
110 </div>
111 </n-spin>
@@ -113,24 +113,18 @@
113 </template>
114
115 <script setup lang="ts">
116 -import { ref, onBeforeMount, toRefs, computed, nextTick } from "vue"
117 -import { useMessage, NSpin, NButton, NEmpty, NSelect, NInput, NTooltip } from "naive-ui"
118 -import Api from "@/api"
119 -import CommandItem from "./CommandItem.vue"
120 -import Badge from "@/components/common/Badge.vue"
121 -import type { Agent } from "@/types/agents.d"
116 import type { CommandRequest } from "@/api/endpoints/artifacts"
117 +import type { Agent } from "@/types/agents.d"
118 import type { Artifact, CommandResult } from "@/types/artifacts.d"
124 -import dayjs from "@/utils/dayjs"
119 +import Api from "@/api"
120 +import Badge from "@/components/common/Badge.vue"
121 import Icon from "@/components/common/Icon.vue"
122 import { useSettingsStore } from "@/stores/settings"
123 import { formatDate } from "@/utils"
128 -// import { commandResult } from "./mock"
129 -
130 -const emit = defineEmits<{
131 - (e: "loaded-agents", value: Agent[]): void
132 - (e: "loaded-artifacts", value: Artifact[]): void
133 -}>()
124 +import dayjs from "@/utils/dayjs"
125 +import { NButton, NEmpty, NInput, NSelect, NSpin, NTooltip, useMessage } from "naive-ui"
126 +import { computed, nextTick, onBeforeMount, ref, toRefs } from "vue"
127 +import CommandItem from "./CommandItem.vue"
128
129 const props = defineProps<{
130 hostname?: string
@@ -139,6 +133,14 @@ const props = defineProps<{
133 hideHostnameField?: boolean
134 hideVelociraptorIdField?: boolean
135 }>()
136 +
137 +// import { commandResult } from "./mock"
138 +
139 +const emit = defineEmits<{
140 + (e: "loaded-agents", value: Agent[]): void
141 + (e: "loaded-artifacts", value: Artifact[]): void
142 +}>()
143 +
144 const { hostname, agents, artifacts, hideHostnameField, hideVelociraptorIdField } = toRefs(props)
145
146 const TimeIcon = "carbon:time"
@@ -157,7 +159,7 @@ const dFormats = useSettingsStore().dateFormat
159
160 const diffTime = computed(() => {
161 if (commandTime.value && responseTime.value) {
160 - return dayjs.duration(dayjs(responseTime.value).diff(commandTime.value, "ms", true)).asSeconds() + "s"
162 + return `${dayjs.duration(dayjs(responseTime.value).diff(commandTime.value, "ms", true)).asSeconds()}s`
163 } else {
164 return null
165 }
frontend/src/components/artifacts/ArtifactsList.vue
+22 -21
@@ -1,6 +1,6 @@
1 <template>
2 <div class="artifacts-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -38,7 +38,7 @@
38 type="success"
39 :offset="[-4, 0]"
40 >
41 - <n-button size="small" v-show="!isFilterPreselected" @click="showFilters = true">
41 + <n-button v-show="!isFilterPreselected" size="small" @click="showFilters = true">
42 <template #icon>
43 <Icon :name="FilterIcon"></Icon>
44 </template>
@@ -50,14 +50,8 @@
50 <div class="px-3">
51 <n-input-group class="artifacts-list-filter-combo" :class="{ 'filters-active': filterType }">
52 <n-select
53 - class="artifacts-list-filter-type"
53 v-model:value="filterType"
55 - @update:value="
56 - () => {
57 - filters.hostname = undefined
58 - filters.os = undefined
59 - }
60 - "
54 + class="artifacts-list-filter-type"
55 :options="[
56 {
57 label: 'Agent ',
@@ -70,6 +64,12 @@
64 ]"
65 placeholder="Filters..."
66 clearable
67 + @update:value="
68 + () => {
69 + filters.hostname = undefined
70 + filters.os = undefined
71 + }
72 + "
73 />
74
75 <n-select
@@ -91,8 +91,8 @@
91 </n-input-group>
92 </div>
93 <div class="px-3 flex justify-end gap-2">
94 - <n-button size="small" @click="showFilters = false" secondary>Close</n-button>
95 - <n-button size="small" @click="getData()" type="primary" secondary>Submit</n-button>
94 + <n-button size="small" secondary @click="showFilters = false">Close</n-button>
95 + <n-button size="small" type="primary" secondary @click="getData()">Submit</n-button>
96 </div>
97 </div>
98 </n-popover>
@@ -108,40 +108,41 @@
108 />
109 </template>
110 <template v-else>
111 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
111 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
112 </template>
113 </div>
114 </n-spin>
115 <div class="footer flex justify-end">
116 <n-pagination
117 + v-if="itemsPaginated.length > 3"
118 v-model:page="currentPage"
119 :page-size="pageSize"
120 :item-count="totalArtifacts"
121 :page-slot="6"
121 - v-if="itemsPaginated.length > 3"
122 />
123 </div>
124 </div>
125 </template>
126
127 <script setup lang="ts">
128 -import { ref, onBeforeMount, toRefs, computed, nextTick, watch } from "vue"
129 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NSelect, NPagination, NInputGroup, NBadge } from "naive-ui"
130 -import Api from "@/api"
131 -import _cloneDeep from "lodash/cloneDeep"
132 -import Icon from "@/components/common/Icon.vue"
133 -import ArtifactItem from "./ArtifactItem.vue"
134 -import type { Agent } from "@/types/agents.d"
128 import type { ArtifactsQuery } from "@/api/endpoints/artifacts"
129 +import type { Agent } from "@/types/agents.d"
130 import type { Artifact } from "@/types/artifacts.d"
131 +import Api from "@/api"
132 +import Icon from "@/components/common/Icon.vue"
133 import { useResizeObserver } from "@vueuse/core"
134 +import _cloneDeep from "lodash/cloneDeep"
135 +import { NBadge, NButton, NEmpty, NInputGroup, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
136 +import { computed, nextTick, onBeforeMount, ref, toRefs, watch } from "vue"
137 +import ArtifactItem from "./ArtifactItem.vue"
138 +
139 +const props = defineProps<{ agentHostname?: string; agents?: Agent[]; artifacts?: Artifact[] }>()
140
141 const emit = defineEmits<{
142 (e: "loaded-agents", value: Agent[]): void
143 (e: "loaded-artifacts", value: Artifact[]): void
144 }>()
145
144 -const props = defineProps<{ agentHostname?: string; agents?: Agent[]; artifacts?: Artifact[] }>()
146 const { agentHostname, agents, artifacts } = toRefs(props)
147
148 const message = useMessage()
frontend/src/components/artifacts/ArtifactsQuarantine.vue
+18 -16
@@ -2,7 +2,7 @@
2 <div class="artifacts-quarantine">
3 <div class="header flex justify-end items-start gap-2">
4 <div class="grow flex items-center justify-end gap-2 flex-wrap">
5 - <div class="grow basis-56" v-if="!hideHostnameField">
5 + <div v-if="!hideHostnameField" class="grow basis-56">
6 <n-select
7 v-model:value="filters.hostname"
8 :options="agentHostnameOptions"
@@ -26,7 +26,7 @@
26 :loading="loadingArtifacts"
27 />
28 </div>
29 - <div class="grow basis-56" v-if="!hideVelociraptorIdField">
29 + <div v-if="!hideVelociraptorIdField" class="grow basis-56">
30 <n-input
31 v-model:value="filters.velociraptor_id"
32 placeholder="Velociraptor id"
@@ -47,11 +47,11 @@
47 />
48 <n-button
49 size="small"
50 - @click="getData()"
50 type="primary"
51 secondary
52 :loading="loading"
53 :disabled="!areFiltersValid"
54 + @click="getData()"
55 >
56 <Icon :name="SubmitIcon"></Icon>
57 </n-button>
@@ -70,7 +70,7 @@
70 />
71 </template>
72 <template v-else>
73 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
73 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
74 </template>
75 </div>
76 </n-spin>
@@ -78,21 +78,14 @@
78 </template>
79
80 <script setup lang="ts">
81 -import { ref, onBeforeMount, toRefs, computed, nextTick } from "vue"
82 -import { useMessage, NSpin, NButton, NEmpty, NSelect, NInput, NInputGroup } from "naive-ui"
81 +import type { QuarantineRequest } from "@/api/endpoints/artifacts"
82 +import type { Agent } from "@/types/agents.d"
83 +import type { Artifact, QuarantineResult } from "@/types/artifacts.d"
84 import Api from "@/api"
85 import Icon from "@/components/common/Icon.vue"
86 +import { NButton, NEmpty, NInput, NInputGroup, NSelect, NSpin, useMessage } from "naive-ui"
87 +import { computed, nextTick, onBeforeMount, ref, toRefs } from "vue"
88 import QuarantineItem from "./QuarantineItem.vue"
86 -import type { Agent } from "@/types/agents.d"
87 -import type { QuarantineRequest } from "@/api/endpoints/artifacts"
88 -import type { Artifact, QuarantineResult } from "@/types/artifacts.d"
89 -// import { quarantineResult } from "./mock"
90 -
91 -const emit = defineEmits<{
92 - (e: "loaded-agents", value: Agent[]): void
93 - (e: "loaded-artifacts", value: Artifact[]): void
94 - (e: "action-performed"): void
95 -}>()
89
90 const props = defineProps<{
91 hostname?: string
@@ -101,6 +94,15 @@ const props = defineProps<{
94 hideHostnameField?: boolean
95 hideVelociraptorIdField?: boolean
96 }>()
97 +
98 +// import { quarantineResult } from "./mock"
99 +
100 +const emit = defineEmits<{
101 + (e: "loaded-agents", value: Agent[]): void
102 + (e: "loaded-artifacts", value: Artifact[]): void
103 + (e: "action-performed"): void
104 +}>()
105 +
106 const { hostname, agents, artifacts, hideHostnameField, hideVelociraptorIdField } = toRefs(props)
107
108 const message = useMessage()
frontend/src/components/artifacts/CollectItem.vue
+14 -10
@@ -1,8 +1,12 @@
1 <template>
2 <div class="collect-item flex flex-wrap gap-2 p-2" :class="{ embedded }">
3 <KVCard v-for="prop of displayData" :key="prop.key" :class="{ 'hide-mobile': prop.hideMobile }">
4 - <template #key>{{ prop.key }}</template>
5 - <template #value>{{ prop.value }}</template>
4 + <template #key>
5 + {{ prop.key }}
6 + </template>
7 + <template #value>
8 + {{ prop.value }}
9 + </template>
10 </KVCard>
11 <KVCard class="more" @click="showDetails = true">
12 <template #value>
@@ -16,23 +20,23 @@
20 :style="{ maxWidth: 'min(800px, 90vw)', overflow: 'hidden' }"
21 :bordered="false"
22 >
19 - <SimpleJsonViewer class="vuesjv-override" :model-value="jsonData" :initialExpandedDepth="2" />
23 + <SimpleJsonViewer class="vuesjv-override" :model-value="jsonData" :initial-expanded-depth="2" />
24 </n-modal>
25 </div>
26 </template>
27
28 <script setup lang="ts">
25 -import { NModal } from "naive-ui"
26 -import { useSettingsStore } from "@/stores/settings"
29 import type { CollectResult } from "@/types/artifacts.d"
30 +import KVCard from "@/components/common/KVCard.vue"
31 +import { useSettingsStore } from "@/stores/settings"
32 +import { formatDate } from "@/utils"
33 import dayjs from "@/utils/dayjs"
34 +import _isNumber from "lodash/isNumber"
35 +import _isString from "lodash/isString"
36 +import { NModal } from "naive-ui"
37 +import { onBeforeMount, ref } from "vue"
38 import { SimpleJsonViewer } from "vue-sjv"
39 import "@/assets/scss/vuesjv-override.scss"
31 -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"
40
41 interface Prop {
42 key: string
frontend/src/components/artifacts/CommandItem.vue
+4 -4
@@ -3,7 +3,7 @@
3 <div class="header-box flex justify-between">
4 <div class="status">
5 Complete:
6 - <strong class="font-mono" :class="{ success: command.Complete + '' === 'true' }">
6 + <strong class="font-mono" :class="{ success: `${command.Complete}` === 'true' }">
7 {{ command.Complete }}
8 </strong>
9 </div>
@@ -13,7 +13,7 @@
13 </div>
14 </div>
15 <div class="main-box">
16 - <div class="output stdout" v-if="command.Stdout">
16 + <div v-if="command.Stdout" class="output stdout">
17 <label>Stdout</label>
18 <n-input
19 :value="command.Stdout"
@@ -26,7 +26,7 @@
26 }"
27 />
28 </div>
29 - <div class="output stderr" v-if="command.Stderr">
29 + <div v-if="command.Stderr" class="output stderr">
30 <label class="flex items-center">
31 <Icon :name="DangerIcon" class="mr-1"></Icon>
32 Stderr
@@ -47,9 +47,9 @@
47 </template>
48
49 <script setup lang="ts">
50 -import { NInput } from "naive-ui"
50 import type { CommandResult } from "@/types/artifacts.d"
51 import Icon from "@/components/common/Icon.vue"
52 +import { NInput } from "naive-ui"
53
54 const { command } = defineProps<{ command: CommandResult }>()
55
frontend/src/components/artifacts/QuarantineItem.vue
+4 -2
@@ -3,13 +3,15 @@
3 <div class="time text-secondary-color">
4 {{ formatDate(quarantine.Time, dFormats.datetimesec) }}
5 </div>
6 - <div class="result">{{ quarantine.Result }}</div>
6 + <div class="result">
7 + {{ quarantine.Result }}
8 + </div>
9 </div>
10 </template>
11
12 <script setup lang="ts">
11 -import { useSettingsStore } from "@/stores/settings"
13 import type { QuarantineResult } from "@/types/artifacts.d"
14 +import { useSettingsStore } from "@/stores/settings"
15 import { formatDate } from "@/utils"
16
17 const { quarantine } = defineProps<{ quarantine: QuarantineResult }>()
frontend/src/components/auth/AuthForm.vue
+16 -19
@@ -1,7 +1,9 @@
1 <template>
2 <div class="form-wrap">
3 <Logo mini :dark="isDark" class="mb-4" />
4 - <div class="title mb-4">{{ title }}</div>
4 + <div class="title mb-4">
5 + {{ title }}
6 + </div>
7 <div class="text mb-12">Access the world of OpenSource security: Simplified, Streamlined, Accessible.</div>
8
9 <div class="form">
@@ -13,31 +15,31 @@
15 </div>
16
17 <div class="sign-text text-center mt-10">
16 - <div class="sign-text" v-if="typeRef === 'signin'">
18 + <div v-if="typeRef === 'signin'" class="sign-text">
19 Don't you have an account?
18 - <n-button text @click="gotoSignUp()" type="primary" size="large">Sign up</n-button>
20 + <n-button text type="primary" size="large" @click="gotoSignUp()">Sign up</n-button>
21 </div>
20 - <div class="sign-text" v-if="typeRef === 'forgotpassword'">
21 - <n-button text @click="gotoSignIn()" type="primary" size="large">Back to Sign in</n-button>
22 + <div v-if="typeRef === 'forgotpassword'" class="sign-text">
23 + <n-button text type="primary" size="large" @click="gotoSignIn()">Back to Sign in</n-button>
24 </div>
23 - <div class="sign-text" v-if="typeRef === 'signup'">
25 + <div v-if="typeRef === 'signup'" class="sign-text">
26 Do you have an account?
25 - <n-button text @click="gotoSignIn()" type="primary" size="large">Sign in</n-button>
27 + <n-button text type="primary" size="large" @click="gotoSignIn()">Sign in</n-button>
28 </div>
29 </div>
30 </div>
31 </template>
32
33 <script lang="ts" setup>
32 -import { useThemeStore } from "@/stores/theme"
33 -import SignIn from "./SignIn.vue"
34 -import ForgotPassword from "./ForgotPassword.vue"
35 -import SignUp from "./SignUp.vue"
34 +import type { FormType } from "./types.d"
35 import Logo from "@/layouts/common/Logo.vue"
36 +import { useThemeStore } from "@/stores/theme"
37 import { NButton } from "naive-ui"
38 -import { ref, onBeforeMount, computed } from "vue"
38 +import { computed, onBeforeMount, ref } from "vue"
39 import { useRouter } from "vue-router"
40 -import type { FormType } from "./types.d"
40 +import ForgotPassword from "./ForgotPassword.vue"
41 +import SignIn from "./SignIn.vue"
42 +import SignUp from "./SignUp.vue"
43
44 const props = defineProps<{
45 type?: FormType
@@ -74,14 +76,9 @@ function gotoForgotPassword() {
76 if (!props.useOnlyRouter) {
77 typeRef.value = "forgotpassword"
78 }
79 + router.replace({ name: "ForgotPassword" })
80 }
81
79 -onBeforeMount(() => {
80 - if (props.type) {
81 - typeRef.value = props.type
82 - }
83 -})
84 -
82 onBeforeMount(() => {
83 if (props.type) {
84 typeRef.value = props.type
frontend/src/components/auth/ForgotPassword.vue
+6 -6
@@ -3,31 +3,31 @@
3 <n-form-item path="email" label="Email">
4 <n-input
5 v-model:value="model.email"
6 - @keydown.enter="forgotPassword"
6 placeholder="Example@email.com"
7 size="large"
8 + @keydown.enter="forgotPassword"
9 />
10 </n-form-item>
11 <div class="flex flex-col items-end gap-6">
12 <div class="w-full">
13 - <n-button type="primary" @click="forgotPassword" class="!w-full" size="large">Send Reset Link</n-button>
13 + <n-button type="primary" class="!w-full" size="large" @click="forgotPassword">Send Reset Link</n-button>
14 </div>
15 </div>
16 </n-form>
17 </template>
18
19 <script lang="ts" setup>
20 -import { ref } from "vue"
20 import {
21 type FormInst,
23 - type FormValidationError,
22 type FormRules,
25 - useMessage,
23 + type FormValidationError,
24 + NButton,
25 NForm,
26 NFormItem,
27 NInput,
29 - NButton
28 + useMessage
29 } from "naive-ui"
30 +import { ref } from "vue"
31
32 interface ModelType {
33 email: string | null
frontend/src/components/auth/SignIn.vue
+9 -9
@@ -3,10 +3,10 @@
3 <n-form-item path="username" label="Username">
4 <n-input
5 v-model:value="model.username"
6 - @keydown.enter="signIn"
6 placeholder="Username..."
7 size="large"
8 autocomplete="on"
9 + @keydown.enter="signIn"
10 />
11 </n-form-item>
12 <n-form-item path="password" label="Password">
@@ -15,9 +15,9 @@
15 type="password"
16 show-password-on="click"
17 placeholder="Password..."
18 - @keydown.enter="signIn"
18 autocomplete="on"
19 size="large"
20 + @keydown.enter="signIn"
21 />
22 </n-form-item>
23 <div class="flex flex-col items-end gap-6">
@@ -27,7 +27,7 @@
27 </div>
28 -->
29 <div class="w-full">
30 - <n-button type="primary" @click="signIn" class="!w-full" size="large" :loading="loading">
30 + <n-button type="primary" class="!w-full" size="large" :loading="loading" @click="signIn">
31 Sign in
32 </n-button>
33 </div>
@@ -36,20 +36,20 @@
36 </template>
37
38 <script lang="ts" setup>
39 -import { ref } from "vue"
39 +import type { LoginPayload } from "@/types/auth.d"
40 +import { useAuthStore } from "@/stores/auth"
41 import {
42 type FormInst,
42 - type FormValidationError,
43 type FormRules,
44 - useMessage,
44 + type FormValidationError,
45 + NButton,
46 NForm,
47 NFormItem,
48 NInput,
48 - NButton
49 + useMessage
50 } from "naive-ui"
50 -import { useAuthStore } from "@/stores/auth"
51 +import { ref } from "vue"
52 import { useRouter } from "vue-router"
52 -import type { LoginPayload } from "@/types/auth.d"
53
54 interface ModelType {
55 username: string
frontend/src/components/auth/SignUp.vue
+22 -22
@@ -3,23 +3,23 @@
3 <n-form ref="formRef" :model="model" :rules="rules">
4 <n-steps :current="wizardCurrent" vertical>
5 <n-step title="Account">
6 - <div class="pt-3" v-show="wizardCurrent === 1">
6 + <div v-show="wizardCurrent === 1" class="pt-3">
7 <n-form-item path="email" label="Email">
8 <n-input
9 v-model:value="model.email"
10 - @keydown.enter="signUp"
10 size="large"
11 placeholder="Email..."
12 + @keydown.enter="signUp"
13 />
14 </n-form-item>
15 <n-form-item path="password" label="Password">
16 <n-input
17 v-model:value="model.password"
18 type="password"
19 - @keydown.enter="signUp"
19 size="large"
20 show-password-on="click"
21 placeholder="At least 8 characters"
22 + @keydown.enter="signUp"
23 />
24 </n-form-item>
25 <n-form-item path="confirmPassword" label="Confirm Password" first>
@@ -27,19 +27,19 @@
27 v-model:value="model.confirmPassword"
28 type="password"
29 :disabled="!model.password"
30 - @keydown.enter="signUp"
30 size="large"
31 show-password-on="click"
32 placeholder="At least 8 characters"
33 + @keydown.enter="signUp"
34 />
35 </n-form-item>
36 <div class="flex items-center justify-end mt-3">
37 <n-button
38 type="primary"
39 - @click="wizardCurrent = 2"
39 size="large"
40 icon-placement="right"
41 :disabled="!accountStepValid"
42 + @click="wizardCurrent = 2"
43 >
44 <template #icon>
45 <Icon :name="ArrowRightIcon"></Icon>
@@ -50,13 +50,13 @@
50 </div>
51 </n-step>
52 <n-step title="Details">
53 - <div class="pt-3" v-show="wizardCurrent === 2">
53 + <div v-show="wizardCurrent === 2" class="pt-3">
54 <n-form-item path="username" label="Username">
55 <n-input
56 v-model:value="model.username"
57 - @keydown.enter="signUp"
57 size="large"
58 placeholder="Username..."
59 + @keydown.enter="signUp"
60 />
61 </n-form-item>
62
@@ -114,7 +114,7 @@
114 -->
115
116 <div class="flex items-center justify-between mt-3 gap-3">
117 - <n-button @click="wizardCurrent = 1" size="large">
117 + <n-button size="large" @click="wizardCurrent = 1">
118 <template #icon>
119 <Icon :name="ArrowLeftIcon"></Icon>
120 </template>
@@ -122,10 +122,10 @@
122 </n-button>
123 <n-button
124 type="primary"
125 - @click="signUp"
125 size="large"
126 :loading="loading"
127 :disabled="!accountStepValid || !detailsStepValid"
128 + @click="signUp"
129 >
130 <template #icon>
131 <Icon :name="UserAddIcon"></Icon>
@@ -141,28 +141,28 @@
141 </template>
142
143 <script lang="ts" setup>
144 -import { computed, ref } from "vue"
144 +import type { RegisterPayload } from "@/types/auth.d"
145
146 +import Api from "@/api"
147 +import Icon from "@/components/common/Icon.vue"
148 import {
149 type FormInst,
148 - type FormValidationError,
150 + type FormItemRule,
151 type FormRules,
150 - useMessage,
152 + type FormValidationError,
153 + NButton,
154 NForm,
155 NFormItem,
156 NInput,
154 - NButton,
155 - NSteps,
156 - NStep,
157 NSpin,
158 - type FormItemRule
158 + NStep,
159 + NSteps,
160 + useMessage
161 } from "naive-ui"
162 import isEmail from "validator/es/lib/isEmail"
163 +import { computed, ref } from "vue"
164 // import ImageCropper, { type ImageCropperResult } from "@/components/common/ImageCropper.vue"
162 -import passwordValidator from "password-validator"
163 -import Api from "@/api"
164 -import type { RegisterPayload } from "@/types/auth.d"
165 -import Icon from "@/components/common/Icon.vue"
165 +import PasswordValidator from "password-validator"
166
167 interface ModelType {
168 email: string
@@ -206,9 +206,9 @@ const model = ref<ModelType>({
206
207 const accountStepValid = computed(() => !!model.value.email && !!model.value.password && !!model.value.confirmPassword)
208 const detailsStepValid = computed(() => !!model.value.username)
209 -//const detailsStepValid = computed(() => !!model.value.customerCode && !!model.value.firstName && !!model.value.lastName)
209 +// const detailsStepValid = computed(() => !!model.value.customerCode && !!model.value.firstName && !!model.value.lastName)
210
211 -const passwordSchema = new passwordValidator()
211 +const passwordSchema = new PasswordValidator()
212 passwordSchema
213 .is()
214 .min(8) // Minimum length 8
frontend/src/components/cards/CardActions.vue
+23 -19
@@ -1,43 +1,42 @@
1 <template>
2 <n-card hoverable size="large" :class="{ actionBoxTransparent }">
3 - <template #cover v-if="showImage">
3 + <template v-if="showImage" #cover>
4 <img :src="image || 'https://picsum.photos/seed/IqZMU/900/300'" width="900" height="300" />
5 </template>
6 <template #header>
7 - <div class="title" v-if="title">{{ title }}</div>
8 - <div class="subtitle" v-if="!hideSubtitle && subtitle">{{ subtitle }}</div>
7 + <div v-if="title" class="title">
8 + {{ title }}
9 + </div>
10 + <div v-if="!hideSubtitle && subtitle" class="subtitle">
11 + {{ subtitle }}
12 + </div>
13 </template>
14 <template #header-extra>
11 - <n-dropdown :options="menuOptions" placement="bottom-end" to="body" @select="menuSelect" v-if="!hideMenu">
15 + <n-dropdown v-if="!hideMenu" :options="menuOptions" placement="bottom-end" to="body" @select="menuSelect">
16 <Icon :size="20" :name="MenuIcon" />
17 </n-dropdown>
18 </template>
19 <template #default>
20 <div class="overflow-hidden w-full">
21 <n-scrollbar x-scrollable class="!w-full" trigger="none">
18 - <slot></slot>
22 + <slot />
23 </n-scrollbar>
24 </div>
25 </template>
22 - <template #footer v-if="$slots.footer">
26 + <template v-if="$slots.footer" #footer>
27 <slot name="footer" />
28 </template>
25 - <template #action v-if="$slots.action">
29 + <template v-if="$slots.action" #action>
30 <slot name="action" />
31 </template>
32 </n-card>
33 </template>
34
35 <script setup lang="ts">
32 -import { NCard, NDropdown, NScrollbar } from "naive-ui"
36 import Icon from "@/components/common/Icon.vue"
34 -import { computed, toRefs, onMounted, ref } from "vue"
37 import { renderIcon } from "@/utils"
36 -
37 -const MenuIcon = "carbon:overflow-menu-vertical"
38 -const ContractIcon = "fluent:contract-down-left-24-regular"
39 -const ExpandIcon = "fluent:expand-up-right-24-regular"
40 -const ReloadIcon = "tabler:refresh"
38 +import { NCard, NDropdown, NScrollbar } from "naive-ui"
39 +import { computed, onMounted, ref, toRefs } from "vue"
40
41 const props = defineProps<{
42 showImage?: boolean
@@ -51,13 +50,18 @@ const props = defineProps<{
50 subtitle?: string
51 image?: string
52 }>()
53 +const MenuIcon = "carbon:overflow-menu-vertical"
54 +const ContractIcon = "fluent:contract-down-left-24-regular"
55 +const ExpandIcon = "fluent:expand-up-right-24-regular"
56 +const ReloadIcon = "tabler:refresh"
57 +
58 const { showImage, hideSubtitle, title, subtitle, image, actionBoxTransparent, hideMenu, reload, expand, isExpand } =
59 toRefs(props)
60
61 let reloadTimeout: NodeJS.Timeout | null = null
62 const showExpandButton = ref(true)
63
60 -/*eslint no-mixed-spaces-and-tabs: "off"*/
64 +/* eslint no-mixed-spaces-and-tabs: "off" */
65 const menuOptions = computed(() =>
66 showExpandButton.value
67 ? [
@@ -88,20 +92,20 @@ const menuOptions = computed(() =>
92
93 function menuSelect(key: string) {
94 if (key === "expand") {
91 - expand?.value && expand?.value(true)
95 + expand?.value?.(true)
96 }
97 if (key === "collapse") {
94 - expand?.value && expand?.value(false)
98 + expand?.value?.(false)
99 }
100 if (key === "reload") {
97 - reload?.value && reload?.value(true)
101 + reload?.value?.(true)
102
103 if (reloadTimeout) {
104 clearTimeout(reloadTimeout)
105 }
106
107 reloadTimeout = setTimeout(() => {
104 - reload?.value && reload?.value(false)
108 + reload?.value?.(false)
109 }, 1000)
110 }
111 }
frontend/src/components/cards/CardWrapper.vue
+2 -2
@@ -1,11 +1,11 @@
1 <template>
2 <div class="card-wrapper">
3 <n-spin :show="showSpin" class="h-full">
4 - <slot :expand="expand" :reload="reload" :isExpand="isExpand"></slot>
4 + <slot :expand="expand" :reload="reload" :is-expand="isExpand" />
5 </n-spin>
6 <n-modal v-model:show="showModal" display-directive="show" class="card-modal-wrapper">
7 <n-spin :show="showSpin">
8 - <slot :expand="expand" :reload="reload" :isExpand="isExpand"></slot>
8 + <slot :expand="expand" :reload="reload" :is-expand="isExpand" />
9 </n-spin>
10 </n-modal>
11 </div>
frontend/src/components/cloudSecurityAssessment/AvailableReportsItem.vue
+6 -5
@@ -6,13 +6,13 @@
6 </div>
7 <div class="footer-box flex justify-end">
8 <n-popconfirm
9 - @positive-click="deleteScoutSuiteReport()"
9 v-model:show="showConfirm"
10 trigger="manual"
11 + @positive-click="deleteScoutSuiteReport()"
12 @clickoutside="showConfirm = false"
13 >
14 <template #trigger>
15 - <div @click.stop="showConfirm = true" class="delete-btn">delete</div>
15 + <div class="delete-btn" @click.stop="showConfirm = true">delete</div>
16 </template>
17 Are you sure you want to delete the report?
18 </n-popconfirm>
@@ -24,15 +24,16 @@
24 <script setup lang="ts">
25 import type { ScoutSuiteReport } from "@/types/cloudSecurityAssessment.d"
26 import Api from "@/api"
27 -import { ref } from "vue"
28 -import { useMessage, NPopconfirm, NSpin } from "naive-ui"
27 import { getBaseUrl } from "@/utils"
28 +import { NPopconfirm, NSpin, useMessage } from "naive-ui"
29 +import { ref } from "vue"
30 +
31 +const { report } = defineProps<{ report: ScoutSuiteReport }>()
32
33 const emit = defineEmits<{
34 (e: "deleted"): void
35 }>()
36
35 -const { report } = defineProps<{ report: ScoutSuiteReport }>()
37 const message = useMessage()
38 const canceling = ref(false)
39 const showConfirm = ref(false)
frontend/src/components/cloudSecurityAssessment/AvailableReportsList.vue
+5 -4
@@ -42,7 +42,7 @@
42 </div>
43 </template>
44 <template v-else>
45 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
45 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
46 </template>
47 </n-spin>
48
@@ -59,14 +59,15 @@
59 </n-modal>
60 </div>
61 </template>
62 +
63 <script setup lang="ts">
63 -import { computed, onBeforeMount, ref, watch } from "vue"
64 -import { NSpin, NEmpty, NPopover, NButton, NModal, useMessage } from "naive-ui"
64 import type { ScoutSuiteReport } from "@/types/cloudSecurityAssessment.d"
65 +import Api from "@/api"
66 import Icon from "@/components/common/Icon.vue"
67 +import { NButton, NEmpty, NModal, NPopover, NSpin, useMessage } from "naive-ui"
68 +import { computed, onBeforeMount, ref, watch } from "vue"
69 import AvailableReportsItem from "./AvailableReportsItem.vue"
70 import CreationReportForm from "./CreationReportForm.vue"
69 -import Api from "@/api"
71
72 const InfoIcon = "carbon:information"
73 const NewReportIcon = "carbon:fetch-upload-cloud"
frontend/src/components/cloudSecurityAssessment/CloudSecurityAssessmentButton.vue
+5 -3
@@ -1,14 +1,16 @@
1 <template>
2 <n-button :size="size" :type="type" @click="gotoPage()">
3 - <template #icon><Icon :name="CloudIcon"></Icon></template>
3 + <template #icon>
4 + <Icon :name="CloudIcon"></Icon>
5 + </template>
6 Cloud Security Assessment
7 </n-button>
8 </template>
9
10 <script setup lang="ts">
9 -import { NButton } from "naive-ui"
10 -import Icon from "@/components/common/Icon.vue"
11 import type { Size, Type } from "naive-ui/es/button/src/interface"
12 +import Icon from "@/components/common/Icon.vue"
13 +import { NButton } from "naive-ui"
14 import { useRouter } from "vue-router"
15
16 const { type, size } = defineProps<{
frontend/src/components/cloudSecurityAssessment/CreationReportForm.vue
+18 -18
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="creation-report-form">
3 - <n-form :model="baseForm" :rules="rules" ref="baseFormRef">
3 + <n-form ref="baseFormRef" :model="baseForm" :rules="rules">
4 <div class="flex flex-col gap-2">
5 <div class="flex gap-4 items-start">
6 <n-form-item label="Type" path="report_type" class="w-32">
@@ -43,12 +43,12 @@
43 />
44
45 <div class="flex justify-between gap-4 mt-8">
46 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
46 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
47 <n-button
48 type="primary"
49 :disabled="!isValid"
50 - @click="validate(() => submit())"
50 :loading="submitting"
51 + @click="validate(() => submit())"
52 >
53 Submit
54 </n-button>
@@ -59,21 +59,8 @@
59 </template>
60
61 <script setup lang="ts">
62 -import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
62 +import type { ApiCommonResponse, ApiError } from "@/types/common.d"
63 import Api from "@/api"
64 -import {
65 - useMessage,
66 - NForm,
67 - NFormItem,
68 - NInput,
69 - NButton,
70 - NSpin,
71 - NSelect,
72 - type FormValidationError,
73 - type FormInst,
74 - type FormRules,
75 - type MessageReactive
76 -} from "naive-ui"
64 import {
65 type ScoutSuiteAwsReportPayload,
66 type ScoutSuiteAzureReportPayload,
@@ -81,10 +68,23 @@ import {
68 type ScoutSuiteReportPayload,
69 ScoutSuiteReportType
70 } from "@/types/cloudSecurityAssessment.d"
71 +import {
72 + type FormInst,
73 + type FormRules,
74 + type FormValidationError,
75 + type MessageReactive,
76 + NButton,
77 + NForm,
78 + NFormItem,
79 + NInput,
80 + NSelect,
81 + NSpin,
82 + useMessage
83 +} from "naive-ui"
84 +import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
85 import AwsTypeForm from "./FormTypes/AwsTypeForm.vue"
86 import AzureTypeForm from "./FormTypes/AzureTypeForm.vue"
87 import GcpTypeForm from "./FormTypes/GcpTypeForm.vue"
87 -import type { ApiError, ApiCommonResponse } from "@/types/common.d"
88
89 type BaseFormPayload = Omit<ScoutSuiteReportPayload, "report_type"> & { report_type: ScoutSuiteReportType | null }
90 type TypeFormPayload = Partial<ScoutSuiteAwsReportPayload | ScoutSuiteAzureReportPayload | ScoutSuiteGcpReportPayload>
frontend/src/components/cloudSecurityAssessment/FormTypes/AwsTypeForm.vue
+3 -3
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form :model="form" :rules="rules" ref="formRef">
2 + <n-form ref="formRef" :model="form" :rules="rules">
3 <div class="flex flex-col gap-2">
4 <n-form-item label="Access Key ID" path="access_key_id">
5 <n-input v-model:value.trim="form.access_key_id" placeholder="Please insert Access Key ID" clearable />
@@ -18,9 +18,9 @@
18 </template>
19
20 <script setup lang="ts">
21 -import { computed, onMounted, ref, watch } from "vue"
22 -import { NForm, NFormItem, NInput, type FormRules, type FormInst } from "naive-ui"
21 import type { ScoutSuiteAwsReportPayload } from "@/types/cloudSecurityAssessment.d"
22 +import { type FormInst, type FormRules, NForm, NFormItem, NInput } from "naive-ui"
23 +import { computed, onMounted, ref, watch } from "vue"
24
25 const emit = defineEmits<{
26 (e: "mounted", value: FormInst): void
frontend/src/components/cloudSecurityAssessment/FormTypes/AzureTypeForm.vue
+3 -3
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form :model="form" :rules="rules" ref="formRef">
2 + <n-form ref="formRef" :model="form" :rules="rules">
3 <div class="flex flex-col gap-2">
4 <n-form-item label="Username" path="username">
5 <n-input v-model:value.trim="form.username" placeholder="Please insert Username" clearable />
@@ -23,9 +23,9 @@
23 </template>
24
25 <script setup lang="ts">
26 -import { computed, onMounted, ref, watch } from "vue"
27 -import { NForm, NFormItem, NInput, type FormRules, type FormInst } from "naive-ui"
26 import type { ScoutSuiteAzureReportPayload } from "@/types/cloudSecurityAssessment.d"
27 +import { type FormInst, type FormRules, NForm, NFormItem, NInput } from "naive-ui"
28 +import { computed, onMounted, ref, watch } from "vue"
29
30 const emit = defineEmits<{
31 (e: "mounted", value: FormInst): void
frontend/src/components/cloudSecurityAssessment/FormTypes/GcpTypeForm.vue
+9 -9
@@ -1,8 +1,8 @@
1 <template>
2 - <n-form :model="form" :rules="rules" ref="formRef">
2 + <n-form ref="formRef" :model="form" :rules="rules">
3 <div class="flex flex-col gap-2">
4 <n-form-item label="Configuration file" path="file">
5 - <n-upload :max="1" accept="application/json, .json, .JSON" v-model:file-list="fileList">
5 + <n-upload v-model:file-list="fileList" :max="1" accept="application/json, .json, .JSON">
6 <n-upload-dragger>
7 <div>
8 <Icon :name="UploadIcon" :size="28" :depth="3"></Icon>
@@ -17,19 +17,19 @@
17 </template>
18
19 <script setup lang="ts">
20 -import { computed, onMounted, ref, watch } from "vue"
20 +import type { ScoutSuiteGcpReportPayload } from "@/types/cloudSecurityAssessment.d"
21 +import Icon from "@/components/common/Icon.vue"
22 import {
23 + type FormInst,
24 + type FormItemRule,
25 + type FormRules,
26 NForm,
27 NFormItem,
28 NUpload,
29 NUploadDragger,
26 - type FormRules,
27 - type FormInst,
28 - type UploadFileInfo,
29 - type FormItemRule
30 + type UploadFileInfo
31 } from "naive-ui"
31 -import Icon from "@/components/common/Icon.vue"
32 -import type { ScoutSuiteGcpReportPayload } from "@/types/cloudSecurityAssessment.d"
32 +import { computed, onMounted, ref, watch } from "vue"
33
34 const emit = defineEmits<{
35 (e: "mounted", value: FormInst): void
frontend/src/components/common/CardStats.vue
+5 -3
@@ -8,9 +8,11 @@
8 <div class="info flex flex-col grow overflow-hidden">
9 <div class="title flex items-center gap-2">
10 {{ title }}
11 - <Icon :name="ArrowRightIcon" v-if="hovered" :size="12"></Icon>
11 + <Icon v-if="hovered" :name="ArrowRightIcon" :size="12"></Icon>
12 + </div>
13 + <div v-if="value" class="value mt-1">
14 + {{ value }}
15 </div>
13 - <div class="value mt-1" v-if="value">{{ value }}</div>
16 </div>
17 </div>
18 </div>
@@ -18,9 +20,9 @@
20 </template>
21
22 <script setup lang="ts">
23 +import Icon from "@/components/common/Icon.vue"
24 import { NCard } from "naive-ui"
25 import { toRefs } from "vue"
23 -import Icon from "@/components/common/Icon.vue"
26
27 const props = defineProps<{
28 title: string
frontend/src/components/common/CardStatsDouble.vue
+14 -6
@@ -4,7 +4,7 @@
4 <div class="card-header flex gap-4 items-center justify-between">
5 <div class="title flex items-center gap-2">
6 {{ title }}
7 - <Icon :name="ArrowRightIcon" v-if="hovered" :size="12"></Icon>
7 + <Icon v-if="hovered" :name="ArrowRightIcon" :size="12"></Icon>
8 </div>
9 <div class="icon">
10 <slot name="icon"></slot>
@@ -12,12 +12,20 @@
12 </div>
13 <div class="flex card-content">
14 <div class="flex flex-col basis-1/2 value-box" :class="firstStatus">
15 - <div class="value">{{ value }}</div>
16 - <div class="label" v-if="firstLabel">{{ firstLabel }}</div>
15 + <div class="value">
16 + {{ value }}
17 + </div>
18 + <div v-if="firstLabel" class="label">
19 + {{ firstLabel }}
20 + </div>
21 </div>
22 <div class="flex flex-col basis-1/2 value-box" :class="secondStatus">
19 - <div class="value">{{ subValue }}</div>
20 - <div class="label" v-if="secondLabel">{{ secondLabel }}</div>
23 + <div class="value">
24 + {{ subValue }}
25 + </div>
26 + <div v-if="secondLabel" class="label">
27 + {{ secondLabel }}
28 + </div>
29 </div>
30 </div>
31 </div>
@@ -25,9 +33,9 @@
33 </template>
34
35 <script setup lang="ts">
36 +import Icon from "@/components/common/Icon.vue"
37 import { NCard } from "naive-ui"
38 import { toRefs } from "vue"
30 -import Icon from "@/components/common/Icon.vue"
39
40 const props = defineProps<{
41 title: string
frontend/src/components/common/CardStatsIcon.vue
+5 -5
@@ -1,17 +1,17 @@
1 <template>
2 <div class="icon" :class="{ boxed }" :style="`--size:${boxSize}px`">
3 - <div class="bg" v-if="boxed"></div>
4 - <Icon :size="iconFinalSize" v-if="$slots.default">
3 + <div v-if="boxed" class="bg"></div>
4 + <Icon v-if="$slots.default" :size="iconFinalSize">
5 <slot></slot>
6 </Icon>
7 - <Icon :size="iconFinalSize" :name="iconName" v-else></Icon>
7 + <Icon v-else :size="iconFinalSize" :name="iconName"></Icon>
8 </div>
9 </template>
10
11 <script setup lang="ts">
12 -import { toRefs, computed } from "vue"
13 -import { useThemeStore } from "@/stores/theme"
12 import Icon from "@/components/common/Icon.vue"
13 +import { useThemeStore } from "@/stores/theme"
14 +import { computed, toRefs } from "vue"
15
16 const props = withDefaults(
17 defineProps<{
frontend/src/components/common/CodeSource.vue
+4 -4
@@ -1,7 +1,7 @@
1 <template>
2 <div class="flex flex-col gap-2">
3 - <n-card content-class="bg-secondary-color !p-0" class="overflow-hidden" v-if="!showSource">
4 - <div class="scrollbar-styled overflow-hidden code-bg-transparent" v-shiki="{ lang, decode }">
3 + <n-card v-if="!showSource" content-class="bg-secondary-color !p-0" class="overflow-hidden">
4 + <div v-shiki="{ lang, decode }" class="scrollbar-styled overflow-hidden code-bg-transparent">
5 <pre v-html="source"></pre>
6 </div>
7 </n-card>
@@ -26,9 +26,9 @@
26 </template>
27
28 <script setup lang="ts">
29 -import { computed, ref } from "vue"
30 -import { NCard, NInput, NButton } from "naive-ui"
29 import vShiki from "@/directives/v-shiki"
30 +import { NButton, NCard, NInput } from "naive-ui"
31 +import { computed, ref } from "vue"
32
33 const { code, lang } = defineProps<{
34 code: string | object | number
frontend/src/components/common/ExpandableText.vue
+3 -3
@@ -13,8 +13,8 @@
13 </template>
14
15 <div
16 - class="expandable-text-popover-container scrollbar-styled"
16 v-shiki="{ fallbackLang: 'json', decode: true }"
17 + class="expandable-text-popover-container scrollbar-styled"
18 >
19 <pre> {{ text }} </pre>
20 </div>
@@ -23,9 +23,9 @@
23
24 <script setup lang="ts">
25 import vShiki from "@/directives/v-shiki"
26 -import { toRefs } from "vue"
27 -import { NPopover } from "naive-ui"
26 import _truncate from "lodash/truncate"
27 +import { NPopover } from "naive-ui"
28 +import { toRefs } from "vue"
29
30 const props = defineProps<{
31 text: string
frontend/src/components/common/FileDrop.vue
+4 -4
@@ -1,8 +1,8 @@
1 <template>
2 <form class="file-drop">
3 <label>
4 - <slot></slot>
5 - <input type="file" :multiple="multiple" @change="onChange" :accept="accept" />
4 + <slot />
5 + <input type="file" :multiple="multiple" :accept @change="onChange" />
6 </label>
7 </form>
8 </template>
@@ -17,12 +17,12 @@ const props = withDefaults(
17 }>(),
18 { accept: ".jpg,.jpeg,.png,.webp", multiple: false }
19 )
20 -const { accept, multiple } = toRefs(props)
21 -
20 const emit = defineEmits<{
21 (e: "change-file", value: FileList | File | null): void
22 }>()
23
24 +const { accept, multiple } = toRefs(props)
25 +
26 function onChange(event: Event) {
27 const input = event.target as HTMLInputElement
28 if (multiple.value) {
frontend/src/components/common/Icon.vue
+3 -3
@@ -4,14 +4,14 @@
4 <slot />
5 </template>
6 <template v-else>
7 - <Icon v-if="icon" :icon="icon" :width="size" :height="size" />
7 + <Icon v-if="icon" :icon :width="size" :height="size" />
8 </template>
9 </component>
10 </template>
11
12 <script setup lang="ts">
13 -import { NIconWrapper, NIcon } from "naive-ui"
14 -import { Icon, loadIcon, type IconifyIcon } from "@iconify/vue"
13 +import { Icon, type IconifyIcon, loadIcon } from "@iconify/vue"
14 +import { NIcon, NIconWrapper } from "naive-ui"
15 import { computed, ref, watchEffect } from "vue"
16
17 const props = defineProps<{
frontend/src/components/common/ImageCropper.vue
+15 -14
@@ -1,33 +1,33 @@
1 <template>
2 <div class="image-cropper">
3 - <slot :openCropper="openCropper"></slot>
3 + <slot :open-cropper="openCropper" />
4
5 <n-modal v-model:show="showCropper">
6 <n-card class="image-cropper-modal flex flex-col" content-class="!p-5">
7 <div class="aspect-square">
8 - <div class="upload-box" v-if="!img">
9 - <n-upload accept="image/*" :show-file-list="false" @change="setImage" v-show="!img">
8 + <div v-if="!img" class="upload-box">
9 + <n-upload v-show="!img" accept="image/*" :show-file-list="false" @change="setImage">
10 <n-upload-dragger>
11 <div>{{ placeholder }}</div>
12 </n-upload-dragger>
13 </n-upload>
14 </div>
15 - <div class="crop-box" v-if="img">
15 + <div v-if="img" class="crop-box">
16 <Cropper
17 - class="cropper aspect-square"
17 ref="cropper"
18 + class="cropper aspect-square"
19 :src="img"
20 :stencil-size="stencilSize"
21 :stencil-props="stencilProps"
22 :resize-image="resizeImage"
23 image-restriction="stencil"
24 :stencil-component="stencil"
25 - ></Cropper>
25 + />
26 </div>
27 </div>
28 <div class="flex justify-end gap-4 mt-4">
29 - <n-button @click="closeCropper()" secondary>Close</n-button>
30 - <n-button @click="emitCrop()" type="primary" v-if="img">Save</n-button>
29 + <n-button secondary @click="closeCropper()">Close</n-button>
30 + <n-button v-if="img" type="primary" @click="emitCrop()">Save</n-button>
31 </div>
32 </n-card>
33 </n-modal>
@@ -35,17 +35,13 @@
35 </template>
36
37 <script lang="ts" setup>
38 -import { NButton, NCard, NUpload, NUploadDragger, NModal, type UploadSettledFileInfo } from "naive-ui"
38 +import { NButton, NCard, NModal, NUpload, NUploadDragger, type UploadSettledFileInfo } from "naive-ui"
39 import { computed, ref, toRefs } from "vue"
40 -import { Cropper, CircleStencil, RectangleStencil, type CropperResult } from "vue-advanced-cropper"
40 +import { CircleStencil, Cropper, type CropperResult, RectangleStencil } from "vue-advanced-cropper"
41 import "vue-advanced-cropper/dist/style.css"
42
43 export type ImageCropperResult = CropperResult
44
45 -const emit = defineEmits<{
46 - (e: "crop", value: ImageCropperResult): void
47 -}>()
48 -
45 const props = withDefaults(
46 defineProps<{
47 placeholder?: string
@@ -53,6 +49,11 @@ const props = withDefaults(
49 }>(),
50 { placeholder: "Select an image", shape: "square" }
51 )
52 +
53 +const emit = defineEmits<{
54 + (e: "crop", value: ImageCropperResult): void
55 +}>()
56 +
57 const { placeholder, shape } = toRefs(props)
58
59 const stencil = computed(() => (shape.value === "circle" ? CircleStencil : RectangleStencil))
frontend/src/components/common/ImageLoader.vue
+7 -8
@@ -1,19 +1,19 @@
1 <template>
2 <img
3 - :src="fallback"
4 - @load="imageFallback"
3 v-if="isImageError && fallback"
4 + :src="fallback"
5 class="image-fallback"
6 draggable="false"
7 :class="imageClass"
8 + @load="imageFallback"
9 />
10 <img
11 + v-if="!isImageError"
12 :src="mainSrc"
13 draggable="false"
13 - @load="imageLoading"
14 - v-if="!isImageError"
14 class="image-loading"
15 :class="imageClass"
16 + @load="imageLoading"
17 />
18 </template>
19
@@ -27,8 +27,6 @@ export interface ImageLoadEvent extends Event {
27 path?: HTMLElement[]
28 }
29
30 -const emit = defineEmits(["image-error", "image-loading", "image-loaded", "image-fallback"])
31 -
30 const props = defineProps({
31 src: {
32 type: String,
@@ -46,8 +44,11 @@ const props = defineProps({
44 }
45 })
46
47 +const emit = defineEmits(["image-error", "image-loading", "image-loaded", "image-fallback"])
48 +
49 const isImageLoaded = ref(false)
50 const isImageError = ref(false)
51 +const mainSrc = ref(props.loading)
52
53 function imageLoading(e: ImageLoadEvent) {
54 const elPath: HTMLElement | undefined = e.path && e.path[0]
@@ -76,8 +77,6 @@ function imageError() {
77 emit("image-error")
78 }
79
79 -const mainSrc = ref(props.loading)
80 -
80 function createLoader() {
81 const img = new Image()
82 img.onload = imageLoaded
frontend/src/components/common/KVCard.vue
+6 -2
@@ -1,7 +1,11 @@
1 <template>
2 <div class="kv-card" :class="[`kv-color-${color}`, `kv-size-${size}`]">
3 - <div class="key" v-if="$slots.key"><slot name="key"></slot></div>
4 - <div class="value" v-if="$slots.value"><slot name="value"></slot></div>
3 + <div v-if="$slots.key" class="key">
4 + <slot name="key"></slot>
5 + </div>
6 + <div v-if="$slots.value" class="value">
7 + <slot name="value"></slot>
8 + </div>
9 </div>
10 </template>
11
frontend/src/components/common/LayoutSettings.vue
+20 -20
@@ -1,15 +1,15 @@
1 <template>
2 <div class="layout-settings flex items-center justify-center shadow-xl" :class="{ open }">
3 <Transition mode="out-in" name="anim">
4 - <div class="open-btn flex items-center justify-center" @click="open = true" v-if="!open" key="btn">
5 - <Icon :size="24" :name="SettingsIcon"></Icon>
4 + <div v-if="!open" key="btn" class="open-btn flex items-center justify-center" @click="open = true">
5 + <Icon :size="24" :name="SettingsIcon" />
6 </div>
7
8 - <div class="ls-form flex flex-col" v-else key="form">
8 + <div v-else key="form" class="ls-form flex flex-col">
9 <div class="ls-header flex items-center justify-between">
10 <div class="ls-title">Layout settings</div>
11 <div class="ls-icon flex items-center">
12 - <Icon @click="open = false" :size="20" :name="CloseIcon"></Icon>
12 + <Icon :size="20" :name="CloseIcon" @click="open = false" />
13 </div>
14 </div>
15 <n-scrollbar class="ls-main">
@@ -17,21 +17,21 @@
17 <div class="ls-label">Primary color</div>
18 <div class="color-picker-box">
19 <n-color-picker
20 + v-if="theme === ThemeEnum.Dark"
21 v-model:value="darkColor"
22 :modes="['hex']"
23 :show-alpha="false"
23 - v-if="theme === ThemeEnum.Dark"
24 />
25 - <n-color-picker v-model:value="lightColor" :modes="['hex']" :show-alpha="false" v-else />
25 + <n-color-picker v-else v-model:value="lightColor" :modes="['hex']" :show-alpha="false" />
26 </div>
27 <div class="palette flex justify-around mt-3">
28 - <n-button text v-for="color of palette" :key="color.light" @click="setPrimary(color)">
28 + <n-button v-for="color of palette" :key="color.light" text @click="setPrimary(color)">
29 <template #icon>
30 <Icon
31 :color="theme === ThemeEnum.Dark ? color.dark : color.light"
32 :size="24"
33 :name="ColorIcon"
34 - ></Icon>
34 + />
35 </template>
36 </n-button>
37 </div>
@@ -43,12 +43,12 @@
43 <div class="basis-1/2">
44 <n-button
45 class="!w-full"
46 - @click="theme = ThemeEnum.Light"
46 :type="theme === ThemeEnum.Light ? 'primary' : 'default'"
47 + @click="theme = ThemeEnum.Light"
48 >
49 <template #icon>
50 - <Icon :name="LightIcon" v-if="theme === ThemeEnum.Light"></Icon>
51 - <Icon :name="LightOutlineIcon" v-else></Icon>
50 + <Icon v-if="theme === ThemeEnum.Light" :name="LightIcon" />
51 + <Icon v-else :name="LightOutlineIcon" />
52 </template>
53 Light
54 </n-button>
@@ -56,12 +56,12 @@
56 <div class="basis-1/2">
57 <n-button
58 class="!w-full"
59 - @click="theme = ThemeEnum.Dark"
59 :type="theme === ThemeEnum.Dark ? 'primary' : 'default'"
60 + @click="theme = ThemeEnum.Dark"
61 >
62 <template #icon>
63 - <Icon :name="DarkIcon" v-if="theme === ThemeEnum.Dark"></Icon>
64 - <Icon :name="DarkOutlineIcon" v-else></Icon>
63 + <Icon v-if="theme === ThemeEnum.Dark" :name="DarkIcon" />
64 + <Icon v-else :name="DarkOutlineIcon" />
65 </template>
66 Dark
67 </n-button>
@@ -79,9 +79,9 @@
79 <!--
80 <n-button
81 class="!w-full"
82 - @click="layout = Layout.VerticalNav"
82 :type="layout === Layout.VerticalNav ? 'primary' : 'default'"
83 :disabled="isMobileView"
84 + @click="layout = Layout.VerticalNav"
85 >
86 Vertical
87 </n-button>
@@ -90,9 +90,9 @@
90 <div class="basis-1/2">
91 <n-button
92 class="!w-full"
93 - @click="layout = Layout.HorizontalNav"
93 :type="layout === Layout.HorizontalNav ? 'primary' : 'default'"
94 :disabled="isMobileView"
95 + @click="layout = Layout.HorizontalNav"
96 >
97 Horizontal
98 </n-button>
@@ -136,7 +136,7 @@
136 <div class="ls-section ls-transition-selection">
137 <div class="ls-label">Router transition</div>
138 <div class="ls-input flex justify-between">
139 - <n-select v-model:value="routerTransition" :options="transitionOptions"></n-select>
139 + <n-select v-model:value="routerTransition" :options="transitionOptions" />
140 </div>
141 </div>
142 <div class="ls-section ls-reset-selection items-center">
@@ -159,12 +159,12 @@
159 </template>
160
161 <script setup lang="ts">
162 -import { computed, ref } from "vue"
163 -import { NColorPicker, NButton, NSelect, useOsTheme, NScrollbar, NSwitch } from "naive-ui"
164 -import { useThemeStore } from "@/stores/theme"
162 import Icon from "@/components/common/Icon.vue"
163 +import { useThemeStore } from "@/stores/theme"
164 import { Layout, RouterTransition, ThemeEnum } from "@/types/theme.d"
165 import { useWindowSize } from "@vueuse/core"
166 +import { NButton, NColorPicker, NScrollbar, NSelect, NSwitch, useOsTheme } from "naive-ui"
167 +import { computed, ref } from "vue"
168
169 interface ColorPalette {
170 light: string
frontend/src/components/common/ListPercentage.vue
+13 -7
@@ -1,22 +1,28 @@
1 <template>
2 <div class="flex flex-col gap-2">
3 - <n-empty description="No items found" class="justify-center h-48" v-if="!list.length" />
3 + <n-empty v-if="!list.length" description="No items found" class="justify-center h-48" />
4
5 <div class="flex gap-4 justify-between items-center list-header font-mono text-secondary-color text-sm">
6 - <div class="basis-2/3 truncate borde">{{ labelKey }}</div>
7 - <div class="grow">{{ percentageKey }}</div>
6 + <div class="basis-2/3 truncate borde">
7 + {{ labelKey }}
8 + </div>
9 + <div class="grow">
10 + {{ percentageKey }}
11 + </div>
12 </div>
13 <div
10 - class="flex gap-4 justify-between items-center"
14 v-for="item of list"
15 :key="item[labelKey as keyof typeof item]"
16 + class="flex gap-4 justify-between items-center"
17 >
14 - <div class="basis-2/3 truncate font-mono">{{ item[labelKey as keyof typeof item] }}</div>
18 + <div class="basis-2/3 truncate font-mono">
19 + {{ item[labelKey as keyof typeof item] }}
20 + </div>
21 <div class="grow">
22 <n-progress
23 type="line"
24 :percentage="parseInt(item[percentageKey as keyof typeof item], 10)"
19 - :indicator-placement="'inside'"
25 + indicator-placement="inside"
26 :indicator-text-color="style['bg-color']"
27 :color="style['fg-color']"
28 :rail-color="style['divider-020-color']"
@@ -28,8 +34,8 @@
34 </template>
35
36 <script setup lang="ts">
31 -import { useThemeStore } from "@/stores/theme"
37 import type { SafeAny } from "@/types/common.d"
38 +import { useThemeStore } from "@/stores/theme"
39 import { NEmpty, NProgress } from "naive-ui"
40 import { computed } from "vue"
41
frontend/src/components/common/LocaleSelect.vue
+2 -2
@@ -5,10 +5,10 @@
5 </template>
6
7 <script lang="ts" setup>
8 -import { NSelect, type SelectOption } from "naive-ui"
8 import Icon from "@/components/common/Icon.vue"
10 -import { computed, h, type VNodeChild } from "vue"
9 import { useStoreI18n } from "@/composables/useStoreI18n"
10 +import { NSelect, type SelectOption } from "naive-ui"
11 +import { computed, h, type VNodeChild } from "vue"
12
13 const { getAvailableLocales, getLocale, setLocale, t } = useStoreI18n()
14
frontend/src/components/common/Markdown.vue
+12 -11
@@ -17,26 +17,27 @@
17 </template>
18
19 <script setup lang="ts">
20 -import { toRefs } from "vue"
20 +import type { HighlighterGeneric } from "shiki/core"
21 +import { codeThemes, getHighlighter } from "@/utils/highlighter"
22 import { VueMarkdownIt } from "@f3ve/vue-markdown-it"
22 -import { getHighlighter, codeThemes } from "@/utils/highlighter"
23 import { fromHighlighter } from "@shikijs/markdown-it/core"
24 -import type { HighlighterGeneric } from "shiki/core"
24 +import { toRefs } from "vue"
25 import "@/assets/scss/vue-md-it-override.scss"
26
27 -const highlighter: HighlighterGeneric<string, string> = (await getHighlighter()) as unknown as HighlighterGeneric<
28 - string,
29 - string
30 ->
27 +const props = defineProps<{
28 + source: string
29 + codeBgTransparent?: boolean
30 +}>()
31
32 const emit = defineEmits<{
33 (e: "click", value: PointerEvent): void
34 }>()
35
36 -const props = defineProps<{
37 - source: string
38 - codeBgTransparent?: boolean
39 -}>()
36 +const highlighter: HighlighterGeneric<string, string> = (await getHighlighter()) as unknown as HighlighterGeneric<
37 + string,
38 + string
39 +>
40 +
41 const { source, codeBgTransparent } = toRefs(props)
42 </script>
43
frontend/src/components/common/Notifications/List.vue
+23 -16
@@ -1,51 +1,58 @@
1 <template>
2 <n-scrollbar class="notifications-list">
3 <div
4 - class="item flex"
4 v-for="item of listSanitized"
5 :key="item.id"
7 - @click="item.action ? preformAction(item.id, item.action) : () => {}"
6 + class="item flex"
7 :class="[{ pointer: !!item.action }, item.type]"
8 + @click="item.action ? preformAction(item.id, item.action) : () => {}"
9 >
10 <div class="icon-box">
11 - <Icon :name="AlertIcon" :size="21" v-if="item.category === 'alert'"></Icon>
12 - <n-tooltip trigger="hover" class="!p-0" content-class="!p-0" placement="right" v-if="!item.read">
11 + <Icon v-if="item.category === 'alert'" :name="AlertIcon" :size="21"></Icon>
12 + <n-tooltip v-if="!item.read" trigger="hover" class="!p-0" content-class="!p-0" placement="right">
13 <template #trigger>
14 - <div class="read-badge" @click.stop="setRead(item.id)"></div>
14 + <div class="read-badge" @click.stop="setRead(item.id)" />
15 </template>
16 <div class="px-3 py-2">Set as read</div>
17 </n-tooltip>
18 </div>
19 <div class="content grow">
20 - <div class="title">{{ item.title }}</div>
21 - <div class="description">{{ item.description }}</div>
20 + <div class="title">
21 + {{ item.title }}
22 + </div>
23 + <div class="description">
24 + {{ item.description }}
25 + </div>
26 <div class="footer flex justify-between items-center">
23 - <div class="date">{{ formatDatetime(item.date) }}</div>
24 - <div class="action-text" v-if="!!item.action">{{ item.actionTitle || "Details" }}</div>
27 + <div class="date">
28 + {{ formatDatetime(item.date) }}
29 + </div>
30 + <div v-if="!!item.action" class="action-text">
31 + {{ item.actionTitle || "Details" }}
32 + </div>
33 </div>
34 </div>
35 <div class="delete-btn" @click.stop="deleteOne(item.id)">
28 - <Icon :name="DeleteIcon" :size="18"></Icon>
36 + <Icon :name="DeleteIcon" :size="18" />
37 </div>
38 </div>
31 - <slot name="last"></slot>
39 + <slot name="last" />
40 <n-empty v-if="!list.length" description="There is no notification" class="justify-center h-48" />
41 </n-scrollbar>
42 </template>
43
44 <script lang="ts" setup>
37 -import { NScrollbar, NTooltip, NEmpty } from "naive-ui"
45 import Icon from "@/components/common/Icon.vue"
46 import { useNotifications } from "@/composables/useNotifications"
40 -import { computed } from "vue"
47 import _take from "lodash/take"
42 -
43 -const DeleteIcon = "carbon:close"
44 -const AlertIcon = "mdi:alert-outline"
48 +import { NEmpty, NScrollbar, NTooltip } from "naive-ui"
49 +import { computed } from "vue"
50
51 const props = defineProps<{
52 maxItems?: number
53 }>()
54 +const DeleteIcon = "carbon:close"
55 +const AlertIcon = "mdi:alert-outline"
56
57 const list = useNotifications().list
58
frontend/src/components/common/Notifications/Toolbar.vue
+2 -2
@@ -3,7 +3,7 @@
3 class="notifications-toolbar flex"
4 :class="{ 'justify-between': hasNotifications, 'justify-end': !hasNotifications }"
5 >
6 - <n-button quaternary @click="deleteAll()" v-if="hasNotifications">Clear</n-button>
6 + <n-button v-if="hasNotifications" quaternary @click="deleteAll()">Clear</n-button>
7 <n-button strong secondary type="primary" :disabled="!hasUnread" @click="setAllRead()">
8 Mark all as read
9 </n-button>
@@ -11,8 +11,8 @@
11 </template>
12
13 <script lang="ts" setup>
14 -import { NButton } from "naive-ui"
14 import { useNotifications } from "@/composables/useNotifications"
15 +import { NButton } from "naive-ui"
16
17 const hasUnread = useNotifications().hasUnread
18 const hasNotifications = useNotifications().hasNotifications
frontend/src/components/common/PaginationIndeterminate.vue
+7 -7
@@ -1,8 +1,8 @@
1 <template>
2 <div class="pagination-indeterminate flex items-center gap-2">
3 <n-input-number
4 - size="small"
4 v-model:value="page"
5 + size="small"
6 :min="1"
7 button-placement="both"
8 class="page"
@@ -16,18 +16,18 @@
16 </template>
17 </n-input-number>
18 <n-select
19 - size="small"
19 v-if="showPageSizes"
20 v-model:value="pageSize"
21 + size="small"
22 :options="pageSizesOptions"
23 :show-checkmark="false"
24 class="page-sizes"
25 :disabled="disabled"
26 />
27 <n-select
28 - size="small"
28 v-if="showSort"
29 v-model:value="sort"
30 + size="small"
31 :options="sortOptions"
32 :show-checkmark="false"
33 class="sort"
@@ -37,19 +37,19 @@
37 </template>
38
39 <script setup lang="ts">
40 -import { computed, toRefs, watch } from "vue"
41 -import { NSelect, NInputNumber } from "naive-ui"
40 import Icon from "@/components/common/Icon.vue"
41 +import { NInputNumber, NSelect } from "naive-ui"
42 +import { computed, toRefs, watch } from "vue"
43
44 +const props = defineProps<{ showPageSizes?: boolean; showSort?: boolean; pageSizes?: number[]; disabled?: boolean }>()
45 const page = defineModel<number>("page", { default: 1 })
46 const pageSize = defineModel<number>("pageSize", { default: 10 })
47 const sort = defineModel<"asc" | "desc">("sort", { default: "desc" })
48
48 -const props = defineProps<{ showPageSizes?: boolean; showSort?: boolean; pageSizes?: number[]; disabled?: boolean }>()
49 const { pageSizes, disabled, showPageSizes, showSort } = toRefs(props)
50
51 const pageSizesOptions = computed(() =>
52 - (pageSizes.value || [10, 25, 50, 100]).map(o => ({ label: o + " / page", value: o }))
52 + (pageSizes.value || [10, 25, 50, 100]).map(o => ({ label: `${o} / page`, value: o }))
53 )
54
55 const sortOptions = [
frontend/src/components/common/Percentage.vue
+6 -6
@@ -10,7 +10,7 @@
10 direction
11 ]"
12 >
13 - <span class="progress" v-if="progress && progress === 'line'">
13 + <span v-if="progress && progress === 'line'" class="progress">
14 <n-progress
15 type="line"
16 :status="direction === 'up' ? 'success' : 'error'"
@@ -21,14 +21,14 @@
21 />
22 </span>
23 <span v-if="icon && icon === 'arrow'" class="flex items-center percentage-icon">
24 - <Icon v-if="direction === 'up'" :name="ChevronUp"></Icon>
25 - <Icon v-if="direction === 'down'" :name="ChevronDown"></Icon>
24 + <Icon v-if="direction === 'up'" :name="ChevronUp" />
25 + <Icon v-if="direction === 'down'" :name="ChevronDown" />
26 </span>
27 <span v-if="icon && icon === 'operator'" class="percentage-icon">
28 {{ direction === "up" ? "+" : "-" }}
29 </span>
30 <span>{{ value }}%</span>
31 - <span class="progress flex items-center" v-if="progress && progress === 'circle'">
31 + <span v-if="progress && progress === 'circle'" class="progress flex items-center">
32 <n-progress
33 type="circle"
34 :status="direction === 'up' ? 'success' : 'error'"
@@ -42,9 +42,9 @@
42 </template>
43
44 <script setup lang="ts">
45 -import { toRefs } from "vue"
46 -import { NProgress } from "naive-ui"
45 import Icon from "@/components/common/Icon.vue"
46 +import { NProgress } from "naive-ui"
47 +import { toRefs } from "vue"
48
49 export interface PercentageProps {
50 value: number
frontend/src/components/common/SearchDialog.vue
+29 -26
@@ -3,38 +3,42 @@
3 <n-card content-class="!p-0" class="!w-150" :bordered="false" size="huge" role="dialog" aria-modal="true">
4 <div class="search-box" @keydown.up="prevItem()" @keydown.down="nextItem()">
5 <div class="search-input flex items-center">
6 - <Icon :name="SearchIcon" :size="16"></Icon>
7 - <input placeholder="Search" v-model="search" class="grow" />
6 + <Icon :name="SearchIcon" :size="16" />
7 + <input v-model="search" placeholder="Search" class="grow" />
8 <n-text code>ESC</n-text>
9 - <Icon :name="CloseIcon" :size="20" @click="closeBox()" class="cursor-pointer"></Icon>
9 + <Icon :name="CloseIcon" :size="20" class="cursor-pointer" @click="closeBox()" />
10 </div>
11 <n-divider />
12 - <n-scrollbar class="!h-96" ref="scrollContent">
12 + <n-scrollbar ref="scrollContent" class="!h-96">
13 <div class="conten-wrap">
14 - <div class="group" v-for="group of filteredGroups" :key="group.name">
15 - <div class="group-title">{{ group.name }}</div>
14 + <div v-for="group of filteredGroups" :key="group.name" class="group">
15 + <div class="group-title">
16 + {{ group.name }}
17 + </div>
18 <div class="group-list">
19 <button
20 v-for="item of group.items"
19 - :key="item.key"
21 :id="item.key.toString()"
22 + :key="item.key"
23 class="item flex items-center"
24 :class="{ active: item.key === activeItem }"
25 @click="callAction(item.action)"
26 >
27 <div class="icon">
28 <n-avatar v-if="item.iconImage" round :size="28" :src="item.iconImage" />
27 - <Icon :name="item.iconName" v-if="item.iconName" :size="16"></Icon>
29 + <Icon v-if="item.iconName" :name="item.iconName" :size="16" />
30 </div>
31 <div class="title grow">
32 <Highlighter
31 - highlightClassName="highlight"
32 - :searchWords="keywords"
33 - :autoEscape="true"
34 - :textToHighlight="item.title"
33 + highlight-class-name="highlight"
34 + :search-words="keywords"
35 + :auto-escape="true"
36 + :text-to-highlight="item.title"
37 />
38 </div>
37 - <div class="label">{{ item.label }}</div>
39 + <div class="label">
40 + {{ item.label }}
41 + </div>
42 </button>
43 </div>
44 </div>
@@ -47,13 +51,13 @@
51 <div class="hint-bar flex items-center justify-center">
52 <div class="hint flex items-center justify-center gap-1">
53 <div class="icon">
50 - <Icon :name="ArrowEnterIcon" :size="12"></Icon>
54 + <Icon :name="ArrowEnterIcon" :size="12" />
55 </div>
56 <span class="label">to select</span>
57 </div>
58 <div class="hint flex items-center justify-center gap-1">
59 <div class="icon">
56 - <Icon :name="ArrowSortIcon" :size="12"></Icon>
60 + <Icon :name="ArrowSortIcon" :size="12" />
61 </div>
62 <span class="label">to navigate</span>
63 </div>
@@ -64,17 +68,17 @@
68 </template>
69
70 <script lang="ts" setup>
67 -import { computed, onMounted, ref } from "vue"
68 -import { NText, NModal, NCard, NDivider, NAvatar, NScrollbar, type ScrollbarInst } from "naive-ui"
69 -import { useMagicKeys, whenever } from "@vueuse/core"
70 -import Highlighter from "vue-highlight-words"
71 -import { useThemeSwitch } from "@/composables/useThemeSwitch"
71 +import Icon from "@/components/common/Icon.vue"
72 import { useFullscreenSwitch } from "@/composables/useFullscreenSwitch"
73 +import { useGoto } from "@/composables/useGoto"
74 import { useSearchDialog } from "@/composables/useSearchDialog"
74 -import { getOS } from "@/utils"
75 -import Icon from "@/components/common/Icon.vue"
75 +import { useThemeSwitch } from "@/composables/useThemeSwitch"
76 import { emitter } from "@/emitter"
77 -import { useGoto } from "@/composables/useGoto"
77 +import { getOS } from "@/utils"
78 +import { useMagicKeys, whenever } from "@vueuse/core"
79 +import { NAvatar, NCard, NDivider, NModal, NScrollbar, NText, type ScrollbarInst } from "naive-ui"
80 +import { computed, onMounted, ref } from "vue"
81 +import Highlighter from "vue-highlight-words"
82
83 interface GroupItem {
84 iconName: string | null
@@ -199,7 +203,7 @@ const filteredGroups = computed<Groups>(() => {
203 const newGroups: Groups = []
204 for (const group of groups.value) {
205 const items = group.items.filter(item => {
202 - if (keywords.value.filter(k => item.title.toLowerCase().indexOf(k.toLowerCase()) !== -1).length !== 0) {
206 + if (keywords.value.filter(k => item.title.toLowerCase().includes(k.toLowerCase())).length !== 0) {
207 return true
208 }
209 if (
@@ -220,7 +224,6 @@ const filteredGroups = computed<Groups>(() => {
224 return newGroups
225 })
226
223 -/*eslint @typescript-eslint/no-unused-vars: "off"*/
227 const filteredFlattenItems = computed<GroupItem[]>(() => {
228 const items = []
229
@@ -287,7 +290,7 @@ onMounted(() => {
290
291 const keys = useMagicKeys()
292 const ActiveCMD = isWindows ? keys["ctrl+k"] : keys["cmd+k"]
290 - const Enter = keys["enter"]
293 + const Enter = keys.enter
294
295 useSearchDialog().trigger(openBox)
296
frontend/src/components/common/SegmentedPage.vue
+29 -28
@@ -1,29 +1,29 @@
1 <template>
2 <n-split
3 + ref="splitPane"
4 direction="horizontal"
5 :default-size="splitDefault"
6 :min="0"
7 :max="1"
8 :resize-trigger-size="0"
9 :disabled="!enableResize || splitDisabled"
9 - ref="splitPane"
10 class="wrapper flex grow"
11 :class="[{ 'sidebar-open': sidebarOpen }, `sidebar-position-${sidebarPosition}`]"
12 >
13 <template #1>
14 - <div class="sidebar flex flex-col" ref="sidebar" v-if="sidebarAvailable">
15 - <div class="sidebar-header flex items-center" v-if="$slots['sidebar-header']">
16 - <slot name="sidebar-header"></slot>
14 + <div v-if="sidebarAvailable" ref="sidebar" class="sidebar flex flex-col">
15 + <div v-if="$slots['sidebar-header']" class="sidebar-header flex items-center">
16 + <slot name="sidebar-header" />
17 </div>
18 - <div class="sidebar-main grow" v-if="$slots['sidebar-content']">
18 + <div v-if="$slots['sidebar-content']" class="sidebar-main grow">
19 <n-scrollbar class="max-h-full">
20 <div class="sidebar-main-content" :style="sidebarContentStyle" :class="sidebarContentClass">
21 - <slot name="sidebar-content"></slot>
21 + <slot name="sidebar-content" />
22 </div>
23 </n-scrollbar>
24 </div>
25 - <div class="sidebar-footer flex items-center" v-if="$slots['sidebar-footer']">
26 - <slot name="sidebar-footer"></slot>
25 + <div v-if="$slots['sidebar-footer']" class="sidebar-footer flex items-center">
26 + <slot name="sidebar-footer" />
27 </div>
28 </div>
29 </template>
@@ -31,51 +31,51 @@
31 <template #resize-trigger>
32 <div class="split-trigger">
33 <div class="split-trigger-icon">
34 - <Icon :name="SplitIcon" :size="12"></Icon>
34 + <Icon :name="SplitIcon" :size="12" />
35 </div>
36 </div>
37 </template>
38
39 <template #2>
40 <div class="main flex-grow flex flex-col">
41 - <div class="main-toolbar flex items-center" v-if="$slots['main-toolbar']">
42 - <div class="menu-btn flex justify-center opacity-50" v-if="sidebarAvailable && !hideMenuBtn">
41 + <div v-if="$slots['main-toolbar']" class="main-toolbar flex items-center">
42 + <div v-if="sidebarAvailable && !hideMenuBtn" class="menu-btn flex justify-center opacity-50">
43 <n-button text @click="sidebarOpen = true">
44 - <Icon :size="24" :name="MenuIcon"></Icon>
44 + <Icon :size="24" :name="MenuIcon" />
45 </n-button>
46 </div>
47
48 <div class="grow">
49 - <slot name="main-toolbar"></slot>
49 + <slot name="main-toolbar" />
50 </div>
51 </div>
52 <div class="main-view grow" :class="{ 'no-container-query': disableContainerQuery }">
53 - <n-scrollbar class="max-h-full" v-if="useMainScroll" ref="mainScrollbar">
53 + <n-scrollbar v-if="useMainScroll" ref="mainScrollbar" class="max-h-full">
54 <div class="main-content" :style="mainContentStyle" :class="mainContentClass">
55 - <slot name="main-content"></slot>
55 + <slot name="main-content" />
56 </div>
57 </n-scrollbar>
58 - <div class="main-content" :style="mainContentStyle" :class="mainContentClass" v-else>
59 - <slot name="main-content"></slot>
58 + <div v-else class="main-content" :style="mainContentStyle" :class="mainContentClass">
59 + <slot name="main-content" />
60 </div>
61 </div>
62 - <div class="main-footer flex items-center" v-if="$slots['main-footer']">
62 + <div v-if="$slots['main-footer']" class="main-footer flex items-center">
63 <div class="wrap">
64 - <slot name="main-footer"></slot>
64 + <slot name="main-footer" />
65 </div>
66 </div>
67
68 - <div class="main-overlay" v-if="sidebarOpen"></div>
68 + <div v-if="sidebarOpen" class="main-overlay" />
69 </div>
70 </template>
71 </n-split>
72 </template>
73
74 <script setup lang="ts">
75 -import { computed, onMounted, ref, toRefs, useSlots, watch } from "vue"
76 -import { NScrollbar, NButton, NSplit } from "naive-ui"
77 -import { onClickOutside, useWindowSize } from "@vueuse/core"
75 import Icon from "@/components/common/Icon.vue"
76 +import { onClickOutside, useWindowSize } from "@vueuse/core"
77 +import { NButton, NScrollbar, NSplit } from "naive-ui"
78 +import { computed, onMounted, ref, toRefs, useSlots, watch } from "vue"
79
80 type SidebarPosition = "left" | "right"
81
@@ -85,11 +85,6 @@ export interface CtxSegmentedPage {
85 openSidebar: () => void
86 }
87
88 -const emit = defineEmits<{
89 - (e: "mounted", value: CtxSegmentedPage): void
90 - (e: "sidebar", value: boolean): void
91 -}>()
92 -
88 const props = withDefaults(
89 defineProps<{
90 sidebarPosition?: SidebarPosition
@@ -104,6 +99,12 @@ const props = withDefaults(
99 }>(),
100 { sidebarPosition: "left", useMainScroll: true }
101 )
102 +
103 +const emit = defineEmits<{
104 + (e: "mounted", value: CtxSegmentedPage): void
105 + (e: "sidebar", value: boolean): void
106 +}>()
107 +
108 const {
109 sidebarPosition,
110 hideMenuBtn,
frontend/src/components/common/TestScope.vue
+3 -1
@@ -1,9 +1,11 @@
1 <template>
2 <div>{{ msg }}</div>
3 </template>
4 +
5 <script setup lang="ts">
6 // THIS COMPONENT IS USED FOR VITEST TESTING PURPOSES
7 import { toRefs } from "vue"
7 -const props = defineProps(["msg"])
8 +
9 +const props = defineProps<{ msg: string }>()
10 const { msg } = toRefs(props)
11 </script>
frontend/src/components/connectors/ConfigForm/ConfigForm.vue
+25 -25
@@ -8,7 +8,7 @@
8 round
9 :size="60"
10 :src="`/images/connectors/${
11 - connector ? connector.connector_name.toLowerCase() + '.svg' : 'default-logo.svg'
11 + connector ? `${connector.connector_name.toLowerCase()}.svg` : 'default-logo.svg'
12 }`"
13 :alt="`${connector.connector_name} Logo`"
14 fallback-src="/images/img-not-found.svg"
@@ -19,35 +19,35 @@
19
20 <div class="connector-form-type">
21 <CredentialsType
22 - :form="connectorForm"
22 v-if="connectorFormType === ConnectorFormType.CREDENTIALS"
23 + :form="connectorForm"
24 @mounted="formRef = $event"
25 />
26 <FileType
27 - :form="connectorForm"
27 v-if="connectorFormType === ConnectorFormType.FILE"
28 + :form="connectorForm"
29 @mounted="formRef = $event"
30 />
31 <TokenType
32 - :form="connectorForm"
32 v-if="connectorFormType === ConnectorFormType.TOKEN"
33 + :form="connectorForm"
34 @mounted="formRef = $event"
35 />
36 <HostType
37 - :form="connectorForm"
37 v-if="connectorFormType === ConnectorFormType.HOST"
38 + :form="connectorForm"
39 @mounted="formRef = $event"
40 />
41 </div>
42 <div class="connector-form-options">
43 <n-form
44 + ref="formOptionsRef"
45 :model="connectorForm"
46 :rules="optionsRules"
47 label-width="120px"
47 - ref="formOptionsRef"
48 label-placement="top"
49 >
50 - <n-form-item label="Extra data" path="connector_extra_data" v-if="connectorFormOptions.extraData">
50 + <n-form-item v-if="connectorFormOptions.extraData" label="Extra data" path="connector_extra_data">
51 <n-input v-model:value="connectorForm.connector_extra_data" type="text" />
52 </n-form-item>
53 </n-form>
@@ -66,44 +66,44 @@
66 </template>
67
68 <script setup lang="ts">
69 -import { computed, onMounted, ref, toRefs, watch } from "vue"
69 +import Api from "@/api"
70 import {
71 - ConnectorFormType,
71 type Connector,
72 type ConnectorForm,
74 - type ConnectorRequestPayload,
73 + type ConnectorFormOptionKeys,
74 type ConnectorFormOptions,
76 - type ConnectorFormOptionKeys
75 + ConnectorFormType,
76 + type ConnectorRequestPayload
77 } from "@/types/connectors.d"
78 -import CredentialsType from "./FormTypes/CredentialsType.vue"
79 -import FileType from "./FormTypes/FileType.vue"
80 -import TokenType from "./FormTypes/TokenType.vue"
81 -import HostType from "./FormTypes/HostType.vue"
78 import _pick from "lodash/pick"
79 import {
84 - useMessage,
85 - NFormItem,
80 + type FormInst,
81 + type FormRules,
82 + type FormValidationError,
83 NAvatar,
87 - NSpin,
84 + NButton,
85 NForm,
86 + NFormItem,
87 NInput,
90 - NButton,
91 - type FormInst,
92 - type FormValidationError,
93 - type FormRules
88 + NSpin,
89 + useMessage
90 } from "naive-ui"
95 -import Api from "@/api"
91 +import { computed, onMounted, ref, toRefs, watch } from "vue"
92 +import CredentialsType from "./FormTypes/CredentialsType.vue"
93 +import FileType from "./FormTypes/FileType.vue"
94 +import HostType from "./FormTypes/HostType.vue"
95 +import TokenType from "./FormTypes/TokenType.vue"
96
97 const props = defineProps<{
98 connector: Connector
99 }>()
100 -const { connector } = toRefs(props)
101 -
100 const emit = defineEmits<{
101 (e: "close", value: boolean): void
102 (e: "loading", value: boolean): void
103 }>()
104
105 +const { connector } = toRefs(props)
106 +
107 const connectorForm = ref<ConnectorForm>({
108 connector_url: "",
109 connector_username: "",
frontend/src/components/connectors/ConfigForm/FormTypes/CredentialsType.vue
+8 -7
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form :model="form" :rules="rules" label-width="120px" ref="formRef" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
3 <n-form-item label="Connector URL" path="connector_url">
4 <n-input v-model:value="form.connector_url" required type="text" />
5 </n-form-item>
@@ -19,9 +19,9 @@
19 </template>
20
21 <script setup lang="ts">
22 -import { onMounted, ref, toRefs } from "vue"
22 +import { type FormInst, type FormItemRule, type FormRules, NForm, NFormItem, NInput } from "naive-ui"
23 import isURL from "validator/es/lib/isURL"
24 -import { NForm, NFormItem, NInput, type FormRules, type FormInst, type FormItemRule } from "naive-ui"
24 +import { onMounted, ref, toRefs } from "vue"
25
26 export interface ICredentialsForm {
27 connector_url: string
@@ -29,18 +29,19 @@ export interface ICredentialsForm {
29 connector_password: string
30 }
31
32 +const props = defineProps<{
33 + form: ICredentialsForm
34 +}>()
35 +
36 const emit = defineEmits<{
37 (e: "mounted", value: FormInst): void
38 }>()
39
36 -const props = defineProps<{
37 - form: ICredentialsForm
38 -}>()
40 const { form } = toRefs(props)
41
42 const formRef = ref<FormInst>()
43
43 -const validateUrl = (rule: FormItemRule, value: string) => {
44 +function validateUrl(rule: FormItemRule, value: string) {
45 if (!value) {
46 return new Error("Please input a valid URL")
47 }
frontend/src/components/connectors/ConfigForm/FormTypes/FileType.vue
+15 -14
@@ -1,13 +1,13 @@
1 <template>
2 - <n-form :model="form" :rules="rules" label-width="120px" ref="formRef" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
3 <n-form-item label="File" path="connector_file">
4 <n-upload
5 class="file-upload-wrap"
6 :max="1"
7 :show-file-list="true"
8 + accept=".yaml, .YAML, .yml, .YML"
9 @change="handleChange"
10 @remove="handleChange"
10 - accept=".yaml, .YAML, .yml, .YML"
11 >
12 <n-upload-dragger>
13 <div>
@@ -22,37 +22,38 @@
22 </template>
23
24 <script setup lang="ts">
25 -import { onMounted, ref, toRefs } from "vue"
25 +import Icon from "@/components/common/Icon.vue"
26 import {
27 + type FormInst,
28 + type FormItemRule,
29 + type FormRules,
30 NForm,
31 NFormItem,
32 NUpload,
33 NUploadDragger,
31 - type FormRules,
32 - type FormInst,
33 - type FormItemRule,
34 type UploadFileInfo
35 } from "naive-ui"
36 -import Icon from "@/components/common/Icon.vue"
36 +import { onMounted, ref, toRefs } from "vue"
37
38 export interface IFileForm {
39 connector_file: File | null
40 }
41
42 -const UploadIcon = "carbon:cloud-upload"
42 +const props = defineProps<{
43 + form: IFileForm
44 +}>()
45
46 const emit = defineEmits<{
47 (e: "mounted", value: FormInst): void
48 }>()
49
48 -const props = defineProps<{
49 - form: IFileForm
50 -}>()
50 +const UploadIcon = "carbon:cloud-upload"
51 +
52 const { form } = toRefs(props)
53
54 const formRef = ref<FormInst>()
55
55 -const handleChange = ({ file }: { file: UploadFileInfo }) => {
56 +function handleChange({ file }: { file: UploadFileInfo }) {
57 if (file.status === "removed") {
58 form.value.connector_file = null
59 } else {
@@ -61,14 +62,14 @@ const handleChange = ({ file }: { file: UploadFileInfo }) => {
62 formRef.value?.validate()
63 }
64
64 -const validateFile = (rule: FormItemRule, value: File) => {
65 +function validateFile(rule: FormItemRule, value: File) {
66 if (!value) {
67 return new Error("Please input a valid File")
68 }
69
70 const stringCheck = value.name + value.type
71
71 - if (stringCheck.indexOf("yaml") === -1) {
72 + if (!stringCheck.includes("yaml")) {
73 return new Error("Please input a valid File")
74 }
75
frontend/src/components/connectors/ConfigForm/FormTypes/HostType.vue
+8 -7
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form :model="form" :rules="rules" label-width="120px" ref="formRef" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
3 <n-form-item label="Connector URL" path="connector_url">
4 <n-input v-model:value="form.connector_url" required type="text" />
5 </n-form-item>
@@ -7,26 +7,27 @@
7 </template>
8
9 <script setup lang="ts">
10 -import { onMounted, ref, toRefs } from "vue"
10 +import { type FormInst, type FormItemRule, type FormRules, NForm, NFormItem, NInput } from "naive-ui"
11 import isURL from "validator/es/lib/isURL"
12 -import { NForm, NFormItem, NInput, type FormRules, type FormInst, type FormItemRule } from "naive-ui"
12 +import { onMounted, ref, toRefs } from "vue"
13
14 export interface IHostForm {
15 connector_url: string
16 }
17
18 +const props = defineProps<{
19 + form: IHostForm
20 +}>()
21 +
22 const emit = defineEmits<{
23 (e: "mounted", value: FormInst): void
24 }>()
25
22 -const props = defineProps<{
23 - form: IHostForm
24 -}>()
26 const { form } = toRefs(props)
27
28 const formRef = ref<FormInst>()
29
29 -const validateUrl = (rule: FormItemRule, value: string) => {
30 +function validateUrl(rule: FormItemRule, value: string) {
31 if (!value) {
32 return new Error("Please input a valid URL")
33 }
frontend/src/components/connectors/ConfigForm/FormTypes/TokenType.vue
+8 -7
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form :model="form" :rules="rules" label-width="120px" ref="formRef" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
3 <n-form-item label="Connector URL" path="connector_url">
4 <n-input v-model:value="form.connector_url" required type="text" />
5 </n-form-item>
@@ -10,27 +10,28 @@
10 </template>
11
12 <script setup lang="ts">
13 -import { onMounted, ref, toRefs } from "vue"
13 +import { type FormInst, type FormItemRule, type FormRules, NForm, NFormItem, NInput } from "naive-ui"
14 import isURL from "validator/es/lib/isURL"
15 -import { NForm, NFormItem, NInput, type FormRules, type FormInst, type FormItemRule } from "naive-ui"
15 +import { onMounted, ref, toRefs } from "vue"
16
17 export interface ITokenForm {
18 connector_url: string
19 connector_api_key: string
20 }
21
22 +const props = defineProps<{
23 + form: ITokenForm
24 +}>()
25 +
26 const emit = defineEmits<{
27 (e: "mounted", value: FormInst): void
28 }>()
29
26 -const props = defineProps<{
27 - form: ITokenForm
28 -}>()
30 const { form } = toRefs(props)
31
32 const formRef = ref<FormInst>()
33
33 -const validateUrl = (rule: FormItemRule, value: string) => {
34 +function validateUrl(rule: FormItemRule, value: string) {
35 if (!value) {
36 return new Error("Please input a valid URL")
37 }
frontend/src/components/connectors/ConnectorItem.vue
+19 -16
@@ -11,7 +11,7 @@
11 round
12 :size="40"
13 :src="`/images/connectors/${
14 - connector ? connector.connector_name.toLowerCase() + '.svg' : 'default-logo.svg'
14 + connector ? `${connector.connector_name.toLowerCase()}.svg` : 'default-logo.svg'
15 }`"
16 :alt="`${connector.connector_name} Logo`"
17 fallback-src="/images/img-not-found.svg"
@@ -19,11 +19,13 @@
19 </div>
20
21 <div class="info flex flex-col gap-2">
22 - <div class="name">{{ connector.connector_name }}</div>
23 - <div class="description" v-if="connector.connector_description">
22 + <div class="name">
23 + {{ connector.connector_name }}
24 + </div>
25 + <div v-if="connector.connector_description" class="description">
26 {{ connector.connector_description }}
27 </div>
26 - <div class="extra-data" v-if="connector.connector_extra_data">
28 + <div v-if="connector.connector_extra_data" class="extra-data">
29 {{ connector.connector_extra_data }}
30 </div>
31 </div>
@@ -54,11 +56,11 @@
56
57 <div class="actions-box flex flex-col gap-2 justify-center">
58 <n-button
57 - type="primary"
59 v-if="!connector.connector_verified"
59 - @click="verify(connector)"
60 + type="primary"
61 :loading="loadingVerify"
62 size="small"
63 + @click="verify(connector)"
64 >
65 <template #icon>
66 <Icon :name="VerifyIcon"></Icon>
@@ -69,8 +71,8 @@
71 <n-button
72 :type="!connector.connector_configured ? 'primary' : undefined"
73 :loading="loadingConfiguration"
72 - @click="openConfigDialog()"
74 size="small"
75 + @click="openConfigDialog()"
76 >
77 <template #icon>
78 <Icon :name="DetailsIcon"></Icon>
@@ -83,8 +85,8 @@
85 </div>
86
87 <n-modal
86 - title="Connector configuration"
88 v-model:show="showConfigDialog"
89 + title="Connector configuration"
90 :mask-closable="false"
91 :close-on-esc="false"
92 width="600px"
@@ -92,8 +94,8 @@
94 <n-card style="width: 90vw; max-width: 500px">
95 <ConfigForm
96 v-if="showConfigDialog"
95 - @loading="loadingConfiguration = $event"
97 :connector="connector"
98 + @loading="loadingConfiguration = $event"
99 @close="closeConfigDialog"
100 />
101 </n-card>
@@ -102,22 +104,23 @@
104 </template>
105
106 <script setup lang="ts">
105 -import Icon from "@/components/common/Icon.vue"
107 +import type { Connector } from "@/types/connectors.d"
108 +import Api from "@/api"
109 import Badge from "@/components/common/Badge.vue"
110 +import Icon from "@/components/common/Icon.vue"
111 +import { NAvatar, NButton, NCard, NModal, NSpin, useMessage } from "naive-ui"
112 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.d"
113 import ConfigForm from "./ConfigForm"
114
115 +const props = defineProps<{
116 + connector: Connector
117 +}>()
118 +
119 const emit = defineEmits<{
120 (e: "verified"): void
121 (e: "updated"): void
122 }>()
123
118 -const props = defineProps<{
119 - connector: Connector
120 -}>()
124 const { connector } = toRefs(props)
125
126 const DetailsIcon = "carbon:settings-adjust"
frontend/src/components/connectors/ConnectorsList.vue
+5 -5
@@ -14,13 +14,13 @@
14 v-for="connector of connectorsList"
15 :key="connector.id"
16 :connector="connector"
17 + class="item-appear item-appear-bottom item-appear-005 mb-2"
18 @verified="getConnectors()"
19 @updated="getConnectors()"
19 - class="item-appear item-appear-bottom item-appear-005 mb-2"
20 />
21 </template>
22 <template v-else>
23 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingConnectors" />
23 + <n-empty v-if="!loadingConnectors" description="No items found" class="justify-center h-48" />
24 </template>
25 </div>
26 </n-spin>
@@ -28,11 +28,11 @@
28 </template>
29
30 <script setup lang="ts">
31 -import { ref, onBeforeMount, computed } from "vue"
32 -import { useMessage, NSpin, NEmpty } from "naive-ui"
31 +import type { Connector } from "@/types/connectors.d"
32 import Api from "@/api"
33 +import { NEmpty, NSpin, useMessage } from "naive-ui"
34 +import { computed, onBeforeMount, ref } from "vue"
35 import ConnectorItem from "./ConnectorItem.vue"
35 -import type { Connector } from "@/types/connectors.d"
36
37 const message = useMessage()
38 const loadingConnectors = ref(false)
frontend/src/components/customers/CustomerAgents.vue
+6 -6
@@ -7,9 +7,9 @@
7 :agent="agent"
8 bg-secondary
9 show-actions
10 + class="item-appear item-appear-bottom item-appear-005"
11 @delete="getAgents()"
12 @click="gotoAgent(agent.agent_id)"
12 - class="item-appear item-appear-bottom item-appear-005"
13 />
14 <n-empty v-if="!list.length" description="No Agents found" class="justify-center h-48" />
15 </div>
@@ -17,13 +17,13 @@
17 </template>
18
19 <script setup lang="ts">
20 -import { onBeforeMount, ref, toRefs } from "vue"
21 -import AgentCard from "@/components/agents/AgentCard.vue"
22 -import Api from "@/api"
23 -import { useMessage, NSpin, NEmpty } from "naive-ui"
24 -import type { Customer } from "@/types/customers.d"
20 import type { Agent } from "@/types/agents.d"
21 +import type { Customer } from "@/types/customers.d"
22 +import Api from "@/api"
23 +import AgentCard from "@/components/agents/AgentCard.vue"
24 import { useGoto } from "@/composables/useGoto"
25 +import { NEmpty, NSpin, useMessage } from "naive-ui"
26 +import { onBeforeMount, ref, toRefs } from "vue"
27
28 const props = defineProps<{
29 customer: Customer
frontend/src/components/customers/CustomerCreationButton.vue
+5 -5
@@ -14,23 +14,23 @@
14 display-directive="show"
15 >
16 <n-drawer-content title="Add Customer" closable :native-scrollbar="false">
17 - <CustomerForm @mounted="customerFormCTX = $event" @submitted="emit('submitted')" :resetOnSubmit="true" />
17 + <CustomerForm :reset-on-submit="true" @mounted="customerFormCTX = $event" @submitted="emit('submitted')" />
18 </n-drawer-content>
19 </n-drawer>
20 </template>
21
22 <script setup lang="ts">
23 -import { ref, watch } from "vue"
24 -import { NButton, NDrawer, NDrawerContent } from "naive-ui"
23 import Icon from "@/components/common/Icon.vue"
24 +import { NButton, NDrawer, NDrawerContent } from "naive-ui"
25 +import { ref, watch } from "vue"
26 import CustomerForm from "./CustomerForm.vue"
27
28 -const openForm = defineModel<boolean | undefined>("openForm", { default: false })
29 -
28 const emit = defineEmits<{
29 (e: "submitted"): void
30 }>()
31
32 +const openForm = defineModel<boolean | undefined>("openForm", { default: false })
33 +
34 const AddUserIcon = "carbon:user-follow"
35
36 const customerFormCTX = ref<{ reset: () => void } | null>(null)
frontend/src/components/customers/CustomerForm.vue
+23 -22
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="customer-form">
3 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 <div v-for="(_, key) of form" :key="key" class="grow">
@@ -20,8 +20,8 @@
20 <slot name="additionalActions"></slot>
21 </div>
22 <div class="flex gap-4">
23 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
24 - <n-button type="primary" :disabled="!isValid" @click="validate()" :loading="loading">
23 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
24 + <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">
25 Submit
26 </n-button>
27 </div>
@@ -32,22 +32,29 @@
32 </template>
33
34 <script setup lang="ts">
35 -import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
35 +import type { Customer } from "@/types/customers.d"
36 import Api from "@/api"
37 +import _get from "lodash/get"
38 +import _trim from "lodash/trim"
39 import {
38 - useMessage,
40 + type FormInst,
41 + type FormRules,
42 + type FormValidationError,
43 + NButton,
44 NForm,
45 NFormItem,
46 NInput,
42 - NButton,
47 NSpin,
44 - type FormValidationError,
45 - type FormInst,
46 - type FormRules
48 + useMessage
49 } from "naive-ui"
48 -import type { Customer } from "@/types/customers.d"
49 -import _trim from "lodash/trim"
50 -import _get from "lodash/get"
50 +import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
51 +
52 +const props = defineProps<{
53 + customer?: Customer
54 + resetOnSubmit?: boolean
55 + /** lock customer_code on reset and editing (readonly) */
56 + lockCode?: boolean
57 +}>()
58
59 const emit = defineEmits<{
60 (e: "update:loading", value: boolean): void
@@ -60,12 +67,6 @@ const emit = defineEmits<{
67 ): void
68 }>()
69
63 -const props = defineProps<{
64 - customer?: Customer
65 - resetOnSubmit?: boolean
66 - /** lock customer_code on reset and editing (readonly) */
67 - lockCode?: boolean
68 -}>()
70 const { customer, resetOnSubmit, lockCode } = toRefs(props)
71
72 const loading = ref(false)
@@ -80,11 +81,11 @@ const rules: FormRules = {
81 trigger: ["input", "blur"],
82 validator: (rule, value) => {
83 if (value !== value.toLowerCase()) {
83 - return Promise.reject("Code must be all lowercase")
84 + return new Error("Code must be all lowercase")
85 } else if (!/^[a-z]+$/.test(value)) {
85 - return Promise.reject("Code must not contain spaces or special characters")
86 + return new Error("Code must not contain spaces or special characters")
87 } else {
87 - return Promise.resolve()
88 + return true
89 }
90 }
91 },
@@ -211,7 +212,7 @@ function getClearForm(customer?: Partial<Customer>) {
212 }
213
214 function reset() {
214 - let fields = undefined
215 + let fields
216 if (lockCode.value) {
217 fields = { customer_code: customer.value?.customer_code || "" }
218 }
frontend/src/components/customers/CustomerInfo.vue
+18 -13
@@ -1,7 +1,7 @@
1 <template>
2 <div class="customer-info">
3 - <div class="p-7 pt-4" v-if="editing">
4 - <CustomerForm @submitted="submitted" :customer="customer" :lockCode="true">
3 + <div v-if="editing" class="p-7 pt-4">
4 + <CustomerForm :customer="customer" :lock-code="true" @submitted="submitted">
5 <template #additionalActions>
6 <n-button @click="editing = false">Close</n-button>
7 </template>
@@ -9,13 +9,13 @@
9 </div>
10 <template v-else>
11 <div class="flex items-center justify-between gap-4 px-7 pt-2">
12 - <n-button size="small" @click="editing = true" :disabled="loadingDelete">
12 + <n-button size="small" :disabled="loadingDelete" @click="editing = true">
13 <template #icon>
14 <Icon :name="EditIcon" :size="14"></Icon>
15 </template>
16 Edit
17 </n-button>
18 - <n-button size="small" type="error" ghost @click="handleDelete" :loading="loadingDelete">
18 + <n-button size="small" type="error" ghost :loading="loadingDelete" @click="handleDelete">
19 <template #icon>
20 <Icon :name="DeleteIcon" :size="15"></Icon>
21 </template>
@@ -25,8 +25,12 @@
25
26 <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
27 <KVCard v-for="(value, key) of customer" :key="key">
28 - <template #key>{{ key }}</template>
29 - <template #value>{{ value || "-" }}</template>
28 + <template #key>
29 + {{ key }}
30 + </template>
31 + <template #value>
32 + {{ value || "-" }}
33 + </template>
34 </KVCard>
35 </div>
36 </template>
@@ -34,13 +38,17 @@
38 </template>
39
40 <script setup lang="ts">
37 -import { h, ref, toRefs, watch } from "vue"
38 -import { useMessage, NButton, useDialog } from "naive-ui"
41 +import type { Customer } from "@/types/customers.d"
42 +import Api from "@/api"
43 import Icon from "@/components/common/Icon.vue"
44 import KVCard from "@/components/common/KVCard.vue"
45 +import { NButton, useDialog, useMessage } from "naive-ui"
46 +import { h, ref, toRefs, watch } from "vue"
47 import CustomerForm from "./CustomerForm.vue"
42 -import Api from "@/api"
43 -import type { Customer } from "@/types/customers.d"
48 +
49 +const props = defineProps<{
50 + customer: Customer
51 +}>()
52
53 const emit = defineEmits<{
54 (e: "update:loading", value: boolean): void
@@ -48,9 +56,6 @@ const emit = defineEmits<{
56 (e: "submitted", value: Customer): void
57 }>()
58
51 -const props = defineProps<{
52 - customer: Customer
53 -}>()
59 const { customer } = toRefs(props)
60
61 const EditIcon = "uil:edit-alt"
frontend/src/components/customers/CustomerItem.vue
+53 -43
@@ -1,9 +1,9 @@
1 <template>
2 - <n-spin :show="loading" :class="{ highlight }" :id="'customer-' + customer.customer_code" class="customer-item">
2 + <n-spin :id="`customer-${customer.customer_code}`" :show="loading" :class="{ highlight }" class="customer-item">
3 <div class="px-4 py-3 flex flex-col gap-2">
4 <div class="header-box flex justify-between items-center">
5 <div class="id">#{{ customer.customer_code }}</div>
6 - <div class="actions" v-if="!hideCardActions">
6 + <div v-if="!hideCardActions" class="actions">
7 <n-button size="small" @click.stop="showDetails = true">
8 <template #icon>
9 <Icon :name="DetailsIcon"></Icon>
@@ -21,7 +21,9 @@
21 />
22
23 <div class="content flex flex-col gap-1 grow">
24 - <div class="title">{{ customerInfo?.customer_name }}</div>
24 + <div class="title">
25 + {{ customerInfo?.customer_name }}
26 + </div>
27 <div class="description">
28 {{ customerInfo?.contact_first_name }} {{ customerInfo?.contact_last_name }}
29 </div>
@@ -34,7 +36,9 @@
36 <Icon :name="UserTypeIcon" :size="14"></Icon>
37 </template>
38 <template #label>Type</template>
37 - <template #value>{{ customerInfo?.customer_type || "-" }}</template>
39 + <template #value>
40 + {{ customerInfo?.customer_type || "-" }}
41 + </template>
42 </Badge>
43
44 <n-popover trigger="hover" :disabled="addressLabel === '-'">
@@ -50,27 +54,27 @@
54 </template>
55
56 <div class="flex flex-col gap-1">
53 - <div class="box" v-if="customerInfo?.address_line1">
57 + <div v-if="customerInfo?.address_line1" class="box">
58 address_line1:
59 <code>{{ customerInfo.address_line1 }}</code>
60 </div>
57 - <div class="box" v-if="customerInfo?.address_line2">
61 + <div v-if="customerInfo?.address_line2" class="box">
62 address_line2:
63 <code>{{ customerInfo.address_line2 }}</code>
64 </div>
61 - <div class="box" v-if="customerInfo?.postal_code">
65 + <div v-if="customerInfo?.postal_code" class="box">
66 postal_code:
67 <code>{{ customerInfo.postal_code }}</code>
68 </div>
65 - <div class="box" v-if="customerInfo?.city">
69 + <div v-if="customerInfo?.city" class="box">
70 city:
71 <code>{{ customerInfo.city }}</code>
72 </div>
69 - <div class="box" v-if="customerInfo?.state">
73 + <div v-if="customerInfo?.state" class="box">
74 state:
75 <code>{{ customerInfo.state }}</code>
76 </div>
73 - <div class="box" v-if="customerInfo?.country">
77 + <div v-if="customerInfo?.country" class="box">
78 country:
79 <code>{{ customerInfo.country }}</code>
80 </div>
@@ -81,15 +85,19 @@
85 <template #iconLeft>
86 <Icon :name="PhoneIcon" :size="13"></Icon>
87 </template>
84 - <template #value>{{ customerInfo?.phone || "-" }}</template>
88 + <template #value>
89 + {{ customerInfo?.phone || "-" }}
90 + </template>
91 </Badge>
92
87 - <Badge type="splitted" color="primary" v-if="customerInfo?.parent_customer_code">
93 + <Badge v-if="customerInfo?.parent_customer_code" type="splitted" color="primary">
94 <template #iconLeft>
95 <Icon :name="ParentIcon" :size="13"></Icon>
96 </template>
97 <template #label>Parent</template>
92 - <template #value>{{ customerInfo?.parent_customer_code }}</template>
98 + <template #value>
99 + {{ customerInfo?.parent_customer_code }}
100 + </template>
101 </Badge>
102 </div>
103 </div>
@@ -104,21 +112,21 @@
112 segmented
113 >
114 <Transition :name="`slide-tabs-${selectedTabsGroup === 'customer' ? 'left' : 'right'}`">
107 - <n-tabs type="line" animated :tabs-padding="24" v-if="selectedTabsGroup === 'customer'" class="h-full">
115 + <n-tabs v-if="selectedTabsGroup === 'customer'" type="line" animated :tabs-padding="24" class="h-full">
116 <n-tab-pane name="Info" tab="Info" display-directive="show:lazy">
117 <CustomerInfo
118 + v-if="customerInfo"
119 + v-model:loading="loadingDelete"
120 :customer="customerInfo"
121 @delete="deletedItem()"
122 @submitted="customerInfo = $event"
113 - v-model:loading="loadingDelete"
114 - v-if="customerInfo"
123 />
124 </n-tab-pane>
125 <n-tab-pane name="Provision" tab="Provision" display-directive="show:lazy">
126 <CustomerProvision
119 - :customerMeta="customerMeta"
120 - :customerCode="customer.customer_code"
121 - :customerName="customer.customer_name"
127 + :customer-meta="customerMeta"
128 + :customer-code="customer.customer_code"
129 + :customer-name="customer.customer_name"
130 @delete="customerMeta = null"
131 @submitted="customerMeta = $event"
132 />
@@ -129,14 +137,14 @@
137 display-directive="show:lazy"
138 >
139 <CustomerIntegrations
132 - :customerCode="customer.customer_code"
133 - :customerName="customer.customer_name"
140 + :customer-code="customer.customer_code"
141 + :customer-name="customer.customer_name"
142 />
143 </n-tab-pane>
144 <n-tab-pane name="Network Connectors" tab="Network Connectors" display-directive="show:lazy">
145 <CustomerNetworkConnectors
138 - :customerCode="customer.customer_code"
139 - :customerName="customer.customer_name"
146 + :customer-code="customer.customer_code"
147 + :customer-name="customer.customer_name"
148 />
149 </n-tab-pane>
150 <n-tab-pane
@@ -144,7 +152,7 @@
152 tab="Notification Workflows"
153 display-directive="show:lazy"
154 >
147 - <CustomerNotificationsWorkflows :customerCode="customer.customer_code" />
155 + <CustomerNotificationsWorkflows :customer-code="customer.customer_code" />
156 </n-tab-pane>
157 <template #suffix>
158 <div class="pr-8 hover:text-primary-color cursor-pointer" @click="selectedTabsGroup = 'agents'">
@@ -152,7 +160,7 @@
160 </div>
161 </template>
162 </n-tabs>
155 - <n-tabs type="line" animated :tabs-padding="24" v-else-if="selectedTabsGroup === 'agents'">
163 + <n-tabs v-else-if="selectedTabsGroup === 'agents'" type="line" animated :tabs-padding="24">
164 <template #prefix>
165 <div
166 class="pl-6 relative top-1 hover:text-primary-color cursor-pointer"
@@ -164,14 +172,14 @@
172 <n-tab-pane name="Agents" tab="Agents" display-directive="show:lazy">
173 <n-scrollbar style="max-height: 470px" trigger="none">
174 <div class="p-6 pt-2">
167 - <CustomerAgents :customer="customerInfo" v-if="customerInfo" />
175 + <CustomerAgents v-if="customerInfo" :customer="customerInfo" />
176 </div>
177 </n-scrollbar>
178 </n-tab-pane>
179 <n-tab-pane name="Healthcheck Wazuh" tab="Healthcheck Wazuh" display-directive="show:lazy">
180 <n-scrollbar style="max-height: 470px" trigger="none">
181 <div class="p-6 pt-2">
174 - <CustomerHealthcheckList source="wazuh" :customerCode="customer.customer_code" />
182 + <CustomerHealthcheckList source="wazuh" :customer-code="customer.customer_code" />
183 </div>
184 </n-scrollbar>
185 </n-tab-pane>
@@ -182,7 +190,10 @@
190 >
191 <n-scrollbar style="max-height: 470px" trigger="none">
192 <div class="p-6 pt-2">
185 - <CustomerHealthcheckList source="velociraptor" :customerCode="customer.customer_code" />
193 + <CustomerHealthcheckList
194 + source="velociraptor"
195 + :customer-code="customer.customer_code"
196 + />
197 </div>
198 </n-scrollbar>
199 </n-tab-pane>
@@ -193,15 +204,23 @@
204 </template>
205
206 <script setup lang="ts">
196 -import { computed, defineAsyncComponent, onBeforeMount, ref, toRefs, watch } from "vue"
197 -import { NAvatar, useMessage, NPopover, NModal, NTabs, NTabPane, NSpin, NScrollbar, NButton } from "naive-ui"
198 -import Icon from "@/components/common/Icon.vue"
199 -import Badge from "@/components/common/Badge.vue"
207 +import type { Customer, CustomerMeta } from "@/types/customers.d"
208 import Api from "@/api"
209 +import Badge from "@/components/common/Badge.vue"
210 +import Icon from "@/components/common/Icon.vue"
211 import { hashMD5 } from "@/utils"
212 import _toSafeInteger from "lodash/toSafeInteger"
203 -import type { Customer, CustomerMeta } from "@/types/customers.d"
213 +import { NAvatar, NButton, NModal, NPopover, NScrollbar, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
214 +import { computed, defineAsyncComponent, onBeforeMount, ref, toRefs, watch } from "vue"
215
216 +const props = defineProps<{
217 + customer: Customer
218 + highlight?: boolean | null | undefined
219 + hideCardActions?: boolean | null | undefined
220 +}>()
221 +const emit = defineEmits<{
222 + (e: "delete"): void
223 +}>()
224 const CustomerInfo = defineAsyncComponent(() => import("./CustomerInfo.vue"))
225 const CustomerAgents = defineAsyncComponent(() => import("./CustomerAgents.vue"))
226 const CustomerProvision = defineAsyncComponent(() => import("./provision/CustomerProvision.vue"))
@@ -214,15 +233,6 @@ const CustomerNotificationsWorkflows = defineAsyncComponent(
233 () => import("./notifications/CustomerNotificationsWorkflows.vue")
234 )
235
217 -const emit = defineEmits<{
218 - (e: "delete"): void
219 -}>()
220 -
221 -const props = defineProps<{
222 - customer: Customer
223 - highlight?: boolean | null | undefined
224 - hideCardActions?: boolean | null | undefined
225 -}>()
236 const { customer, highlight, hideCardActions } = toRefs(props)
237
238 const DetailsIcon = "carbon:settings-adjust"
@@ -244,7 +254,7 @@ const loading = computed(() => loadingFull.value || loadingDelete.value)
254 const fallbackAvatar = computed(() => {
255 let text = customer.value.customer_name.slice(0, 2).toUpperCase()
256
247 - if (customer.value.customer_name.indexOf(" ") !== -1) {
257 + if (customer.value.customer_name.includes(" ")) {
258 const chunks = customer.value.customer_name.split(" ")
259 text = (chunks[0][0] + chunks[1][0]).toUpperCase()
260 }
frontend/src/components/customers/CustomerMetaForm.vue
+21 -20
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="customer-meta-form">
3 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 <div v-for="(_, key) of form" :key="key" class="grow">
@@ -19,8 +19,8 @@
19 <slot name="additionalActions"></slot>
20 </div>
21 <div class="flex gap-4">
22 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
23 - <n-button type="primary" :disabled="!isValid" @click="validate()" :loading="loading">
22 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
23 + <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">
24 Submit
25 </n-button>
26 </div>
@@ -31,29 +31,37 @@
31 </template>
32
33 <script setup lang="ts">
34 -import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
34 +import type { CustomerMeta } from "@/types/customers.d"
35 import Api from "@/api"
36 +import _get from "lodash/get"
37 +import _toSafeInteger from "lodash/toSafeInteger"
38 +import _trim from "lodash/trim"
39 import {
37 - useMessage,
40 + type FormInst,
41 + type FormRules,
42 + type FormValidationError,
43 + NButton,
44 NForm,
45 NFormItem,
46 NInput,
41 - NButton,
47 NSpin,
43 - type FormValidationError,
44 - type FormInst,
45 - type FormRules
48 + useMessage
49 } from "naive-ui"
47 -import type { CustomerMeta } from "@/types/customers.d"
48 -import _trim from "lodash/trim"
49 -import _get from "lodash/get"
50 -import _toSafeInteger from "lodash/toSafeInteger"
50 +import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
51
52 interface CustomerMetaExt extends Omit<CustomerMeta, "id" | "customer_meta_iris_customer_id"> {
53 id: string
54 customer_meta_iris_customer_id: string
55 }
56
57 +const props = defineProps<{
58 + customerMeta?: CustomerMeta
59 + customerCode: string
60 + customerName: string
61 + metaId: number
62 + resetOnSubmit?: boolean
63 +}>()
64 +
65 const emit = defineEmits<{
66 (e: "update:loading", value: boolean): void
67 (e: "submitted", value: CustomerMeta): void
@@ -65,13 +73,6 @@ const emit = defineEmits<{
73 ): void
74 }>()
75
68 -const props = defineProps<{
69 - customerMeta?: CustomerMeta
70 - customerCode: string
71 - customerName: string
72 - metaId: number
73 - resetOnSubmit?: boolean
74 -}>()
76 const { customerMeta, customerCode, customerName, metaId, resetOnSubmit } = toRefs(props)
77
78 const loading = ref(false)
frontend/src/components/customers/CustomersList.vue
+8 -8
@@ -17,13 +17,13 @@
17 :key="customer.customer_code"
18 :customer="customer"
19 :highlight="customer.customer_code === highlight"
20 - :hideCardActions="loadingCustomers"
21 - @delete="getCustomers()"
20 + :hide-card-actions="loadingCustomers"
21 class="item-appear item-appear-bottom item-appear-005 mb-2"
22 + @delete="getCustomers()"
23 />
24 </template>
25 <template v-else>
26 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingCustomers" />
26 + <n-empty v-if="!loadingCustomers" description="No items found" class="justify-center h-48" />
27 </template>
28 </div>
29 </n-spin>
@@ -31,19 +31,19 @@
31 </template>
32
33 <script setup lang="ts">
34 -import { ref, onBeforeMount, computed, watch, toRefs, nextTick } from "vue"
35 -import { useMessage, NSpin, NEmpty } from "naive-ui"
34 +import type { Customer } from "@/types/customers.d"
35 import Api from "@/api"
36 +import { NEmpty, NSpin, useMessage } from "naive-ui"
37 +import { computed, nextTick, onBeforeMount, ref, toRefs, watch } from "vue"
38 import CustomerItem from "./CustomerItem.vue"
38 -import type { Customer } from "@/types/customers.d"
39
40 const props = defineProps<{ highlight: string | null | undefined; reload?: boolean }>()
41 -const { highlight, reload } = toRefs(props)
42 -
41 const emit = defineEmits<{
42 (e: "reloaded"): void
43 }>()
44
45 +const { highlight, reload } = toRefs(props)
46 +
47 const message = useMessage()
48 const loadingCustomers = ref(false)
49 const customersList = ref<Customer[]>([])
frontend/src/components/customers/healthcheck/CustomerHealthcheckItem.vue
+22 -14
@@ -6,7 +6,7 @@
6 <span>#{{ healthData.id }} - {{ healthData.label }}</span>
7 <Icon :name="InfoIcon" :size="16"></Icon>
8 </div>
9 - <div class="time" v-if="cardDate">
9 + <div v-if="cardDate" class="time">
10 {{ cardDate }}
11 </div>
12 </div>
@@ -24,14 +24,18 @@
24 </div>
25
26 <div class="badges-box flex flex-wrap items-center gap-3 mt-2">
27 - <Badge type="splitted" color="primary" v-if="agentVersion">
27 + <Badge v-if="agentVersion" type="splitted" color="primary">
28 <template #label>Agent version</template>
29 - <template #value>{{ agentVersion }}</template>
29 + <template #value>
30 + {{ agentVersion }}
31 + </template>
32 </Badge>
33
32 - <Badge type="splitted" color="primary" v-if="source === 'velociraptor'">
34 + <Badge v-if="source === 'velociraptor'" type="splitted" color="primary">
35 <template #label>Velociraptor Id</template>
34 - <template #value>{{ healthData.velociraptor_id }}</template>
36 + <template #value>
37 + {{ healthData.velociraptor_id }}
38 + </template>
39 </Badge>
40
41 <n-popover overlap placement="bottom-start">
@@ -74,8 +78,12 @@
78 >
79 <div class="grid gap-2 grid-auto-fit-200 px-7 py-6">
80 <KVCard v-for="(value, key) of healthData" :key="key">
77 - <template #key>{{ key }}</template>
78 - <template #value>{{ value || "-" }}</template>
81 + <template #key>
82 + {{ key }}
83 + </template>
84 + <template #value>
85 + {{ value || "-" }}
86 + </template>
87 </KVCard>
88 </div>
89 </n-modal>
@@ -83,16 +91,16 @@
91 </template>
92
93 <script setup lang="ts">
86 -import Icon from "@/components/common/Icon.vue"
94 +import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/customers.d"
95 import Badge from "@/components/common/Badge.vue"
88 -import { computed, ref } from "vue"
96 +import Icon from "@/components/common/Icon.vue"
97 import KVCard from "@/components/common/KVCard.vue"
90 -import { NPopover, NModal } from "naive-ui"
91 -import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/customers.d"
92 -import dayjs from "@/utils/dayjs"
93 -import { iconFromOs } from "@/utils"
94 -import { useSettingsStore } from "@/stores/settings"
98 import { useGoto } from "@/composables/useGoto"
99 +import { useSettingsStore } from "@/stores/settings"
100 +import { iconFromOs } from "@/utils"
101 +import dayjs from "@/utils/dayjs"
102 +import { NModal, NPopover } from "naive-ui"
103 +import { computed, ref } from "vue"
104
105 const { healthData, source, bgSecondary, type } = defineProps<{
106 healthData: CustomerAgentHealth
frontend/src/components/customers/healthcheck/CustomerHealthcheckList.vue
+10 -10
@@ -15,7 +15,7 @@
15 </div>
16 <n-spin :show="loading">
17 <div class="customer-healthcheck-list flex flex-col gap-6">
18 - <div class="list flex flex-col gap-2" v-if="healthyList.length">
18 + <div v-if="healthyList.length" class="list flex flex-col gap-2">
19 <div class="title healthy flex items-center gap-2">
20 <Icon :name="CheckIcon" :size="16"></Icon>
21 Healthy
@@ -31,7 +31,7 @@
31 class="item-appear item-appear-bottom item-appear-005"
32 />
33 </div>
34 - <div class="list flex flex-col gap-2" v-if="unhealthyList.length">
34 + <div v-if="unhealthyList.length" class="list flex flex-col gap-2">
35 <div class="title unhealthy flex items-center gap-2">
36 <Icon :name="AlertIcon" :size="16"></Icon>
37 Unhealthy
@@ -53,15 +53,15 @@
53 </template>
54
55 <script setup lang="ts">
56 -import { onBeforeMount, ref, watch } from "vue"
57 -import { useMessage, NSpin, NEmpty, NSelect, NInputGroup, NInputNumber } from "naive-ui"
58 -import CustomerHealthcheckItem from "./CustomerHealthcheckItem.vue"
59 -import Icon from "@/components/common/Icon.vue"
60 -import _get from "lodash/get"
56 +import type { CustomerAgentsHealthcheckQuery } from "@/api/endpoints/customers"
57 +import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/customers.d"
58 import Api from "@/api"
59 +import Icon from "@/components/common/Icon.vue"
60 import { watchDebounced } from "@vueuse/core"
63 -import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/customers.d"
64 -import type { CustomerAgentsHealthcheckQuery } from "@/api/endpoints/customers"
61 +import _get from "lodash/get"
62 +import { NEmpty, NInputGroup, NInputNumber, NSelect, NSpin, useMessage } from "naive-ui"
63 +import { onBeforeMount, ref, watch } from "vue"
64 +import CustomerHealthcheckItem from "./CustomerHealthcheckItem.vue"
65
66 const { source, customerCode } = defineProps<{
67 source: CustomerHealthcheckSource
@@ -110,7 +110,7 @@ function getList() {
110 return
111 }
112
113 - let query: CustomerAgentsHealthcheckQuery | undefined = undefined
113 + let query: CustomerAgentsHealthcheckQuery | undefined
114 if (filters.value.time && filters.value.unit) {
115 query = {}
116 query[filters.value.unit] = filters.value.time
frontend/src/components/customers/integrations/CustomerIntegrationActions.vue
+17 -15
@@ -1,17 +1,19 @@
1 <template>
2 <div class="alert-actions flex gap-4 justify-end">
3 - <n-button v-if="isDeployEnabled" :loading="loading" @click="provision()" type="success" :size="size" secondary>
4 - <template #icon><Icon :name="DeployIcon"></Icon></template>
3 + <n-button v-if="isDeployEnabled" :loading="loading" type="success" :size="size" secondary @click="provision()">
4 + <template #icon>
5 + <Icon :name="DeployIcon"></Icon>
6 + </template>
7 Deploy
8 </n-button>
9
10 <n-button
11 + v-if="!hideDeleteButton"
12 :size="size"
13 type="error"
14 ghost
12 - @click="handleDelete"
15 :loading="loadingDelete"
14 - v-if="!hideDeleteButton"
16 + @click="handleDelete"
17 >
18 <template #icon>
19 <Icon :name="DeleteIcon" :size="15"></Icon>
@@ -22,13 +24,19 @@
24 </template>
25
26 <script setup lang="ts">
25 -import { computed, h, ref, watch } from "vue"
26 -import { NButton, useDialog, useMessage } from "naive-ui"
27 -import Icon from "@/components/common/Icon.vue"
28 -import Api from "@/api"
27 +import type { ApiCommonResponse } from "@/types/common.d"
28 import type { CustomerIntegration } from "@/types/integrations.d"
29 import type { Size } from "naive-ui/es/button/src/interface"
31 -import type { ApiCommonResponse } from "@/types/common.d"
30 +import Api from "@/api"
31 +import Icon from "@/components/common/Icon.vue"
32 +import { NButton, useDialog, useMessage } from "naive-ui"
33 +import { computed, h, ref, watch } from "vue"
34 +
35 +const { integration, hideDeleteButton, size } = defineProps<{
36 + integration: CustomerIntegration
37 + hideDeleteButton?: boolean
38 + size?: Size
39 +}>()
40
41 const emit = defineEmits<{
42 (e: "startLoading"): void
@@ -37,12 +45,6 @@ const emit = defineEmits<{
45 (e: "deleted"): void
46 }>()
47
40 -const { integration, hideDeleteButton, size } = defineProps<{
41 - integration: CustomerIntegration
42 - hideDeleteButton?: boolean
43 - size?: Size
44 -}>()
45 -
48 const DeployIcon = "carbon:deploy"
49 const DeleteIcon = "ph:trash"
50
frontend/src/components/customers/integrations/CustomerIntegrationForm.vue
+16 -16
@@ -7,7 +7,7 @@
7 <n-step title="Choose Integration" />
8 <n-step title="Set Auth Keys">
9 <template #icon>
10 - <Icon :name="SkipIcon" v-if="!isAuthKeysStepEnabled"></Icon>
10 + <Icon v-if="!isAuthKeysStepEnabled" :name="SkipIcon"></Icon>
11 </template>
12 </n-step>
13 </n-steps>
@@ -17,24 +17,24 @@
17
18 <div class="flex flex-col grow overflow-hidden">
19 <Transition :name="`slide-form-${slideFormDirection}`">
20 - <div class="available-list grow overflow-hidden" v-if="current === 1">
20 + <div v-if="current === 1" class="available-list grow overflow-hidden">
21 <n-scrollbar style="max-height: 355px" trigger="none">
22 <IntegrationsList
23 + v-model:selected="selectedIntegration"
24 embedded
25 hide-totals
26 selectable
26 - :disabledIdsList="disabledIdsList"
27 - v-model:selected="selectedIntegration"
27 + :disabled-ids-list="disabledIdsList"
28 class="px-7"
29 />
30 </n-scrollbar>
31 </div>
32 - <div class="auth-key-form px-7 flex flex-wrap gap-3" v-else>
32 + <div v-else class="auth-key-form px-7 flex flex-wrap gap-3">
33 <template v-for="ak of authKeysForm" :key="ak.key">
34 - <n-form-item :label="ak.key" required class="grow" v-if="ak.type === 'string'">
34 + <n-form-item v-if="ak.type === 'string'" :label="ak.key" required class="grow">
35 <n-input v-model:value="ak.value" :placeholder="`Input ${ak.key}...`" clearable />
36 </n-form-item>
37 - <n-form-item :label="ak.key" required class="grow" v-if="ak.type === 'selectType'">
37 + <n-form-item v-if="ak.type === 'selectType'" :label="ak.key" required class="grow">
38 <n-select
39 v-model:value="ak.value"
40 :options="apiTypeOptions"
@@ -53,24 +53,24 @@
53 <n-button @click="close()">Close</n-button>
54 </div>
55 <div class="flex gap-4">
56 - <n-button @click="prev()" v-if="isPrevStepEnabled">
56 + <n-button v-if="isPrevStepEnabled" @click="prev()">
57 <template #icon>
58 <Icon :name="ArrowLeftIcon"></Icon>
59 </template>
60 Prev
61 </n-button>
62 - <n-button @click="next()" v-if="isNextStepShown" :disabled="!isNextStepEnabled" icon-placement="right">
62 + <n-button v-if="isNextStepShown" :disabled="!isNextStepEnabled" icon-placement="right" @click="next()">
63 <template #icon>
64 <Icon :name="ArrowRightIcon"></Icon>
65 </template>
66 Next
67 </n-button>
68 <n-button
69 + v-if="isSubmitEnabled"
70 type="primary"
70 - @click="submit()"
71 :disabled="!isSubmitValid"
72 :loading="loading"
73 - v-if="isSubmitEnabled"
73 + @click="submit()"
74 >
75 Submit
76 </n-button>
@@ -80,13 +80,13 @@
80 </template>
81
82 <script setup lang="ts">
83 -import { ref, computed, watch } from "vue"
84 -import { useMessage, NFormItem, NInput, NSelect, NButton, NScrollbar, NSteps, NStep, type StepsProps } from "naive-ui"
85 -import Icon from "@/components/common/Icon.vue"
86 -import Api from "@/api"
87 -import IntegrationsList from "@/components/integrations/IntegrationsList.vue"
83 import type { NewIntegration } from "@/api/endpoints/integrations"
84 import type { ServiceItemData } from "@/components/services/types"
85 +import Api from "@/api"
86 +import Icon from "@/components/common/Icon.vue"
87 +import IntegrationsList from "@/components/integrations/IntegrationsList.vue"
88 +import { NButton, NFormItem, NInput, NScrollbar, NSelect, NStep, NSteps, type StepsProps, useMessage } from "naive-ui"
89 +import { computed, ref, watch } from "vue"
90
91 interface AuthKeysInput {
92 key: string
frontend/src/components/customers/integrations/CustomerIntegrationItem.vue
+19 -13
@@ -19,12 +19,14 @@
19 </div>
20 <div class="main-box flex items-center gap-3">
21 <div class="content flex flex-col gap-1 grow">
22 - <div class="title">{{ serviceName }}</div>
22 + <div class="title">
23 + {{ serviceName }}
24 + </div>
25 </div>
26 <CustomerIntegrationActions
27 class="actions-box"
28 :integration="integration"
27 - hideDeleteButton
29 + hide-delete-button
30 @deployed="emit('deployed')"
31 @deleted="emit('deleted')"
32 />
@@ -33,10 +35,10 @@
35 <CustomerIntegrationActions
36 class="actions-box"
37 :integration="integration"
36 - hideDeleteButton
38 + hide-delete-button
39 + size="small"
40 @deployed="emit('deployed')"
41 @deleted="emit('deleted')"
39 - :size="'small'"
42 />
43 </div>
44 </div>
@@ -51,8 +53,12 @@
53 >
54 <div class="grid gap-2 grid-auto-fit-200">
55 <KVCard v-for="ak of authKeys" :key="ak.key">
54 - <template #key>{{ ak.key }}</template>
55 - <template #value>{{ ak.value || "-" }}</template>
56 + <template #key>
57 + {{ ak.key }}
58 + </template>
59 + <template #value>
60 + {{ ak.value || "-" }}
61 + </template>
62 </KVCard>
63 </div>
64 </n-modal>
@@ -60,26 +66,26 @@
66 </template>
67
68 <script setup lang="ts">
63 -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"
69 import type { CustomerIntegration } from "@/types/integrations.d"
68 -import CustomerIntegrationActions from "./CustomerIntegrationActions.vue"
70 +import Badge from "@/components/common/Badge.vue"
71 +import Icon from "@/components/common/Icon.vue"
72 import KVCard from "@/components/common/KVCard.vue"
73 import _uniqBy from "lodash/uniqBy"
74 +import { NButton, NModal } from "naive-ui"
75 +import { computed, ref, toRefs } from "vue"
76 +import CustomerIntegrationActions from "./CustomerIntegrationActions.vue"
77
78 const props = defineProps<{
79 integration: CustomerIntegration
80 embedded?: boolean
81 }>()
76 -const { integration, embedded } = toRefs(props)
77 -
82 const emit = defineEmits<{
83 (e: "deployed"): void
84 (e: "deleted"): void
85 }>()
86
87 +const { integration, embedded } = toRefs(props)
88 +
89 const DeployIcon = "carbon:deploy"
90 const InfoIcon = "carbon:information"
91
frontend/src/components/customers/integrations/CustomerIntegrations.vue
+11 -11
@@ -3,16 +3,16 @@
3 <transition name="form-fade" mode="out-in">
4 <div v-if="showForm">
5 <CustomerIntegrationForm
6 - :customerCode="customerCode"
7 - :customerName="customerName"
8 - :disabledIdsList="disabledIds"
6 + :customer-code="customerCode"
7 + :customer-name="customerName"
8 + :disabled-ids-list="disabledIds"
9 @submitted="refreshList()"
10 @close="closeForm()"
11 />
12 </div>
13 <div v-else>
14 <div class="flex items-center justify-between gap-4 px-7 pt-2">
15 - <n-button size="small" @click="openForm()" type="primary">
15 + <n-button size="small" type="primary" @click="openForm()">
16 <template #icon>
17 <Icon :name="AddIcon" :size="14"></Icon>
18 </template>
@@ -27,14 +27,14 @@
27 v-for="integration of list"
28 :key="integration.id"
29 :integration="integration"
30 - @deployed="refreshList()"
31 - @deleted="refreshList()"
30 embedded
31 class="item-appear item-appear-bottom item-appear-005 mb-2"
32 + @deployed="refreshList()"
33 + @deleted="refreshList()"
34 />
35 </template>
36 <template v-else>
37 - <n-empty description="No integrations found" class="justify-center h-48" v-if="!loading" />
37 + <n-empty v-if="!loading" description="No integrations found" class="justify-center h-48" />
38 </template>
39 </div>
40 </n-spin>
@@ -44,13 +44,13 @@
44 </template>
45
46 <script setup lang="ts">
47 -import { ref, onBeforeMount, computed } from "vue"
48 -import { useMessage, NSpin, NEmpty, NButton } from "naive-ui"
49 -import Icon from "@/components/common/Icon.vue"
47 +import type { CustomerIntegration } from "@/types/integrations.d"
48 import Api from "@/api"
49 +import Icon from "@/components/common/Icon.vue"
50 +import { NButton, NEmpty, NSpin, useMessage } from "naive-ui"
51 +import { computed, onBeforeMount, ref } from "vue"
52 import CustomerIntegrationForm from "./CustomerIntegrationForm.vue"
53 import CustomerIntegrationItem from "./CustomerIntegrationItem.vue"
53 -import type { CustomerIntegration } from "@/types/integrations.d"
54
55 const { customerCode, customerName } = defineProps<{
56 customerCode: string
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorActions.vue
+26 -20
@@ -1,36 +1,40 @@
1 <template>
2 <div class="alert-actions flex gap-4 justify-end">
3 <n-button
4 + v-if="networkConnector.deployed"
5 :loading="loadingDecommission"
6 type="error"
6 - v-if="networkConnector.deployed"
7 :size="size"
8 secondary
9 @click="decommissionNetworkConnector()"
10 >
11 - <template #icon><Icon :name="DecommissionIcon"></Icon></template>
11 + <template #icon>
12 + <Icon :name="DecommissionIcon"></Icon>
13 + </template>
14 Decommission
15 </n-button>
16
17 <n-button
18 + v-if="isFortinet && !networkConnector.deployed"
19 :loading="loadingFortinetProvision"
20 type="success"
18 - v-if="isFortinet && !networkConnector.deployed"
21 :size="size"
22 secondary
23 @click="showFortinetForm = true"
24 >
23 - <template #icon><Icon :name="DeployIcon"></Icon></template>
25 + <template #icon>
26 + <Icon :name="DeployIcon"></Icon>
27 + </template>
28 Deploy
29 </n-button>
30
31 <n-button
32 + v-if="!hideDeleteButton"
33 :size="size"
34 type="error"
35 ghost
31 - @click="handleDelete"
36 :loading="loadingDelete"
33 - v-if="!hideDeleteButton"
37 + @click="handleDelete"
38 >
39 <template #icon>
40 <Icon :name="DeleteIcon" :size="15"></Icon>
@@ -55,12 +59,14 @@
59 <div class="flex justify-end">
60 <n-button
61 :loading="loadingFortinetProvision"
58 - @click="fortinetProvision()"
62 type="success"
63 secondary
64 :disabled="!isFortinetFormValid"
65 + @click="fortinetProvision()"
66 >
63 - <template #icon><Icon :name="DeployIcon"></Icon></template>
67 + <template #icon>
68 + <Icon :name="DeployIcon"></Icon>
69 + </template>
70 Deploy
71 </n-button>
72 </div>
@@ -70,15 +76,21 @@
76 </template>
77
78 <script setup lang="ts">
73 -import { computed, h, watch, ref } from "vue"
74 -import { NButton, NModal, NSpin, useDialog, useMessage } from "naive-ui"
75 -import Icon from "@/components/common/Icon.vue"
76 -import Api from "@/api"
77 -import type { Size } from "naive-ui/es/button/src/interface"
78 -import type { CustomerNetworkConnector } from "@/types/networkConnectors.d"
79 import type { FortinetProvision } from "@/api/endpoints/networkConnectors"
80 +import type { CustomerNetworkConnector } from "@/types/networkConnectors.d"
81 +import type { Size } from "naive-ui/es/button/src/interface"
82 +import Api from "@/api"
83 +import Icon from "@/components/common/Icon.vue"
84 +import { NButton, NModal, NSpin, useDialog, useMessage } from "naive-ui"
85 +import { computed, h, ref, watch } from "vue"
86 import FortinetForm, { type FortinetModel } from "./provisions/FortinetForm.vue"
87
88 +const { networkConnector, hideDeleteButton, size } = defineProps<{
89 + networkConnector: CustomerNetworkConnector
90 + hideDeleteButton?: boolean
91 + size?: Size
92 +}>()
93 +
94 const emit = defineEmits<{
95 (e: "startLoading"): void
96 (e: "stopLoading"): void
@@ -87,12 +99,6 @@ const emit = defineEmits<{
99 (e: "deleted"): void
100 }>()
101
90 -const { networkConnector, hideDeleteButton, size } = defineProps<{
91 - networkConnector: CustomerNetworkConnector
92 - hideDeleteButton?: boolean
93 - size?: Size
94 -}>()
95 -
102 const DeployIcon = "carbon:deploy"
103 const DeleteIcon = "ph:trash"
104 const DecommissionIcon = "carbon:delete"
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorForm.vue
+16 -16
@@ -7,7 +7,7 @@
7 <n-step title="Choose Network Connector" />
8 <n-step title="Set Auth Keys">
9 <template #icon>
10 - <Icon :name="SkipIcon" v-if="!isAuthKeysStepEnabled"></Icon>
10 + <Icon v-if="!isAuthKeysStepEnabled" :name="SkipIcon"></Icon>
11 </template>
12 </n-step>
13 </n-steps>
@@ -17,24 +17,24 @@
17
18 <div class="flex flex-col grow overflow-hidden">
19 <Transition :name="`slide-form-${slideFormDirection}`">
20 - <div class="available-list grow overflow-hidden" v-if="current === 1">
20 + <div v-if="current === 1" class="available-list grow overflow-hidden">
21 <n-scrollbar style="max-height: 355px" trigger="none">
22 <NetworkConnectorsList
23 + v-model:selected="selectedNetworkConnector"
24 embedded
25 hide-totals
26 selectable
26 - :disabledIdsList="disabledIdsList"
27 - v-model:selected="selectedNetworkConnector"
27 + :disabled-ids-list="disabledIdsList"
28 class="px-7"
29 />
30 </n-scrollbar>
31 </div>
32 - <div class="auth-key-form px-7 flex flex-wrap gap-3" v-else>
32 + <div v-else class="auth-key-form px-7 flex flex-wrap gap-3">
33 <template v-for="ak of authKeysForm" :key="ak.key">
34 - <n-form-item :label="ak.key" required class="grow" v-if="ak.type === 'string'">
34 + <n-form-item v-if="ak.type === 'string'" :label="ak.key" required class="grow">
35 <n-input v-model:value="ak.value" :placeholder="`Input ${ak.key}...`" clearable />
36 </n-form-item>
37 - <n-form-item :label="ak.key" required class="grow" v-if="ak.type === 'selectType'">
37 + <n-form-item v-if="ak.type === 'selectType'" :label="ak.key" required class="grow">
38 <n-select
39 v-model:value="ak.value"
40 :options="apiTypeOptions"
@@ -53,24 +53,24 @@
53 <n-button @click="close()">Close</n-button>
54 </div>
55 <div class="flex gap-4">
56 - <n-button @click="prev()" v-if="isPrevStepEnabled">
56 + <n-button v-if="isPrevStepEnabled" @click="prev()">
57 <template #icon>
58 <Icon :name="ArrowLeftIcon"></Icon>
59 </template>
60 Prev
61 </n-button>
62 - <n-button @click="next()" v-if="isNextStepShown" :disabled="!isNextStepEnabled" icon-placement="right">
62 + <n-button v-if="isNextStepShown" :disabled="!isNextStepEnabled" icon-placement="right" @click="next()">
63 <template #icon>
64 <Icon :name="ArrowRightIcon"></Icon>
65 </template>
66 Next
67 </n-button>
68 <n-button
69 + v-if="isSubmitEnabled"
70 type="primary"
70 - @click="submit()"
71 :disabled="!isSubmitValid"
72 :loading="loading"
73 - v-if="isSubmitEnabled"
73 + @click="submit()"
74 >
75 Submit
76 </n-button>
@@ -80,13 +80,13 @@
80 </template>
81
82 <script setup lang="ts">
83 -import { ref, computed, watch } from "vue"
84 -import { useMessage, NFormItem, NInput, NSelect, NButton, NScrollbar, NSteps, NStep, type StepsProps } from "naive-ui"
85 -import Icon from "@/components/common/Icon.vue"
83 +import type { NewNetworkConnector } from "@/api/endpoints/networkConnectors"
84 +import type { ServiceItemData } from "@/components/services/types"
85 import Api from "@/api"
86 +import Icon from "@/components/common/Icon.vue"
87 import NetworkConnectorsList from "@/components/networkConnectors/NetworkConnectorsList.vue"
88 -import type { ServiceItemData } from "@/components/services/types"
89 -import type { NewNetworkConnector } from "@/api/endpoints/networkConnectors"
88 +import { NButton, NFormItem, NInput, NScrollbar, NSelect, NStep, NSteps, type StepsProps, useMessage } from "naive-ui"
89 +import { computed, ref, watch } from "vue"
90
91 interface AuthKeysInput {
92 key: string
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorItem.vue
+21 -15
@@ -19,12 +19,14 @@
19 </div>
20 <div class="main-box flex items-center gap-3">
21 <div class="content flex flex-col gap-1 grow">
22 - <div class="title">{{ serviceName }}</div>
22 + <div class="title">
23 + {{ serviceName }}
24 + </div>
25 </div>
26 <CustomerNetworkConnectorActions
27 class="actions-box"
26 - :networkConnector="networkConnector"
27 - hideDeleteButton
28 + :network-connector="networkConnector"
29 + hide-delete-button
30 @deployed="emit('deployed')"
31 @deleted="emit('deleted')"
32 @decommissioned="emit('decommissioned')"
@@ -33,12 +35,12 @@
35 <div class="footer-box flex justify-between items-center gap-4">
36 <CustomerNetworkConnectorActions
37 class="actions-box"
36 - :networkConnector="networkConnector"
37 - hideDeleteButton
38 + :network-connector="networkConnector"
39 + hide-delete-button
40 + size="small"
41 @deployed="emit('deployed')"
42 @deleted="emit('deleted')"
43 @decommissioned="emit('decommissioned')"
41 - :size="'small'"
44 />
45 </div>
46 </div>
@@ -53,8 +55,12 @@
55 >
56 <div class="grid gap-2 grid-auto-fit-200">
57 <KVCard v-for="ak of authKeys" :key="ak.key">
56 - <template #key>{{ ak.key }}</template>
57 - <template #value>{{ ak.value || "-" }}</template>
58 + <template #key>
59 + {{ ak.key }}
60 + </template>
61 + <template #value>
62 + {{ ak.value || "-" }}
63 + </template>
64 </KVCard>
65 </div>
66 </n-modal>
@@ -62,27 +68,27 @@
68 </template>
69
70 <script setup lang="ts">
65 -import { computed, ref, toRefs } from "vue"
66 -import { NModal, NButton } from "naive-ui"
67 -import CustomerNetworkConnectorActions from "./CustomerNetworkConnectorActions.vue"
68 -import Icon from "@/components/common/Icon.vue"
71 +import type { CustomerNetworkConnector } from "@/types/networkConnectors.d"
72 import Badge from "@/components/common/Badge.vue"
73 +import Icon from "@/components/common/Icon.vue"
74 import KVCard from "@/components/common/KVCard.vue"
75 import _uniqBy from "lodash/uniqBy"
72 -import type { CustomerNetworkConnector } from "@/types/networkConnectors.d"
76 +import { NButton, NModal } from "naive-ui"
77 +import { computed, ref, toRefs } from "vue"
78 +import CustomerNetworkConnectorActions from "./CustomerNetworkConnectorActions.vue"
79
80 const props = defineProps<{
81 networkConnector: CustomerNetworkConnector
82 embedded?: boolean
83 }>()
78 -const { networkConnector, embedded } = toRefs(props)
79 -
84 const emit = defineEmits<{
85 (e: "deployed"): void
86 (e: "decommissioned"): void
87 (e: "deleted"): void
88 }>()
89
90 +const { networkConnector, embedded } = toRefs(props)
91 +
92 const DeployIcon = "carbon:deploy"
93 const InfoIcon = "carbon:information"
94
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectors.vue
+12 -12
@@ -3,16 +3,16 @@
3 <transition name="form-fade" mode="out-in">
4 <div v-if="showForm">
5 <CustomerNetworkConnectorForm
6 - :customerCode="customerCode"
7 - :customerName="customerName"
8 - :disabledIdsList="disabledIds"
6 + :customer-code="customerCode"
7 + :customer-name="customerName"
8 + :disabled-ids-list="disabledIds"
9 @submitted="refreshList()"
10 @close="closeForm()"
11 />
12 </div>
13 <div v-else>
14 <div class="flex items-center justify-between gap-4 px-7 pt-2">
15 - <n-button size="small" @click="openForm()" type="primary">
15 + <n-button size="small" type="primary" @click="openForm()">
16 <template #icon>
17 <Icon :name="AddIcon" :size="14"></Icon>
18 </template>
@@ -26,19 +26,19 @@
26 <CustomerNetworkConnectorItem
27 v-for="networkConnector of list"
28 :key="networkConnector.id"
29 - :networkConnector="networkConnector"
29 + :network-connector="networkConnector"
30 + embedded
31 + class="item-appear item-appear-bottom item-appear-005 mb-2"
32 @deployed="refreshList()"
33 @deleted="refreshList()"
34 @decommissioned="refreshList()"
33 - embedded
34 - class="item-appear item-appear-bottom item-appear-005 mb-2"
35 />
36 </template>
37 <template v-else>
38 <n-empty
39 + v-if="!loading"
40 description="No network connectors found"
41 class="justify-center h-48"
41 - v-if="!loading"
42 />
43 </template>
44 </div>
@@ -49,13 +49,13 @@
49 </template>
50
51 <script setup lang="ts">
52 -import { ref, onBeforeMount, computed } from "vue"
53 -import { useMessage, NSpin, NEmpty, NButton } from "naive-ui"
54 -import Icon from "@/components/common/Icon.vue"
52 +import type { CustomerNetworkConnector } from "@/types/networkConnectors.d"
53 import Api from "@/api"
54 +import Icon from "@/components/common/Icon.vue"
55 +import { NButton, NEmpty, NSpin, useMessage } from "naive-ui"
56 +import { computed, onBeforeMount, ref } from "vue"
57 import CustomerNetworkConnectorForm from "./CustomerNetworkConnectorForm.vue"
58 import CustomerNetworkConnectorItem from "./CustomerNetworkConnectorItem.vue"
58 -import type { CustomerNetworkConnector } from "@/types/networkConnectors.d"
59
60 const { customerCode, customerName } = defineProps<{
61 customerCode: string
frontend/src/components/customers/networkConnectors/provisions/FortinetForm.vue
+2 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form class="flex flex-col gap-5" :model="options" :rules="rules" ref="formRef">
2 + <n-form class="flex flex-col gap-5" :model="options" :rules="rules">
3 <n-form-item label="Protocol" label-placement="left" :show-feedback="false" size="small" path="protocol">
4 <n-select v-model:value="options.protocol" :options="protocolOptions" />
5 </n-form-item>
@@ -27,7 +27,7 @@
27 </template>
28
29 <script setup lang="ts">
30 -import { NFormItem, NSelect, NForm, NInputNumber, type FormRules, type FormItemRule } from "naive-ui"
30 +import { type FormItemRule, type FormRules, NForm, NFormItem, NInputNumber, NSelect } from "naive-ui"
31
32 export interface FortinetModel {
33 protocol: "tcp" | "udp"
frontend/src/components/customers/notifications/CustomerNotificationsWorkflows.vue
+14 -15
@@ -3,12 +3,12 @@
3 <transition name="form-fade" mode="out-in">
4 <div v-if="showForm" class="p-7 pt-4">
5 <CustomerNotificationsWorkflowsForm
6 - :customerCode
6 + :customer-code
7 @mounted="formCTX = $event"
8 @submitted="refreshList()"
9 >
10 - <template #additionalActions="{ loading }">
11 - <n-button @click="closeForm()" :disabled="loading">Close</n-button>
10 + <template #additionalActions="{ loading: loadingForm }">
11 + <n-button :disabled="loadingForm" @click="closeForm()">Close</n-button>
12 </template>
13 </CustomerNotificationsWorkflowsForm>
14 </div>
@@ -19,19 +19,19 @@
19 <CustomerNotificationsWorkflowsItem
20 v-for="item of list"
21 :key="item.id"
22 - :incidentNotification="item"
22 + :incident-notification="item"
23 embedded
24 - @updated="getCustomerNetworkConnectors()"
24 class="item-appear item-appear-bottom item-appear-005 mb-2"
25 + @updated="getCustomerNetworkConnectors()"
26 />
27 </div>
28 </template>
29 <template v-else>
30 - <n-empty class="justify-center h-48" v-if="!loading">
30 + <n-empty v-if="!loading" class="justify-center h-48">
31 <div class="flex flex-col items-center gap-4">
32 <p>No Notification found</p>
33
34 - <n-button size="small" @click="openForm()" type="primary">
34 + <n-button size="small" type="primary" @click="openForm()">
35 <template #icon>
36 <Icon :name="AddIcon" :size="14"></Icon>
37 </template>
@@ -47,12 +47,15 @@
47 </template>
48
49 <script setup lang="ts">
50 -import { ref, onBeforeMount, defineAsyncComponent } from "vue"
51 -import { useMessage, NSpin, NEmpty, NButton } from "naive-ui"
52 -import Icon from "@/components/common/Icon.vue"
53 -import Api from "@/api"
50 import type { IncidentNotification } from "@/types/incidentManagement/notifications.d"
51 +import Api from "@/api"
52 +import Icon from "@/components/common/Icon.vue"
53 +import { NButton, NEmpty, NSpin, useMessage } from "naive-ui"
54 +import { defineAsyncComponent, onBeforeMount, ref } from "vue"
55
56 +const { customerCode } = defineProps<{
57 + customerCode: string
58 +}>()
59 const CustomerNotificationsWorkflowsItem = defineAsyncComponent(
60 () => import("./CustomerNotificationsWorkflowsItem.vue")
61 )
@@ -60,10 +63,6 @@ const CustomerNotificationsWorkflowsForm = defineAsyncComponent(
63 () => import("./CustomerNotificationsWorkflowsForm.vue")
64 )
65
63 -const { customerCode } = defineProps<{
64 - customerCode: string
65 -}>()
66 -
66 const AddIcon = "carbon:add-alt"
67
68 const message = useMessage()
frontend/src/components/customers/notifications/CustomerNotificationsWorkflowsForm.vue
+16 -16
@@ -2,7 +2,7 @@
2 <div class="customer-notifications-workflows-form flex flex-col justify-between grow">
3 <div class="form-box">
4 <n-spin v-model:show="loading">
5 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
5 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
6 <n-form-item label="Enabled" path="enabled">
7 <n-switch v-model:value="form.enabled" />
8 </n-form-item>
@@ -21,35 +21,40 @@
21 <slot name="additionalActions" :loading></slot>
22 </div>
23 <div class="flex gap-3">
24 - <n-button @click="reset()" secondary :disabled="loading">Reset</n-button>
25 - <n-button type="primary" :disabled="!isValid" @click="validate()" :loading="loading">Submit</n-button>
24 + <n-button secondary :disabled="loading" @click="reset()">Reset</n-button>
25 + <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">Submit</n-button>
26 </div>
27 </div>
28 </div>
29 </template>
30
31 <script setup lang="ts">
32 -import { computed, onMounted, ref, watch } from "vue"
32 +import type { IncidentNotification, IncidentNotificationPayload } from "@/types/incidentManagement/notifications.d"
33 +import Api from "@/api"
34 import {
35 + type FormInst,
36 + type FormRules,
37 + type FormValidationError,
38 NButton,
39 NForm,
40 NFormItem,
41 NInput,
38 - NSwitch,
42 NSpin,
40 - useMessage,
41 - type FormRules,
42 - type FormInst,
43 - type FormValidationError
43 + NSwitch,
44 + useMessage
45 } from "naive-ui"
45 -import Api from "@/api"
46 -import type { IncidentNotification, IncidentNotificationPayload } from "@/types/incidentManagement/notifications.d"
46 +import { computed, onMounted, ref, watch } from "vue"
47
48 interface IncidentNotificationForm {
49 shuffle_workflow_id: string
50 enabled: boolean
51 }
52
53 +const { incidentNotification, customerCode } = defineProps<{
54 + incidentNotification?: IncidentNotification
55 + customerCode: string
56 +}>()
57 +
58 const emit = defineEmits<{
59 (e: "submitted", value: IncidentNotification): void
60 (e: "startLoading"): void
@@ -62,11 +67,6 @@ const emit = defineEmits<{
67 ): void
68 }>()
69
65 -const { incidentNotification, customerCode } = defineProps<{
66 - incidentNotification?: IncidentNotification
67 - customerCode: string
68 -}>()
69 -
70 const message = useMessage()
71 const form = ref<IncidentNotificationForm>(getClearForm(incidentNotification))
72 const formRef = ref<FormInst | null>(null)
frontend/src/components/customers/notifications/CustomerNotificationsWorkflowsItem.vue
+16 -13
@@ -6,17 +6,19 @@
6 <div class="status flex gap-2 items-center">
7 <span>{{ incidentNotification.enabled ? "Enabled" : "Disabled" }}</span>
8 <Icon
9 + v-if="incidentNotification.enabled"
10 :name="EnabledIcon"
11 :size="14"
12 class="text-success-color"
12 - v-if="incidentNotification.enabled"
13 ></Icon>
14 - <Icon :name="DisabledIcon" :size="14" class="text-secondary-color" v-else></Icon>
14 + <Icon v-else :name="DisabledIcon" :size="14" class="text-secondary-color"></Icon>
15 </div>
16 </div>
17 <div class="main-box flex items-center gap-3">
18 <div class="content flex flex-col grow">
19 - <div class="title">{{ incidentNotification.shuffle_workflow_id }}</div>
19 + <div class="title">
20 + {{ incidentNotification.shuffle_workflow_id }}
21 + </div>
22 </div>
23 </div>
24 </div>
@@ -31,8 +33,8 @@
33 segmented
34 >
35 <CustomerNotificationsWorkflowsForm
34 - :incidentNotification
35 - :customerCode="incidentNotification.customer_code"
36 + :incident-notification
37 + :customer-code="incidentNotification.customer_code"
38 @mounted="formCTX = $event"
39 @submitted="emitUpdate"
40 />
@@ -41,25 +43,26 @@
43 </template>
44
45 <script setup lang="ts">
44 -import { defineAsyncComponent, ref, toRefs, watch } from "vue"
45 -import { NModal } from "naive-ui"
46 -import Icon from "@/components/common/Icon.vue"
46 import type { IncidentNotification } from "@/types/incidentManagement/notifications.d"
48 -
49 -const CustomerNotificationsWorkflowsForm = defineAsyncComponent(
50 - () => import("./CustomerNotificationsWorkflowsForm.vue")
51 -)
47 +import Icon from "@/components/common/Icon.vue"
48 +import { NModal } from "naive-ui"
49 +import { defineAsyncComponent, ref, toRefs, watch } from "vue"
50
51 const props = defineProps<{
52 incidentNotification: IncidentNotification
53 embedded?: boolean
54 }>()
57 -const { incidentNotification, embedded } = toRefs(props)
55
56 const emit = defineEmits<{
57 (e: "updated", value: IncidentNotification): void
58 }>()
59
60 +const CustomerNotificationsWorkflowsForm = defineAsyncComponent(
61 + () => import("./CustomerNotificationsWorkflowsForm.vue")
62 +)
63 +
64 +const { incidentNotification, embedded } = toRefs(props)
65 +
66 const EnabledIcon = "carbon:circle-solid"
67 const DisabledIcon = "carbon:subtract-alt"
68
frontend/src/components/customers/provision/CustomerDefaultSettingsButton.vue
+4 -4
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button size="small" secondary @click="showForm = true" :loading="loading">
2 + <n-button size="small" secondary :loading="loading" @click="showForm = true">
3 <template #icon>
4 <Icon :name="SettingsIcon" :size="14"></Icon>
5 </template>
@@ -15,14 +15,14 @@
15 :bordered="false"
16 segmented
17 >
18 - <CustomerDefaultSettingsForm @mounted="settingsFormCTX = $event" v-model:loading="loading" />
18 + <CustomerDefaultSettingsForm v-model:loading="loading" @mounted="settingsFormCTX = $event" />
19 </n-modal>
20 </template>
21
22 <script setup lang="ts">
23 -import { ref, watch } from "vue"
24 -import { NButton, NModal } from "naive-ui"
23 import Icon from "@/components/common/Icon.vue"
24 +import { NButton, NModal } from "naive-ui"
25 +import { ref, watch } from "vue"
26 import CustomerDefaultSettingsForm from "./CustomerDefaultSettingsForm.vue"
27
28 const SettingsIcon = "carbon:settings-edit"
frontend/src/components/customers/provision/CustomerDefaultSettingsForm.vue
+14 -14
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="customer-provisioning-default-settings-form">
3 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 <div v-for="(_, key) of form" :key="key" class="grow">
@@ -18,12 +18,12 @@
18 <slot name="additionalActions"></slot>
19 </div>
20 <div class="flex gap-4">
21 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
21 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
22 <n-button
23 type="primary"
24 :disabled="!isValid"
25 - @click="validate()"
25 :loading="submittingDefaultSettings"
26 + @click="validate()"
27 >
28 Submit
29 </n-button>
@@ -35,25 +35,25 @@
35 </template>
36
37 <script setup lang="ts">
38 -import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
38 +import type { CustomerProvisioningDefaultSettings } from "@/types/customers.d"
39 import Api from "@/api"
40 +import _get from "lodash/get"
41 +import _trim from "lodash/trim"
42 import {
41 - useMessage,
43 + type FormInst,
44 + type FormItemRule,
45 + type FormRules,
46 + type FormValidationError,
47 + NButton,
48 NForm,
49 NFormItem,
50 NInput,
45 - NButton,
51 NSpin,
47 - type FormValidationError,
48 - type FormInst,
49 - type FormRules,
50 - type FormItemRule
52 + useMessage
53 } from "naive-ui"
52 -import type { CustomerProvisioningDefaultSettings } from "@/types/customers.d"
53 -import _trim from "lodash/trim"
54 -import _get from "lodash/get"
55 -import isURL from "validator/es/lib/isURL"
54 import isIP from "validator/es/lib/isIP"
55 +import isURL from "validator/es/lib/isURL"
56 +import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
57
58 const emit = defineEmits<{
59 (e: "update:loading", value: boolean): void
frontend/src/components/customers/provision/CustomerProvision.vue
+22 -17
@@ -3,9 +3,9 @@
3 <transition name="form-fade" mode="out-in">
4 <div v-if="editing">
5 <CustomerProvisionWizard
6 + :customer-name="customerNameSanitized"
7 + :customer-code="customerCode"
8 @submitted="submitted"
7 - :customerName="customerNameSanitized"
8 - :customerCode="customerCode"
9 >
10 <template #additionalActions>
11 <n-button @click="editing = false">Close</n-button>
@@ -13,16 +13,16 @@
13 </CustomerProvisionWizard>
14 </div>
15 <div v-else>
16 - <div class="flex items-center justify-end gap-4 px-7 pt-2" v-if="customerMeta">
17 - <n-button size="small" type="error" ghost @click="handleDelete" :loading="loadingDelete">
16 + <div v-if="customerMeta" class="flex items-center justify-end gap-4 px-7 pt-2">
17 + <n-button size="small" type="error" ghost :loading="loadingDelete" @click="handleDelete">
18 <template #icon>
19 <Icon :name="DeleteIcon" :size="15"></Icon>
20 </template>
21 Decommission
22 </n-button>
23 </div>
24 - <div class="flex items-center justify-between gap-4 px-7 pt-2" v-else>
25 - <n-button size="small" @click="editing = true" type="primary">
24 + <div v-else class="flex items-center justify-between gap-4 px-7 pt-2">
25 + <n-button size="small" type="primary" @click="editing = true">
26 <template #icon>
27 <Icon :name="AddIcon" :size="14"></Icon>
28 </template>
@@ -32,8 +32,12 @@
32
33 <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
34 <KVCard v-for="(value, key) of customerMeta" :key="key">
35 - <template #key>{{ key }}</template>
36 - <template #value>{{ value || "-" }}</template>
35 + <template #key>
36 + {{ key }}
37 + </template>
38 + <template #value>
39 + {{ value || "-" }}
40 + </template>
41 </KVCard>
42 </div>
43 </div>
@@ -42,24 +46,25 @@
46 </template>
47
48 <script setup lang="ts">
49 +import type { CustomerMeta } from "@/types/customers.d"
50 +import Api from "@/api"
51 import Icon from "@/components/common/Icon.vue"
46 -import { computed, h, ref, toRefs } from "vue"
52 import KVCard from "@/components/common/KVCard.vue"
53 +import { NButton, useDialog, useMessage } from "naive-ui"
54 +import { computed, h, ref, toRefs } from "vue"
55 import CustomerProvisionWizard from "./CustomerProvisionWizard.vue"
49 -import Api from "@/api"
50 -import { useMessage, NButton, useDialog } from "naive-ui"
51 -import type { CustomerMeta } from "@/types/customers.d"
52 -
53 -const emit = defineEmits<{
54 - (e: "delete"): void
55 - (e: "submitted", value: CustomerMeta): void
56 -}>()
56
57 const props = defineProps<{
58 customerMeta?: CustomerMeta | null
59 customerName?: string | null
60 customerCode: string
61 }>()
62 +
63 +const emit = defineEmits<{
64 + (e: "delete"): void
65 + (e: "submitted", value: CustomerMeta): void
66 +}>()
67 +
68 const { customerMeta, customerCode, customerName } = toRefs(props)
69
70 const DeleteIcon = "ph:trash"
frontend/src/components/customers/provision/CustomerProvisionWizard.vue
+31 -31
@@ -10,19 +10,19 @@
10 <n-step title="Subscription" />
11 <n-step title="Infrastructure">
12 <template #icon>
13 - <Icon :name="SkipIcon" v-if="!isInfrastructureEnabled"></Icon>
13 + <Icon v-if="!isInfrastructureEnabled" :name="SkipIcon"></Icon>
14 </template>
15 </n-step>
16 <n-step title="Wazuh Worker">
17 <template #icon>
18 - <Icon :name="SkipIcon" v-if="!isWazuhStepEnabled"></Icon>
18 + <Icon v-if="!isWazuhStepEnabled" :name="SkipIcon"></Icon>
19 </template>
20 </n-step>
21 </n-steps>
22 </div>
23 </n-scrollbar>
24
25 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef" class="form-container mt-4">
25 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules" class="form-container mt-4">
26 <Transition :name="`slide-form-${slideFormDirection}`">
27 <div v-if="current === 1" class="px-7 flex flex-col gap-3">
28 <div class="flex flex-wrap gap-3">
@@ -55,10 +55,10 @@
55 />
56 </n-form-item>
57 <n-form-item
58 + v-if="showDfirIrisUsername"
59 label="DFIR-IRIS Username"
60 path="dfir_iris_username"
61 class="grow"
61 - v-if="showDfirIrisUsername"
62 >
63 <n-input
64 v-model:value.trim="form.dfir_iris_username"
@@ -126,8 +126,8 @@
126 </div>
127 <div>
128 <n-button
129 - size="tiny"
129 v-if="dashboardOptions.length"
130 + size="tiny"
131 @click="toggleDashboards()"
132 >
133 {{ allDashboardsSelected ? "Deselect All" : "Select All" }}
@@ -224,19 +224,19 @@
224 <slot name="additionalActions"></slot>
225 </div>
226 <div class="flex gap-4">
227 - <n-button @click="prev()" v-if="isPrevStepEnabled">
227 + <n-button v-if="isPrevStepEnabled" @click="prev()">
228 <template #icon>
229 <Icon :name="ArrowLeftIcon"></Icon>
230 </template>
231 Prev
232 </n-button>
233 - <n-button @click="next()" v-if="isNextStepEnabled" icon-placement="right">
233 + <n-button v-if="isNextStepEnabled" icon-placement="right" @click="next()">
234 <template #icon>
235 <Icon :name="ArrowRightIcon"></Icon>
236 </template>
237 Next
238 </n-button>
239 - <n-button type="primary" @click="validate(submit)" :loading="loading" v-if="isSubmitEnabled">
239 + <n-button v-if="isSubmitEnabled" type="primary" :loading="loading" @click="validate(submit)">
240 Submit
241 </n-button>
242 </div>
@@ -246,40 +246,34 @@
246 </template>
247
248 <script setup lang="ts">
249 -import { computed, ref, toRefs } from "vue"
249 +import type { CustomerMeta, CustomerProvision, CustomerProvisioningDefaultSettings } from "@/types/customers.d"
250 +import Api from "@/api"
251 +import Icon from "@/components/common/Icon.vue"
252 +import _uniqBy from "lodash/uniqBy"
253 import {
251 - NSteps,
252 - NStep,
253 - useMessage,
254 - NScrollbar,
254 + type FormInst,
255 + type FormItemRule,
256 + type FormRules,
257 + type FormValidationError,
258 NButton,
259 + NCard,
260 NForm,
261 NFormItem,
262 NInput,
259 - NSelect,
263 NInputNumber,
264 + NScrollbar,
265 + NSelect,
266 NSpin,
267 + NStep,
268 + NSteps,
269 NSwitch,
263 - NCard,
270 type StepsProps,
265 - type FormRules,
266 - type FormInst,
267 - type FormItemRule,
268 - type FormValidationError
271 + useMessage
272 } from "naive-ui"
270 -import type { CustomerMeta, CustomerProvision, CustomerProvisioningDefaultSettings } from "@/types/customers.d"
271 -import Icon from "@/components/common/Icon.vue"
272 -import Api from "@/api"
273 -import isURL from "validator/es/lib/isURL"
274 -import isPort from "validator/es/lib/isPort"
273 import isIP from "validator/es/lib/isIP"
276 -import { onBeforeMount } from "vue"
277 -import _uniqBy from "lodash/uniqBy"
278 -
279 -const emit = defineEmits<{
280 - (e: "update:loading", value: boolean): void
281 - (e: "submitted", value: CustomerMeta): void
282 -}>()
274 +import isPort from "validator/es/lib/isPort"
275 +import isURL from "validator/es/lib/isURL"
276 +import { computed, onBeforeMount, ref, toRefs } from "vue"
277
278 const props = defineProps<{
279 customerCode: string
@@ -287,6 +281,12 @@ const props = defineProps<{
281 mode?: "new" | "update"
282 showDfirIrisUsername?: boolean
283 }>()
284 +
285 +const emit = defineEmits<{
286 + (e: "update:loading", value: boolean): void
287 + (e: "submitted", value: CustomerMeta): void
288 +}>()
289 +
290 const { customerCode, customerName, mode, showDfirIrisUsername } = toRefs(props)
291
292 const SkipIcon = "carbon:subtract"
frontend/src/components/graylog/Alerts/Item.vue
+10 -6
@@ -78,21 +78,25 @@
78 </div>
79 </div>
80 <div class="main-box">
81 - <div class="content">{{ alertsEvent.event.message }}</div>
81 + <div class="content">
82 + {{ alertsEvent.event.message }}
83 + </div>
84 </div>
85 <div class="footer-box flex justify-end items-center gap-3">
84 - <div class="time">{{ formatDateTime(alertsEvent.event.timestamp) }}</div>
86 + <div class="time">
87 + {{ formatDateTime(alertsEvent.event.timestamp) }}
88 + </div>
89 </div>
90 </div>
91 </template>
92
93 <script setup lang="ts">
90 -import { type AlertsEventElement } from "@/types/graylog/alerts.d"
91 -import { NPopover, NTimeline, NTimelineItem } from "naive-ui"
92 -import { useSettingsStore } from "@/stores/settings"
93 -import { formatDate } from "@/utils"
94 +import type { AlertsEventElement } from "@/types/graylog/alerts.d"
95 import Icon from "@/components/common/Icon.vue"
96 import { useGoto } from "@/composables/useGoto"
97 +import { useSettingsStore } from "@/stores/settings"
98 +import { formatDate } from "@/utils"
99 +import { NPopover, NTimeline, NTimelineItem } from "naive-ui"
100
101 const { alertsEvent } = defineProps<{ alertsEvent: AlertsEventElement }>()
102
frontend/src/components/graylog/Alerts/List.vue
+13 -13
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-5">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -33,8 +33,8 @@
33 :page-sizes="pageSizes"
34 :simple="simpleMode"
35 />
36 - <n-select size="small" v-model:value="timerange" :options="timeOptions" class="!w-32" v-if="!compactMode" />
37 - <n-popover overlap v-if="compactMode" placement="right">
36 + <n-select v-if="!compactMode" v-model:value="timerange" size="small" :options="timeOptions" class="!w-32" />
37 + <n-popover v-if="compactMode" overlap placement="right">
38 <template #trigger>
39 <div class="bg-color border-radius">
40 <n-button size="small">
@@ -46,7 +46,7 @@
46 </template>
47 <div class="mb-2">
48 <div class="text-secondary-color text-sm my-1">Time range:</div>
49 - <n-select size="small" v-model:value="timerange" :options="timeOptions" class="!w-32 mb-1" />
49 + <n-select v-model:value="timerange" size="small" :options="timeOptions" class="!w-32 mb-1" />
50 </div>
51 </n-popover>
52 </div>
@@ -55,35 +55,35 @@
55 <AlertsEventItem
56 v-for="alertsEvent of alertsEvents"
57 :key="alertsEvent.event.id"
58 - :alertsEvent="alertsEvent"
58 + :alerts-event="alertsEvent"
59 @click-event="gotoEventsPage($event)"
60 />
61 </template>
62 <template v-else>
63 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
63 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
64 </template>
65 </div>
66 <div class="footer flex justify-end">
67 <n-pagination
68 + v-if="alertsEvents.length > 3"
69 v-model:page="currentPage"
70 :page-size="pageSize"
71 :item-count="total"
72 :page-slot="6"
72 - v-if="alertsEvents.length > 3"
73 />
74 </div>
75 </n-spin>
76 </template>
77
78 <script setup lang="ts">
79 -import { ref, onBeforeMount, watch, computed } from "vue"
80 -import { useMessage, NSpin, NPagination, NSelect, NPopover, NButton, NEmpty } from "naive-ui"
79 +import type { AlertsEventElement, AlertsQuery } from "@/types/graylog/alerts.d"
80 import Api from "@/api"
82 -import AlertsEventItem from "./Item.vue"
83 -import { useResizeObserver } from "@vueuse/core"
84 -import dayjs from "@/utils/dayjs"
81 import Icon from "@/components/common/Icon.vue"
86 -import type { AlertsQuery, AlertsEventElement } from "@/types/graylog/alerts.d"
82 +import dayjs from "@/utils/dayjs"
83 +import { useResizeObserver } from "@vueuse/core"
84 +import { NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
85 +import { computed, onBeforeMount, ref, watch } from "vue"
86 +import AlertsEventItem from "./Item.vue"
87
88 const emit = defineEmits<{
89 (e: "clickEvent", value: string): void
frontend/src/components/graylog/Events/Item.vue
+10 -6
@@ -1,5 +1,5 @@
1 <template>
2 - <div class="item flex flex-col gap-2 px-5 py-3" :class="{ highlight }" :id="'event-' + event.id">
2 + <div :id="`event-${event.id}`" class="item flex flex-col gap-2 px-5 py-3" :class="{ highlight }">
3 <div class="header-box flex justify-between">
4 <div class="flex items-center gap-3">
5 <n-tooltip trigger="hover">
@@ -23,8 +23,12 @@
23 </div>
24 </div>
25 <div class="main-box">
26 - <div class="title">{{ event.title }}</div>
27 - <div class="description">{{ event.description }}</div>
26 + <div class="title">
27 + {{ event.title }}
28 + </div>
29 + <div class="description">
30 + {{ event.description }}
31 + </div>
32 </div>
33 <div class="footer-box flex justify-end items-center gap-3">
34 <div class="notification">
@@ -63,7 +67,7 @@
67 <SimpleJsonViewer
68 class="vuesjv-override"
69 :model-value="event.field_spec"
66 - :initialExpandedDepth="1"
70 + :initial-expanded-depth="1"
71 />
72 </div>
73 </n-tab-pane>
@@ -73,12 +77,12 @@
77 </template>
78
79 <script setup lang="ts">
76 -import { ref, toRefs } from "vue"
80 import type { EventDefinition } from "@/types/graylog/event-definition.d"
81 import Icon from "@/components/common/Icon.vue"
82 +import { NInput, NModal, NTabPane, NTabs, NTooltip } from "naive-ui"
83 +import { ref, toRefs } from "vue"
84 import { SimpleJsonViewer } from "vue-sjv"
85 import "@/assets/scss/vuesjv-override.scss"
81 -import { NModal, NTabs, NTabPane, NInput, NTooltip } from "naive-ui"
86
87 const props = defineProps<{ event: EventDefinition; highlight: boolean | null | undefined }>()
88 const { event, highlight } = toRefs(props)
frontend/src/components/graylog/Events/List.vue
+9 -8
@@ -39,26 +39,27 @@
39 />
40 </template>
41 <template v-else>
42 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
42 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
43 </template>
44 </div>
45 </n-spin>
46 </template>
47
48 <script setup lang="ts">
49 -import { ref, onBeforeMount, computed, toRefs, nextTick, watch } from "vue"
50 -import { useMessage, NSpin, NPopover, NButton, NSelect, NEmpty } from "naive-ui"
51 -import EventItem from "./Item.vue"
52 -import Api from "@/api"
53 -import Icon from "@/components/common/Icon.vue"
49 import type { EventDefinition } from "@/types/graylog/event-definition.d"
50 import type { SelectMixedOption } from "naive-ui/es/select/src/interface"
51 +import Api from "@/api"
52 +import Icon from "@/components/common/Icon.vue"
53 +import { NButton, NEmpty, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
54 +import { computed, nextTick, onBeforeMount, ref, toRefs, watch } from "vue"
55 +import EventItem from "./Item.vue"
56 +
57 +const props = defineProps<{ highlight: string | null | undefined }>()
58
59 const emit = defineEmits<{
60 (e: "loaded", value: EventDefinition[]): void
61 }>()
62
61 -const props = defineProps<{ highlight: string | null | undefined }>()
63 const { highlight } = toRefs(props)
64
65 const InfoIcon = "carbon:information"
@@ -69,7 +70,7 @@ const loading = ref(false)
70 const events = ref<EventDefinition[]>([])
71 const priorities = computed<SelectMixedOption[]>(() =>
72 [...new Set(events.value.map(o => o.priority))].map(o => ({
72 - label: "Priority " + o.toString(),
73 + label: `Priority ${o.toString()}`,
74 value: o
75 }))
76 )
frontend/src/components/graylog/Inputs/Item.vue
+38 -22
@@ -7,7 +7,9 @@
7 {{ input.creator_user_id }}
8 </div>
9 </div>
10 - <div class="time">{{ formatDateTime(input.created_at) }}</div>
10 + <div class="time">
11 + {{ formatDateTime(input.created_at) }}
12 + </div>
13 <n-button size="small" @click.stop="showDetails = true">
14 <template #icon>
15 <Icon :name="InfoIcon"></Icon>
@@ -16,8 +18,12 @@
18 </div>
19 <div class="main-box flex justify-between">
20 <div class="content">
19 - <div class="title">{{ input.title }}</div>
20 - <div class="name mb-2">{{ input.name }}</div>
21 + <div class="title">
22 + {{ input.title }}
23 + </div>
24 + <div class="name mb-2">
25 + {{ input.name }}
26 + </div>
27 <div class="badges-box flex flex-wrap items-center gap-3">
28 <Badge :type="input.global ? 'active' : 'muted'">
29 <template #iconRight>
@@ -40,29 +46,39 @@
46 </div>
47
48 <div class="actions-box flex flex-col justify-end">
43 - <n-button @click="stop()" :loading="loading" v-if="isRunning">
44 - <template #icon><Icon :name="StopIcon"></Icon></template>
49 + <n-button v-if="isRunning" :loading="loading" @click="stop()">
50 + <template #icon>
51 + <Icon :name="StopIcon"></Icon>
52 + </template>
53 Stop input
54 </n-button>
47 - <n-button @click="start()" :loading="loading" v-else type="primary">
48 - <template #icon><Icon :name="StartIcon"></Icon></template>
55 + <n-button v-else :loading="loading" type="primary" @click="start()">
56 + <template #icon>
57 + <Icon :name="StartIcon"></Icon>
58 + </template>
59 Start input
60 </n-button>
61 </div>
62 </div>
63 <div class="footer-box flex justify-between items-center">
64 <div class="actions-box flex flex-col justify-end">
55 - <n-button @click="stop()" :loading="loading" v-if="isRunning" size="small">
56 - <template #icon><Icon :name="StopIcon"></Icon></template>
65 + <n-button v-if="isRunning" :loading="loading" size="small" @click="stop()">
66 + <template #icon>
67 + <Icon :name="StopIcon"></Icon>
68 + </template>
69 Stop
70 </n-button>
59 - <n-button @click="start()" :loading="loading" v-else type="primary" size="small">
60 - <template #icon><Icon :name="StartIcon"></Icon></template>
71 + <n-button v-else :loading="loading" type="primary" size="small" @click="start()">
72 + <template #icon>
73 + <Icon :name="StartIcon"></Icon>
74 + </template>
75 Start
76 </n-button>
77 </div>
78
65 - <div class="time">{{ formatDateTime(input.created_at) }}</div>
79 + <div class="time">
80 + {{ formatDateTime(input.created_at) }}
81 + </div>
82 </div>
83
84 <n-modal
@@ -97,7 +113,7 @@
113 <SimpleJsonViewer
114 class="vuesjv-override"
115 :model-value="input.static_fields"
100 - :initialExpandedDepth="1"
116 + :initial-expanded-depth="1"
117 />
118 </div>
119 </n-tab-pane>
@@ -106,7 +122,7 @@
122 <SimpleJsonViewer
123 class="vuesjv-override"
124 :model-value="input.attributes"
109 - :initialExpandedDepth="1"
125 + :initial-expanded-depth="1"
126 />
127 </div>
128 </n-tab-pane>
@@ -116,23 +132,23 @@
132 </template>
133
134 <script setup lang="ts">
119 -import { useSettingsStore } from "@/stores/settings"
120 -import Icon from "@/components/common/Icon.vue"
135 +import type { InputExtended } from "@/types/graylog/inputs.d"
136 +import Api from "@/api"
137 import Badge from "@/components/common/Badge.vue"
122 -import { NModal, NButton, useMessage, NTooltip, NTabs, NTabPane } from "naive-ui"
138 +import Icon from "@/components/common/Icon.vue"
139 +import { useSettingsStore } from "@/stores/settings"
140 +import { formatDate } from "@/utils"
141 +import { NButton, NModal, NTabPane, NTabs, NTooltip, useMessage } from "naive-ui"
142 import { computed, ref } from "vue"
143 import { SimpleJsonViewer } from "vue-sjv"
144 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"
145 +
146 +const { input } = defineProps<{ input: InputExtended }>()
147
148 const emit = defineEmits<{
149 (e: "updated"): void
150 }>()
151
134 -const { input } = defineProps<{ input: InputExtended }>()
135 -
152 const UserIcon = "carbon:user"
153 const InfoIcon = "carbon:information"
154 const DisabledIcon = "carbon:subtract"
frontend/src/components/graylog/Inputs/List.vue
+5 -5
@@ -44,7 +44,7 @@
44 />
45 </template>
46 <template v-else>
47 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
47 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
48 </template>
49 </div>
50 </n-scrollbar>
@@ -52,12 +52,12 @@
52 </template>
53
54 <script setup lang="ts">
55 -import { ref, onBeforeMount, computed } from "vue"
56 -import { useMessage, NSpin, NPopover, NButton, NSelect, NEmpty, NScrollbar } from "naive-ui"
55 +import type { ConfiguredInput, InputExtended, RunningInput } from "@/types/graylog/inputs.d"
56 import Api from "@/api"
58 -import InputItem from "./Item.vue"
57 import Icon from "@/components/common/Icon.vue"
60 -import type { ConfiguredInput, InputExtended, RunningInput } from "@/types/graylog/inputs.d"
58 +import { NButton, NEmpty, NPopover, NScrollbar, NSelect, NSpin, useMessage } from "naive-ui"
59 +import { computed, onBeforeMount, ref } from "vue"
60 +import InputItem from "./Item.vue"
61
62 const InfoIcon = "carbon:information"
63
frontend/src/components/graylog/Messages/Item.vue
+13 -5
@@ -1,20 +1,28 @@
1 <template>
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, dFormats.datetimesec) }}</div>
4 + <div class="caller">
5 + {{ message.caller }}
6 + </div>
7 + <div class="time">
8 + {{ formatDate(message.timestamp, dFormats.datetimesec) }}
9 + </div>
10 </div>
11 <div class="main-box">
8 - <div class="content">{{ message.content }}</div>
12 + <div class="content">
13 + {{ message.content }}
14 + </div>
15 </div>
16 <div class="footer-box">
11 - <div class="time">{{ formatDate(message.timestamp, dFormats.datetimesec) }}</div>
17 + <div class="time">
18 + {{ formatDate(message.timestamp, dFormats.datetimesec) }}
19 + </div>
20 </div>
21 </div>
22 </template>
23
24 <script setup lang="ts">
17 -import { type Message } from "@/types/graylog/messages.d"
25 +import type { Message } from "@/types/graylog/messages.d"
26 import { useSettingsStore } from "@/stores/settings"
27 import { formatDate } from "@/utils"
28
frontend/src/components/graylog/Messages/List.vue
+6 -6
@@ -27,29 +27,29 @@
27 <MessageItem v-for="msg of messages" :key="msg.id" :message="msg" />
28 </template>
29 <template v-else>
30 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
30 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
31 </template>
32 </div>
33 <div class="footer flex justify-end">
34 <n-pagination
35 + v-if="messages.length > 3"
36 v-model:page="currentPage"
37 :page-size="pageSize"
38 :item-count="total"
39 :page-slot="6"
39 - v-if="messages.length > 3"
40 />
41 </div>
42 </n-spin>
43 </template>
44
45 <script setup lang="ts">
46 -import { ref, onBeforeMount, watch } from "vue"
47 -import { useMessage, NSpin, NPagination, NPopover, NButton, NEmpty } from "naive-ui"
46 +import type { MessageExtended } from "@/types/graylog/messages.d"
47 import Api from "@/api"
49 -import MessageItem from "./Item.vue"
48 import Icon from "@/components/common/Icon.vue"
49 +import { NButton, NEmpty, NPagination, NPopover, NSpin, useMessage } from "naive-ui"
50 import { nanoid } from "nanoid"
52 -import type { MessageExtended } from "@/types/graylog/messages.d"
51 +import { onBeforeMount, ref, watch } from "vue"
52 +import MessageItem from "./Item.vue"
53
54 const InfoIcon = "carbon:information"
55
frontend/src/components/graylog/Metrics/List.vue
+2 -2
@@ -32,12 +32,12 @@
32 </template>
33
34 <script setup lang="ts">
35 -import { toRefs, computed } from "vue"
36 -import { NCard, NProgress } from "naive-ui"
35 import type { ThroughputMetric } from "@/types/graylog/metrics.d"
36 import _groupBy from "lodash/groupBy"
37 import _map from "lodash/map"
38 import _trim from "lodash/trim"
39 +import { NCard, NProgress } from "naive-ui"
40 +import { computed, toRefs } from "vue"
41
42 interface Metrics {
43 groupName: string
frontend/src/components/graylog/Metrics/UncommittedEntries.vue
+10 -10
@@ -3,10 +3,10 @@
3 <div class="uncommitted-entries">
4 <div class="chart grow">
5 <div class="label flex items-center gap-3">
6 - <Icon :name="DangerIcon" v-if="isWarning"></Icon>
6 + <Icon v-if="isWarning" :name="DangerIcon"></Icon>
7 <span>Uncommitted Journal Entries</span>
8 </div>
9 - <apexchart height="70" :options="options" :series="series" ref="chart"></apexchart>
9 + <apexchart height="70" :options="options" :series="series"></apexchart>
10 </div>
11 <div class="value flex flex-col justify-center" :class="{ warning: isWarning }">
12 <span>{{ value }}</span>
@@ -25,21 +25,21 @@
25
26 <script setup lang="ts">
27 import Icon from "@/components/common/Icon.vue"
28 -import { NButton } from "naive-ui"
29 -import { computed, ref, toRefs } from "vue"
28 +import { useGoto } from "@/composables/useGoto"
29 +import { usHealthcheckStore } from "@/stores/healthcheck"
30 import { useThemeStore } from "@/stores/theme"
31 import dayjs from "@/utils/dayjs"
32 -import "@/assets/scss/apexchart-override.scss"
33 -import { watch } from "vue"
34 -import { usHealthcheckStore } from "@/stores/healthcheck"
32 +import { NButton } from "naive-ui"
33 +import { computed, ref, toRefs, watch } from "vue"
34 import apexchart from "vue3-apexcharts"
36 -import { useGoto } from "@/composables/useGoto"
37 -
38 -const UNCOMMITTED_JOURNAL_ENTRIES_THRESHOLD = usHealthcheckStore().uncommittedJournalEntriesThreshold
35 +import "@/assets/scss/apexchart-override.scss"
36
37 const props = defineProps<{
38 value: number
39 }>()
40 +
41 +const UNCOMMITTED_JOURNAL_ENTRIES_THRESHOLD = usHealthcheckStore().uncommittedJournalEntriesThreshold
42 +
43 const { value } = toRefs(props)
44
45 const LinkIcon = "carbon:launch"
frontend/src/components/graylog/MonitoringAlerts/CustomAlertButton.vue
+4 -4
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button size="small" type="primary" secondary @click="showForm = true" :loading="loading">
2 + <n-button size="small" type="primary" secondary :loading="loading" @click="showForm = true">
3 <div class="flex items-center gap-2">
4 <Icon :name="DangerIcon" :size="18"></Icon>
5 <span class="hidden xs:block">Custom Alert</span>
@@ -15,14 +15,14 @@
15 :bordered="false"
16 segmented
17 >
18 - <CustomAlertForm @mounted="formCTX = $event" v-model:loading="loading" />
18 + <CustomAlertForm v-model:loading="loading" @mounted="formCTX = $event" />
19 </n-modal>
20 </template>
21
22 <script setup lang="ts">
23 -import { ref, watch } from "vue"
24 -import { NButton, NModal } from "naive-ui"
23 import Icon from "@/components/common/Icon.vue"
24 +import { NButton, NModal } from "naive-ui"
25 +import { ref, watch } from "vue"
26 import CustomAlertForm from "./CustomAlertForm.vue"
27
28 const DangerIcon = "majesticons:exclamation-line"
frontend/src/components/graylog/MonitoringAlerts/CustomAlertForm.vue
+45 -45
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="custom-alert-form">
3 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-2">
5 <div class="flex gap-4">
6 <n-form-item label="Priority" path="alert_priority" class="w-28">
@@ -43,7 +43,7 @@
43 <div class="custom-fields-editor">
44 <n-form-item label="Custom fields" path="custom_fields">
45 <div class="custom-fields-list flex flex-col gap-1">
46 - <n-card size="small" v-for="(cf, index) of form.custom_fields" :key="cf.key">
46 + <n-card v-for="(cf, index) of form.custom_fields" :key="cf.key" size="small">
47 <div class="custom-field-box flex gap-2">
48 <n-form-item
49 label="Name"
@@ -58,9 +58,9 @@
58 >
59 <n-input
60 v-model:value.trim="cf.name"
61 - @update:value="validate()"
61 placeholder="Custom field Name"
62 clearable
63 + @update:value="validate()"
64 />
65 </n-form-item>
66 <n-form-item
@@ -76,9 +76,9 @@
76 >
77 <n-input
78 v-model:value.trim="cf.value"
79 - @update:value="validate()"
79 placeholder="Custom field Value"
80 clearable
81 + @update:value="validate()"
82 />
83 </n-form-item>
84 <n-form-item size="small">
@@ -104,8 +104,8 @@
104 <div class="flex gap-4">
105 <n-form-item label="Search within (seconds)" path="search_within_seconds" class="grow">
106 <n-input-number
107 - :min="1"
107 v-model:value="form.search_within_seconds"
108 + :min="1"
109 placeholder="Input time in seconds"
110 clearable
111 class="w-full"
@@ -113,8 +113,8 @@
113 </n-form-item>
114 <n-form-item label="Execute every (seconds)" path="execute_every_seconds" class="grow">
115 <n-input-number
116 - :min="1"
116 v-model:value="form.execute_every_seconds"
117 + :min="1"
118 placeholder="Input time in seconds"
119 clearable
120 class="w-full"
@@ -126,12 +126,12 @@
126 <slot name="additionalActions"></slot>
127 </div>
128 <div class="flex gap-4">
129 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
129 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
130 <n-button
131 type="primary"
132 :disabled="!isValid"
133 - @click="validate(() => submit())"
133 :loading="submittingCustomAlert"
134 + @click="validate(() => submit())"
135 >
136 Submit
137 </n-button>
@@ -143,30 +143,30 @@
143 </template>
144
145 <script setup lang="ts">
146 -import { computed, onMounted, ref, watch } from "vue"
146 +import type { CustomProvisionPayload } from "@/api/endpoints/monitoringAlerts"
147 import Api from "@/api"
148 +import Icon from "@/components/common/Icon.vue"
149 +import { CustomProvisionPriority } from "@/types/monitoringAlerts.d"
150 +import _get from "lodash/get"
151 +import _toSafeInteger from "lodash/toSafeInteger"
152 +import _trim from "lodash/trim"
153 import {
149 - useMessage,
154 + type FormInst,
155 + type FormItemRule,
156 + type FormRules,
157 + type FormValidationError,
158 + type MessageReactive,
159 + NButton,
160 + NCard,
161 NForm,
162 NFormItem,
163 NInput,
153 - NButton,
154 - NSpin,
155 - NSelect,
164 NInputNumber,
157 - NCard,
158 - type FormValidationError,
159 - type FormInst,
160 - type FormRules,
161 - type FormItemRule,
162 - type MessageReactive
165 + NSelect,
166 + NSpin,
167 + useMessage
168 } from "naive-ui"
164 -import _trim from "lodash/trim"
165 -import _get from "lodash/get"
166 -import _toSafeInteger from "lodash/toSafeInteger"
167 -import { type CustomProvisionPayload } from "@/api/endpoints/monitoringAlerts"
168 -import Icon from "@/components/common/Icon.vue"
169 -import { CustomProvisionPriority } from "@/types/monitoringAlerts.d"
169 +import { computed, onMounted, ref, watch } from "vue"
170
171 interface CustomProvisionForm {
172 alert_name: string
@@ -200,6 +200,26 @@ const message = useMessage()
200 const form = ref<CustomProvisionForm>(getClearForm())
201 const formRef = ref<FormInst | null>(null)
202
203 +const areAllCustomerFieldsFilled = computed(() => {
204 + const fieldsFilled = form.value.custom_fields.filter(o => !!o.name && !!o.value)
205 +
206 + return fieldsFilled.length === form.value.custom_fields.length
207 +})
208 +
209 +const areAllCustomerFieldsUniques = computed(() => {
210 + const fieldsFilled = form.value.custom_fields.filter(o => !!o.name).map(o => o.name)
211 +
212 + const uniques: string[] = fieldsFilled.filter((value, index, self) => self.indexOf(value) === index)
213 +
214 + return uniques.length === form.value.custom_fields.length
215 +})
216 +
217 +const isCustomerCodePresent = computed(() => {
218 + const field = form.value.custom_fields.filter(o => o.name === "CUSTOMER_CODE" && !!o.value)
219 +
220 + return !!field.length
221 +})
222 +
223 const alertPriorityOptions: { label: string; value: CustomProvisionPriority }[] = [
224 { label: "Low", value: CustomProvisionPriority.LOW },
225 { label: "Medium", value: CustomProvisionPriority.MEDIUM },
@@ -259,26 +279,6 @@ const rules: FormRules = {
279 }
280 }
281
262 -const areAllCustomerFieldsFilled = computed(() => {
263 - const fieldsFilled = form.value.custom_fields.filter(o => !!o.name && !!o.value)
264 -
265 - return fieldsFilled.length === form.value.custom_fields.length
266 -})
267 -
268 -const areAllCustomerFieldsUniques = computed(() => {
269 - const fieldsFilled = form.value.custom_fields.filter(o => !!o.name).map(o => o.name)
270 -
271 - const uniques: string[] = fieldsFilled.filter((value, index, self) => self.indexOf(value) === index)
272 -
273 - return uniques.length === form.value.custom_fields.length
274 -})
275 -
276 -const isCustomerCodePresent = computed(() => {
277 - const field = form.value.custom_fields.filter(o => o.name === "CUSTOMER_CODE" && !!o.value)
278 -
279 - return !!field.length
280 -})
281 -
282 const isValid = computed(() => {
283 if (!areAllCustomerFieldsFilled.value) {
284 return false
frontend/src/components/graylog/MonitoringAlerts/Item.vue
+28 -20
@@ -1,7 +1,9 @@
1 <template>
2 <div class="item flex flex-col gap-2 px-5 py-3">
3 <div class="header-box flex justify-between gap-4">
4 - <div class="name">{{ alert.name }}</div>
4 + <div class="name">
5 + {{ alert.name }}
6 + </div>
7 <div class="badge flex mb-2">
8 <Badge :type="isEnabled ? 'active' : 'muted'">
9 <template #iconRight>
@@ -16,7 +18,9 @@
18 </div>
19 </div>
20 <div class="main-box flex justify-between gap-4">
19 - <div class="content">{{ alert.value }}</div>
21 + <div class="content">
22 + {{ alert.value }}
23 + </div>
24 <div class="actions-box">
25 <n-button
26 v-if="!isEnabled"
@@ -25,7 +29,9 @@
29 secondary
30 @click="openFormDialog()"
31 >
28 - <template #icon><Icon :name="EnableIcon"></Icon></template>
32 + <template #icon>
33 + <Icon :name="EnableIcon"></Icon>
34 + </template>
35 Enable
36 </n-button>
37 </div>
@@ -40,15 +46,17 @@
46 size="small"
47 @click="openFormDialog()"
48 >
43 - <template #icon><Icon :name="EnableIcon"></Icon></template>
49 + <template #icon>
50 + <Icon :name="EnableIcon"></Icon>
51 + </template>
52 Enable
53 </n-button>
54 </div>
55 </div>
56
57 <n-modal
50 - :title="alert.name"
58 v-model:show="showFormDialog"
59 + :title="alert.name"
60 preset="card"
61 segmented
62 :mask-closable="false"
@@ -59,22 +67,22 @@
67 <n-form ref="formRef" :model="formModel" :rules="formRules">
68 <n-form-item path="searchWithinLast" label="Search Within Last (time in seconds)">
69 <n-input-number
62 - :min="1"
70 v-model:value="formModel.searchWithinLast"
64 - @keydown.enter.prevent
71 + :min="1"
72 placeholder="Input time in seconds"
73 clearable
74 class="w-full"
75 + @keydown.enter.prevent
76 />
77 </n-form-item>
78 <n-form-item path="executeEvery" label="Execute Every (time in seconds)">
79 <n-input-number
72 - :min="1"
80 v-model:value="formModel.executeEvery"
74 - @keydown.enter.prevent
81 + :min="1"
82 placeholder="Input time in seconds"
83 clearable
84 class="w-full"
85 + @keydown.enter.prevent
86 />
87 </n-form-item>
88 </n-form>
@@ -90,30 +98,30 @@
98 </template>
99
100 <script setup lang="ts">
101 +import type { ProvisionsMonitoringAlertParams } from "@/api/endpoints/monitoringAlerts"
102 import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
94 -import { ref } from "vue"
95 -import Icon from "@/components/common/Icon.vue"
103 +import Api from "@/api"
104 import Badge from "@/components/common/Badge.vue"
105 +import Icon from "@/components/common/Icon.vue"
106 import {
98 - NButton,
99 - NSpin,
100 - useMessage,
101 - NModal,
107 type FormRules,
108 + type FormValidationError,
109 + NButton,
110 NForm,
111 NFormItem,
112 NInputNumber,
106 - type FormValidationError
113 + NModal,
114 + NSpin,
115 + useMessage
116 } from "naive-ui"
108 -import Api from "@/api"
109 -import type { ProvisionsMonitoringAlertParams } from "@/api/endpoints/monitoringAlerts"
117 +import { ref } from "vue"
118 +
119 +const { alert, isEnabled } = defineProps<{ alert: AvailableMonitoringAlert; isEnabled: boolean }>()
120
121 const emit = defineEmits<{
122 (e: "provisioned"): void
123 }>()
124
115 -const { alert, isEnabled } = defineProps<{ alert: AvailableMonitoringAlert; isEnabled: boolean }>()
116 -
125 const DisabledIcon = "carbon:subtract"
126 const EnabledIcon = "ph:check-bold"
127 const EnableIcon = "carbon:play"
frontend/src/components/graylog/MonitoringAlerts/List.vue
+13 -13
@@ -1,6 +1,6 @@
1 <template>
2 <div class="monitoring-alert-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -47,31 +47,31 @@
47 />
48 </template>
49 <template v-else>
50 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
50 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
51 </template>
52 </div>
53 </n-spin>
54 <div class="footer flex justify-end">
55 <n-pagination
56 + v-if="itemsPaginated.length > 3"
57 v-model:page="currentPage"
58 :page-size="pageSize"
59 :item-count="total"
60 :page-slot="6"
60 - v-if="itemsPaginated.length > 3"
61 />
62 </div>
63 </div>
64 </template>
65
66 <script setup lang="ts">
67 -import { ref, onBeforeMount, computed } from "vue"
68 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination } from "naive-ui"
69 -import Api from "@/api"
70 -import MonitoringAlert from "./Item.vue"
71 -import CustomAlertButton from "./CustomAlertButton.vue"
67 +import type { EventDefinition } from "@/types/graylog/event-definition.d"
68 import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
69 +import Api from "@/api"
70 import Icon from "@/components/common/Icon.vue"
74 -import type { EventDefinition } from "@/types/graylog/event-definition.d"
71 +import { NButton, NEmpty, NPagination, NPopover, NSpin, useMessage } from "naive-ui"
72 +import { computed, onBeforeMount, ref } from "vue"
73 +import CustomAlertButton from "./CustomAlertButton.vue"
74 +import MonitoringAlert from "./Item.vue"
75
76 const { eventsList } = defineProps<{ eventsList: EventDefinition[] }>()
77
@@ -97,10 +97,6 @@ const total = computed<number>(() => {
97 return alerts.value.length || 0
98 })
99
100 -const enabledTotal = computed<number>(() => {
101 - return enabledList.value.length || 0
102 -})
103 -
100 const enabledList = computed<AvailableMonitoringAlert[]>(() => {
101 return alerts.value.filter(alert => {
102 const eventIndex = events.value.findIndex(event => event.title === alert.name)
@@ -108,6 +104,10 @@ const enabledList = computed<AvailableMonitoringAlert[]>(() => {
104 })
105 })
106
107 +const enabledTotal = computed<number>(() => {
108 + return enabledList.value.length || 0
109 +})
110 +
111 const itemsPaginated = computed(() => {
112 const from = (currentPage.value - 1) * pageSize.value
113 const to = currentPage.value * pageSize.value
frontend/src/components/graylog/Pipelines/PipeDetails.vue
+6 -5
@@ -41,23 +41,24 @@
41 </template>
42
43 <script setup lang="ts">
44 -import { NTimeline, NTimelineItem, NButton, NScrollbar, NPopover } from "naive-ui"
45 -import { computed, toRefs } from "vue"
44 import type { PipelineFull, PipelineFullStage } from "@/types/graylog/pipelines.d"
45 import Icon from "@/components/common/Icon.vue"
48 -import RulesSmallList, { type RuleExtended } from "./RulesSmallList.vue"
49 -import { formatDate } from "@/utils"
46 import { useSettingsStore } from "@/stores/settings"
47 +import { formatDate } from "@/utils"
48 +import { NButton, NPopover, NScrollbar, NTimeline, NTimelineItem } from "naive-ui"
49 +import { computed, toRefs } from "vue"
50 +import RulesSmallList, { type RuleExtended } from "./RulesSmallList.vue"
51
52 interface PipelineFullStageExt extends Omit<PipelineFullStage, "rules" | "rule_ids"> {
53 rules: RuleExtended[]
54 }
55
56 +const props = defineProps<{ pipeline: PipelineFull }>()
57 +
58 const emit = defineEmits<{
59 (e: "clickRule", value: string): void
60 }>()
61
60 -const props = defineProps<{ pipeline: PipelineFull }>()
62 const { pipeline } = toRefs(props)
63
64 const RulesIcon = "ic:outline-swipe-right-alt"
frontend/src/components/graylog/Pipelines/PipeInfo.vue
+2 -2
@@ -43,11 +43,11 @@
43 </template>
44
45 <script setup lang="ts">
46 -import { NTabs, NTabPane, NInput } from "naive-ui"
47 -import { toRefs } from "vue"
46 import type { Pipeline } from "@/types/graylog/pipelines.d"
47 import { useSettingsStore } from "@/stores/settings"
48 import { formatDate } from "@/utils"
49 +import { NInput, NTabPane, NTabs } from "naive-ui"
50 +import { toRefs } from "vue"
51
52 const props = defineProps<{ pipeline?: Pipeline }>()
53 const { pipeline } = toRefs(props)
frontend/src/components/graylog/Pipelines/PipeList.vue
+5 -5
@@ -7,7 +7,7 @@
7 accordion
8 :style="minHeight ? `min-height: ${minHeight}` : ''"
9 >
10 - <n-collapse-item :title="pipe.title" :name="pipe.id" v-for="pipe of pipelines" :key="pipe.id">
10 + <n-collapse-item v-for="pipe of pipelines" :key="pipe.id" :title="pipe.title" :name="pipe.id">
11 <template #header>
12 <PipeTitle :pipeline="pipe" />
13 </template>
@@ -41,24 +41,24 @@
41 </template>
42
43 <script setup lang="ts">
44 -import { useMessage, NCollapse, NCollapseItem, NSpin, NButton, NModal, NCard } from "naive-ui"
45 -import { onBeforeMount, ref, toRefs, watch } from "vue"
44 import type { PipelineFull } from "@/types/graylog/pipelines.d"
45 import Api from "@/api"
46 import Icon from "@/components/common/Icon.vue"
47 import PipeDetails from "@/components/graylog/Pipelines/PipeDetails.vue"
48 import PipeInfo from "@/components/graylog/Pipelines/PipeInfo.vue"
49 import PipeTitle from "@/components/graylog/Pipelines/PipeTitle.vue"
50 +import { NButton, NCard, NCollapse, NCollapseItem, NModal, NSpin, useMessage } from "naive-ui"
51 +import { onBeforeMount, ref, toRefs, watch } from "vue"
52
53 const props = defineProps<{
54 minHeight?: string
55 }>()
56 -const { minHeight } = toRefs(props)
57 -
56 const emit = defineEmits<{
57 (e: "openRule", value: string): void
58 }>()
59
60 +const { minHeight } = toRefs(props)
61 +
62 const InfoIcon = "carbon:information"
63
64 const message = useMessage()
frontend/src/components/graylog/Pipelines/PipeTitle.vue
+3 -3
@@ -1,6 +1,6 @@
1 <template>
2 <div class="pipe-title flex items-center gap-1" :class="{ warning: isWarning }">
3 - <n-tooltip placement="top-start" trigger="hover" v-if="isWarning">
3 + <n-tooltip v-if="isWarning" placement="top-start" trigger="hover">
4 <template #trigger>
5 <Icon :name="DangerIcon" :size="18"></Icon>
6 </template>
@@ -11,10 +11,10 @@
11 </template>
12
13 <script setup lang="ts">
14 -import { NTooltip } from "naive-ui"
15 -import { computed, toRefs } from "vue"
14 import type { Pipeline } from "@/types/graylog/pipelines.d"
15 import Icon from "@/components/common/Icon.vue"
16 +import { NTooltip } from "naive-ui"
17 +import { computed, toRefs } from "vue"
18
19 const props = defineProps<{ pipeline: Pipeline }>()
20 const { pipeline } = toRefs(props)
frontend/src/components/graylog/Pipelines/Rule.vue
+10 -6
@@ -1,5 +1,5 @@
1 <template>
2 - <div class="item flex flex-col mb-2 gap-2 px-5 py-3" :class="{ highlight }" :id="'rule-' + rule.id">
2 + <div :id="`rule-${rule.id}`" class="item flex flex-col mb-2 gap-2 px-5 py-3" :class="{ highlight }">
3 <div class="header-box flex justify-between">
4 <div class="flex items-center gap-3">
5 <div class="id">
@@ -33,8 +33,12 @@
33 </div>
34 </div>
35 <div class="main-box">
36 - <div class="title">{{ rule.title }}</div>
37 - <div class="description">{{ rule.description }}</div>
36 + <div class="title">
37 + {{ rule.title }}
38 + </div>
39 + <div class="description">
40 + {{ rule.description }}
41 + </div>
42 </div>
43 <div class="footer-box flex justify-end items-center gap-3">
44 <div class="time">
@@ -82,12 +86,12 @@
86 </template>
87
88 <script setup lang="ts">
85 -import { ref, toRefs } from "vue"
86 -import { NModal, NInput, NPopover, NTimeline, NTimelineItem } from "naive-ui"
87 -import Icon from "@/components/common/Icon.vue"
89 import type { PipelineRule } from "@/types/graylog/pipelines.d"
90 +import Icon from "@/components/common/Icon.vue"
91 import { useSettingsStore } from "@/stores/settings"
92 import { formatDate } from "@/utils"
93 +import { NInput, NModal, NPopover, NTimeline, NTimelineItem } from "naive-ui"
94 +import { ref, toRefs } from "vue"
95
96 const props = defineProps<{ rule: PipelineRule; highlight: boolean | null | undefined }>()
97 const { rule, highlight } = toRefs(props)
frontend/src/components/graylog/Pipelines/RulesList.vue
+5 -4
@@ -9,17 +9,18 @@
9 </template>
10
11 <script setup lang="ts">
12 -import { useMessage, NSpin, NScrollbar, type ScrollbarInst } from "naive-ui"
13 -import { onBeforeMount, ref, toRefs, watch, nextTick } from "vue"
12 import type { PipelineRule } from "@/types/graylog/pipelines.d"
15 -import Rule from "./Rule.vue"
13 import Api from "@/api"
14 +import { NScrollbar, NSpin, type ScrollbarInst, useMessage } from "naive-ui"
15 +import { nextTick, onBeforeMount, ref, toRefs, watch } from "vue"
16 +import Rule from "./Rule.vue"
17 +
18 +const props = defineProps<{ highlight: string | null | undefined }>()
19
20 const emit = defineEmits<{
21 (e: "loaded", value: { total: number }): void
22 }>()
23
22 -const props = defineProps<{ highlight: string | null | undefined }>()
24 const { highlight } = toRefs(props)
25
26 const message = useMessage()
frontend/src/components/graylog/Pipelines/RulesSmallList.vue
+4 -3
@@ -15,20 +15,21 @@
15 </template>
16
17 <script setup lang="ts">
18 -import { toRefs } from "vue"
19 -import { NButton } from "naive-ui"
18 import Icon from "@/components/common/Icon.vue"
19 +import { NButton } from "naive-ui"
20 +import { toRefs } from "vue"
21
22 export interface RuleExtended {
23 title: string
24 id: string
25 }
26
27 +const props = defineProps<{ rules: RuleExtended[] }>()
28 +
29 const emit = defineEmits<{
30 (e: "click", value: string): void
31 }>()
32
31 -const props = defineProps<{ rules: RuleExtended[] }>()
33 const { rules } = toRefs(props)
34
35 const ViewIcon = "iconoir:eye-alt"
frontend/src/components/graylog/Streams/Item.vue
+36 -20
@@ -7,7 +7,9 @@
7 {{ stream.creator_user_id }}
8 </div>
9 </div>
10 - <div class="time">{{ formatDate(stream.created_at, dFormats.datetimesec) }}</div>
10 + <div class="time">
11 + {{ formatDate(stream.created_at, dFormats.datetimesec) }}
12 + </div>
13 <n-button size="small" @click.stop="showDetails = true">
14 <template #icon>
15 <Icon :name="InfoIcon"></Icon>
@@ -16,8 +18,12 @@
18 </div>
19 <div class="main-box flex justify-between">
20 <div class="content">
19 - <div class="title">{{ stream.title }}</div>
20 - <div class="description mb-2">{{ stream.description }}</div>
21 + <div class="title">
22 + {{ stream.title }}
23 + </div>
24 + <div class="description mb-2">
25 + {{ stream.description }}
26 + </div>
27 <div class="badges-box flex flex-wrap items-center gap-3">
28 <Badge :type="stream.disabled ? 'muted' : 'active'">
29 <template #iconRight>
@@ -39,29 +45,39 @@
45 </Badge>
46 </div>
47 </div>
42 - <div class="actions-box flex flex-col justify-end" v-if="stream.is_editable">
43 - <n-button @click="stop()" :loading="loading" v-if="!stream.disabled">
44 - <template #icon><Icon :name="StopIcon"></Icon></template>
48 + <div v-if="stream.is_editable" class="actions-box flex flex-col justify-end">
49 + <n-button v-if="!stream.disabled" :loading="loading" @click="stop()">
50 + <template #icon>
51 + <Icon :name="StopIcon"></Icon>
52 + </template>
53 Stop stream
54 </n-button>
47 - <n-button @click="start()" :loading="loading" v-else type="primary">
48 - <template #icon><Icon :name="StartIcon"></Icon></template>
55 + <n-button v-else :loading="loading" type="primary" @click="start()">
56 + <template #icon>
57 + <Icon :name="StartIcon"></Icon>
58 + </template>
59 Start stream
60 </n-button>
61 </div>
62 </div>
63 <div class="footer-box flex justify-between items-center">
54 - <div class="actions-box flex flex-col justify-end" v-if="stream.is_editable">
55 - <n-button @click="stop()" :loading="loading" v-if="!stream.disabled" size="small">
56 - <template #icon><Icon :name="StopIcon"></Icon></template>
64 + <div v-if="stream.is_editable" class="actions-box flex flex-col justify-end">
65 + <n-button v-if="!stream.disabled" :loading="loading" size="small" @click="stop()">
66 + <template #icon>
67 + <Icon :name="StopIcon"></Icon>
68 + </template>
69 Stop
70 </n-button>
59 - <n-button @click="start()" :loading="loading" v-else type="primary" size="small">
60 - <template #icon><Icon :name="StartIcon"></Icon></template>
71 + <n-button v-else :loading="loading" type="primary" size="small" @click="start()">
72 + <template #icon>
73 + <Icon :name="StartIcon"></Icon>
74 + </template>
75 Start
76 </n-button>
77 </div>
64 - <div class="time">{{ formatDate(stream.created_at, dFormats.datetimesec) }}</div>
78 + <div class="time">
79 + {{ formatDate(stream.created_at, dFormats.datetimesec) }}
80 + </div>
81 </div>
82
83 <n-modal
@@ -81,22 +97,22 @@
97 <code>{{ stream.remove_matches_from_default_stream }}</code>
98 </div>
99 <div class="mb-1">Rules :</div>
84 - <SimpleJsonViewer class="vuesjv-override" :model-value="stream.rules" :initialExpandedDepth="2" />
100 + <SimpleJsonViewer class="vuesjv-override" :model-value="stream.rules" :initial-expanded-depth="2" />
101 </n-modal>
102 </div>
103 </template>
104
105 <script setup lang="ts">
90 -import { type Stream } from "@/types/graylog/stream.d"
91 -import { useSettingsStore } from "@/stores/settings"
92 -import Icon from "@/components/common/Icon.vue"
106 +import type { Stream } from "@/types/graylog/stream.d"
107 +import Api from "@/api"
108 import Badge from "@/components/common/Badge.vue"
109 +import Icon from "@/components/common/Icon.vue"
110 +import { useSettingsStore } from "@/stores/settings"
111 import { formatDate } from "@/utils"
95 -import { NModal, NButton, useMessage } from "naive-ui"
112 +import { NButton, NModal, useMessage } from "naive-ui"
113 import { ref, toRefs } from "vue"
114 import { SimpleJsonViewer } from "vue-sjv"
115 import "@/assets/scss/vuesjv-override.scss"
99 -import Api from "@/api"
116
117 const props = defineProps<{ stream: Stream }>()
118 const { stream } = toRefs(props)
frontend/src/components/graylog/Streams/List.vue
+9 -9
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-5">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -47,8 +47,8 @@
47 <div class="px-3">
48 <div class="text-secondary-color text-sm mb-1">Enabled:</div>
49 <n-select
50 - size="small"
50 v-model:value="enabledFilter"
51 + size="small"
52 :options="enabledOptions"
53 clearable
54 placeholder="All"
@@ -59,8 +59,8 @@
59 <div class="px-3">
60 <div class="text-secondary-color text-sm mb-1">Editable:</div>
61 <n-select
62 - size="small"
62 v-model:value="editableFilter"
63 + size="small"
64 :options="editableOptions"
65 clearable
66 placeholder="All"
@@ -75,29 +75,29 @@
75 <StreamItem v-for="stream of itemsPaginated" :key="stream.id" :stream="stream" />
76 </template>
77 <template v-else>
78 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
78 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
79 </template>
80 </div>
81 <div class="footer flex justify-end">
82 <n-pagination
83 + v-if="itemsPaginated.length > 3"
84 v-model:page="currentPage"
85 :page-size="pageSize"
86 :item-count="total"
87 :page-slot="6"
87 - v-if="itemsPaginated.length > 3"
88 />
89 </div>
90 </n-spin>
91 </template>
92
93 <script setup lang="ts">
94 -import { ref, onBeforeMount, computed } from "vue"
95 -import { useMessage, NSpin, NPagination, NPopover, NButton, NSelect, NDivider, NEmpty } from "naive-ui"
94 +import type { Stream } from "@/types/graylog/stream.d"
95 import Api from "@/api"
97 -import StreamItem from "./Item.vue"
96 import Icon from "@/components/common/Icon.vue"
99 -import type { Stream } from "@/types/graylog/stream.d"
97 import { useResizeObserver } from "@vueuse/core"
98 +import { NButton, NDivider, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
99 +import { computed, onBeforeMount, ref } from "vue"
100 +import StreamItem from "./Item.vue"
101
102 const FilterIcon = "carbon:filter-edit"
103 const InfoIcon = "carbon:information"
frontend/src/components/healthcheck/HealthcheckItem.vue
+7 -5
@@ -11,8 +11,8 @@
11 <div class="main-box flex justify-between gap-4">
12 <div class="content flex gap-3 items-center">
13 <div class="level mt-1">
14 - <Icon :name="WarningIcon" :size="20" v-if="alert.level === InfluxDBAlertLevel.Crit" />
15 - <Icon :name="OKIcon" :size="20" v-else />
14 + <Icon v-if="alert.level === InfluxDBAlertLevel.Crit" :name="WarningIcon" :size="20" />
15 + <Icon v-else :name="OKIcon" :size="20" />
16 </div>
17 <div class="info grow">
18 <div class="message" v-html="message"></div>
@@ -20,7 +20,9 @@
20 </div>
21 </div>
22 <div class="footer-box flex justify-end items-center gap-3">
23 - <div class="time">{{ formatDate(alert.time) }}</div>
23 + <div class="time">
24 + {{ formatDate(alert.time) }}
25 + </div>
26 </div>
27 </div>
28 </template>
@@ -28,8 +30,8 @@
30 <script setup lang="ts">
31 import Icon from "@/components/common/Icon.vue"
32 import { useSettingsStore } from "@/stores/settings"
33 +import { type InfluxDBAlert, InfluxDBAlertLevel } from "@/types/healthchecks.d"
34 import dayjs from "@/utils/dayjs"
32 -import { InfluxDBAlertLevel, type InfluxDBAlert } from "@/types/healthchecks.d"
35 import { computed } from "vue"
36
37 const { alert } = defineProps<{ alert: InfluxDBAlert }>()
@@ -38,7 +40,7 @@ const WarningIcon = "carbon:warning-alt-filled"
40 const OKIcon = "carbon:checkmark-filled"
41
42 const message = computed(() => {
41 - return alert.message.replace(/\n/gim, " <span class='mx-1'>•</span> ")
43 + return alert.message.replace(/\n/g, " <span class='mx-1'>•</span> ")
44 })
45
46 const dFormats = useSettingsStore().dateFormat
frontend/src/components/healthcheck/HealthcheckList.vue
+7 -7
@@ -1,6 +1,6 @@
1 <template>
2 <div class="healthcheck-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -45,31 +45,31 @@
45 />
46 </template>
47 <template v-else>
48 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
48 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
49 </template>
50 </div>
51 </n-spin>
52 <div class="footer flex justify-end">
53 <n-pagination
54 + v-if="itemsPaginated.length > 3"
55 v-model:page="currentPage"
56 :page-size="pageSize"
57 :item-count="total"
58 :page-slot="6"
58 - v-if="itemsPaginated.length > 3"
59 />
60 </div>
61 </div>
62 </template>
63
64 <script setup lang="ts">
65 -import { ref, onBeforeMount, computed } from "vue"
66 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination } from "naive-ui"
65 import Api from "@/api"
68 -import _orderBy from "lodash/orderBy"
66 import Icon from "@/components/common/Icon.vue"
67 +import { type InfluxDBAlert, InfluxDBAlertLevel } from "@/types/healthchecks.d"
68 import { useResizeObserver } from "@vueuse/core"
69 +import _orderBy from "lodash/orderBy"
70 +import { NButton, NEmpty, NPagination, NPopover, NSpin, useMessage } from "naive-ui"
71 +import { computed, onBeforeMount, ref } from "vue"
72 import HealthcheckItem from "./HealthcheckItem.vue"
72 -import { InfluxDBAlertLevel, type InfluxDBAlert } from "@/types/healthchecks.d"
73
74 const message = useMessage()
75 const loading = ref(false)
frontend/src/components/incidentManagement/alerts/AlertAsset.vue
+13 -11
@@ -66,7 +66,7 @@
66 </n-tab-pane>
67 <n-tab-pane name="Context" tab="Context" display-directive="show">
68 <n-spin :show="loading" class="min-h-40">
69 - <div class="p-7 pt-4" v-if="alertContext">
69 + <div v-if="alertContext" class="p-7 pt-4">
70 <div class="flex flex-wrap gap-3 mb-4">
71 <Badge type="splitted">
72 <template #label>id</template>
@@ -74,7 +74,9 @@
74 </Badge>
75 <Badge type="splitted">
76 <template #label>source</template>
77 - <template #value>{{ alertContext.source }}</template>
77 + <template #value>
78 + {{ alertContext.source }}
79 + </template>
80 </Badge>
81 </div>
82
@@ -83,24 +85,24 @@
85 </n-spin>
86 </n-tab-pane>
87 <n-tab-pane
88 + v-if="isInvestigationAvailable"
89 name="Investigate"
90 tab="Investigate"
91 display-directive="show:lazy"
89 - v-if="isInvestigationAvailable"
92 >
93 <div class="p-7 pt-4">
94 <div class="flex flex-wrap gap-2">
95 <ThreatIntelProcessEvaluationProvider
96 v-for="pn of processNameList"
97 :key="pn"
96 - :process-name="pn"
98 v-slot="{ openEvaluation }"
99 + :process-name="pn"
100 >
101 <n-card
100 - @click="openEvaluation()"
102 size="small"
103 content-class="bg-secondary-color"
104 class="overflow-hidden hover:border-primary-color cursor-pointer"
105 + @click="openEvaluation()"
106 >
107 {{ pn }}
108 </n-card>
@@ -130,15 +132,16 @@
132 </template>
133
134 <script setup lang="ts">
133 -import { computed, defineAsyncComponent, ref, toRefs, watch } from "vue"
134 -import { NModal, NSpin, NCard, NTabs, NTabPane, useMessage } from "naive-ui"
135 +import type { AlertAsset, AlertContext } from "@/types/incidentManagement/alerts.d"
136 import Api from "@/api"
136 -import Icon from "@/components/common/Icon.vue"
137 import Badge from "@/components/common/Badge.vue"
138 +import Icon from "@/components/common/Icon.vue"
139 import { useGoto } from "@/composables/useGoto"
140 import _truncate from "lodash/truncate"
140 -import type { AlertAsset, AlertContext } from "@/types/incidentManagement/alerts.d"
141 +import { NCard, NModal, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
142 +import { computed, defineAsyncComponent, ref, toRefs, watch } from "vue"
143
144 +const props = defineProps<{ asset: AlertAsset; embedded?: boolean }>()
145 const AlertAssetInfo = defineAsyncComponent(() => import("./AlertAssetInfo.vue"))
146 const AlertDetailTimeline = defineAsyncComponent(() => import("./AlertDetailTimeline.vue"))
147 const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
@@ -148,7 +151,6 @@ const ThreatIntelProcessEvaluationProvider = defineAsyncComponent(
151 const ArtifactsCollect = defineAsyncComponent(() => import("@/components/artifacts/ArtifactsCollect.vue"))
152 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
153
151 -const props = defineProps<{ asset: AlertAsset; embedded?: boolean }>()
154 const { asset, embedded } = toRefs(props)
155
156 const LinkIcon = "carbon:launch"
@@ -158,7 +160,7 @@ const loading = ref(false)
160 const showDetails = ref(false)
161 const assetNameTruncated = computed(() => _truncate(asset.value.asset_name, { length: 50 }))
162 const alertContext = ref<AlertContext | null>(null)
161 -const processNameList = computed<string[]>(() => alertContext.value?.context?.["process_name"] || [])
163 +const processNameList = computed<string[]>(() => alertContext.value?.context?.process_name || [])
164 const isInvestigationAvailable = computed(() => processNameList.value.length)
165
166 watch(showDetails, val => {
frontend/src/components/incidentManagement/alerts/AlertAssetInfo.vue
+19 -10
@@ -1,7 +1,9 @@
1 <template>
2 <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
3 <KVCard v-for="(value, key) of asset" :key="key">
4 - <template #key>{{ key }}</template>
4 + <template #key>
5 + {{ key }}
6 + </template>
7 <template #value>
8 <div v-if="key === 'id'">#{{ value }}</div>
9 <div v-else-if="key === 'customer_code'">
@@ -31,7 +33,9 @@
33 <Icon :name="ViewIcon" :size="14" class="top-0.5 relative" />
34 </code>
35 </div>
34 - <div v-else>{{ value === "" ? "-" : value ?? "-" }}</div>
36 + <div v-else>
37 + {{ value === "" ? "-" : value ?? "-" }}
38 + </div>
39 </template>
40 </KVCard>
41 </div>
@@ -50,7 +54,9 @@
54 <n-tab-pane name="Info" tab="Info" display-directive="show">
55 <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
56 <KVCard v-for="(value, key) of alertDetailsInfo" :key="key">
53 - <template #key>{{ key }}</template>
57 + <template #key>
58 + {{ key }}
59 + </template>
60 <template #value>
61 <div v-if="key === '_index'">
62 <code
@@ -61,13 +67,15 @@
67 <Icon :name="LinkIcon" :size="14" class="top-0.5 relative" />
68 </code>
69 </div>
64 - <div v-else>{{ value === "" ? "-" : value ?? "-" }}</div>
70 + <div v-else>
71 + {{ value === "" ? "-" : value ?? "-" }}
72 + </div>
73 </template>
74 </KVCard>
75 </div>
76 </n-tab-pane>
77 <n-tab-pane name="Source" tab="Source" display-directive="show">
70 - <div class="p-7 pt-4" v-if="alertDetailsSource">
78 + <div v-if="alertDetailsSource" class="p-7 pt-4">
79 <CodeSource :code="alertDetailsSource" lang="json" />
80 </div>
81 </n-tab-pane>
@@ -77,18 +85,19 @@
85 </template>
86
87 <script setup lang="ts">
80 -import { computed, defineAsyncComponent, ref, toRefs, watch } from "vue"
81 -import { NModal, NSpin, NTabs, NTabPane, useMessage } from "naive-ui"
88 +import type { AlertAsset, AlertDetails } from "@/types/incidentManagement/alerts.d"
89 import Api from "@/api"
83 -import KVCard from "@/components/common/KVCard.vue"
90 import Icon from "@/components/common/Icon.vue"
91 +import KVCard from "@/components/common/KVCard.vue"
92 import { useGoto } from "@/composables/useGoto"
93 import _omit from "lodash/omit"
87 -import type { AlertAsset, AlertDetails } from "@/types/incidentManagement/alerts.d"
94 +import { NModal, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
95 +import { computed, defineAsyncComponent, ref, toRefs, watch } from "vue"
96 +
97 +const props = defineProps<{ asset: AlertAsset }>()
98
99 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
100
91 -const props = defineProps<{ asset: AlertAsset }>()
101 const { asset } = toRefs(props)
102
103 const LinkIcon = "carbon:launch"
frontend/src/components/incidentManagement/alerts/AlertAssetsList.vue
+3 -3
@@ -1,7 +1,7 @@
1 <template>
2 <div class="flex flex-col gap-2">
3 <template v-if="assets.length">
4 - <AlertAssetItem :asset embedded v-for="asset of assets" :key="asset.id" />
4 + <AlertAssetItem v-for="asset of assets" :key="asset.id" :asset embedded />
5 </template>
6 <template v-else>
7 <n-empty description="No assets found" class="justify-center h-48" />
@@ -10,10 +10,10 @@
10 </template>
11
12 <script setup lang="ts">
13 -import { toRefs } from "vue"
13 +import type { AlertAsset } from "@/types/incidentManagement/alerts.d"
14 import { NEmpty } from "naive-ui"
15 +import { toRefs } from "vue"
16 import AlertAssetItem from "./AlertAsset.vue"
16 -import type { AlertAsset } from "@/types/incidentManagement/alerts.d"
17
18 const props = defineProps<{ assets: AlertAsset[] }>()
19 const { assets } = toRefs(props)
frontend/src/components/incidentManagement/alerts/AlertAssignUser.vue
+5 -5
@@ -13,20 +13,20 @@
13 </template>
14
15 <script setup lang="ts">
16 -import { computed, inject, onBeforeMount, ref, toRefs, watch, type Ref } from "vue"
17 -import { useMessage, NPopselect } from "naive-ui"
18 -import Api from "@/api"
16 import type { Alert } from "@/types/incidentManagement/alerts.d"
17 +import Api from "@/api"
18 +import { NPopselect, useMessage } from "naive-ui"
19 +import { computed, inject, onBeforeMount, ref, type Ref, toRefs, watch } from "vue"
20
21 const props = defineProps<{
22 alert: Alert
23 }>()
24 -const { alert } = toRefs(props)
25 -
24 const emit = defineEmits<{
25 (e: "updated", value: Alert): void
26 }>()
27
28 +const { alert } = toRefs(props)
29 +
30 const loadingUsers = ref(false)
31 const users = inject<Ref<string[]>>("assignable-users", ref([]))
32 const message = useMessage()
frontend/src/components/incidentManagement/alerts/AlertComment.vue
+12 -9
@@ -1,12 +1,16 @@
1 <template>
2 <div class="alert-comment-item flex gap-3" :class="{ embedded }">
3 - <div class="user-pic" v-if="userPic">
3 + <div v-if="userPic" class="user-pic">
4 <n-avatar round :size="32" :src="userPic" />
5 </div>
6 <div class="comment grow flex flex-col gap-1">
7 <div class="user flex gap-3 items-center">
8 - <div class="user-name">{{ comment.user_name }}</div>
9 - <div class="comment-time">{{ formatDate(comment.created_at, dFormats.datetime) }}</div>
8 + <div class="user-name">
9 + {{ comment.user_name }}
10 + </div>
11 + <div class="comment-time">
12 + {{ formatDate(comment.created_at, dFormats.datetime) }}
13 + </div>
14 </div>
15 <div class="comment-message" v-html="message"></div>
16 </div>
@@ -14,19 +18,18 @@
18 </template>
19
20 <script setup lang="ts">
17 -import { onBeforeMount, ref, toRefs } from "vue"
18 -import { NAvatar } from "naive-ui"
19 -import { getAvatar, getNameInitials } from "@/utils"
20 -import { useSettingsStore } from "@/stores/settings"
21 -import { formatDate } from "@/utils"
21 import type { AlertComment } from "@/types/incidentManagement/alerts.d"
22 +import { useSettingsStore } from "@/stores/settings"
23 +import { formatDate, getAvatar, getNameInitials } from "@/utils"
24 +import { NAvatar } from "naive-ui"
25 +import { onBeforeMount, ref, toRefs } from "vue"
26
27 const props = defineProps<{ comment: AlertComment; embedded?: boolean }>()
28 const { comment, embedded } = toRefs(props)
29
30 const dFormats = useSettingsStore().dateFormat
31 const userPic = ref("")
29 -const message = ref(comment.value.comment.replace(/\n/gim, "<br/>"))
32 +const message = ref(comment.value.comment.replace(/\n/g, "<br/>"))
33
34 onBeforeMount(() => {
35 const initials = getNameInitials(comment.value.user_name)
frontend/src/components/incidentManagement/alerts/AlertCommentsList.vue
+13 -11
@@ -1,7 +1,7 @@
1 <template>
2 <div class="flex flex-col gap-6">
3 <template v-if="commentsList.length">
4 - <AlertCommentItem :comment embedded v-for="comment of commentsList" :key="comment.id" />
4 + <AlertCommentItem v-for="comment of commentsList" :key="comment.id" :comment embedded />
5 </template>
6 <template v-else>
7 <n-empty description="No comments found" class="justify-center h-48" />
@@ -10,9 +10,9 @@
10 <div class="comment-form mt-6 flex flex-col gap-3">
11 <div class="editor-box">
12 <n-input
13 + v-model:value="commentMessage"
14 placeholder="Write a new comment..."
15 type="textarea"
15 - v-model:value="commentMessage"
16 :autosize="{
17 minRows: 3,
18 maxRows: 10
@@ -20,14 +20,16 @@
20 />
21 </div>
22 <div class="tool-box flex gap-2 justify-end">
23 - <n-button secondary @click="reset()" :disabled="submitting">Reset</n-button>
23 + <n-button secondary :disabled="submitting" @click="reset()">Reset</n-button>
24 <n-button
25 type="primary"
26 - @click="submit()"
26 :disabled="!trimmedValue || submitting"
27 :loading="submitting"
28 + @click="submit()"
29 >
30 - <template #icon><Icon :name="CommentsIcon" /></template>
30 + <template #icon>
31 + <Icon :name="CommentsIcon" />
32 + </template>
33 Send comment
34 </n-button>
35 </div>
@@ -37,22 +39,22 @@
39 </template>
40
41 <script setup lang="ts">
40 -import { computed, ref, toRefs } from "vue"
41 -import { NEmpty, NInput, NButton, NSpin, useMessage } from "naive-ui"
42 -import AlertCommentItem from "./AlertComment.vue"
43 -import Icon from "@/components/common/Icon.vue"
42 import type { AlertComment } from "@/types/incidentManagement/alerts.d"
43 import Api from "@/api"
44 +import Icon from "@/components/common/Icon.vue"
45 import { useAuthStore } from "@/stores/auth"
46 import _trim from "lodash/trim"
47 +import { NButton, NEmpty, NInput, NSpin, useMessage } from "naive-ui"
48 +import { computed, ref, toRefs } from "vue"
49 +import AlertCommentItem from "./AlertComment.vue"
50
51 const props = defineProps<{ comments: AlertComment[]; alertId: number }>()
50 -const { comments, alertId } = toRefs(props)
51 -
52 const emit = defineEmits<{
53 (e: "updated", value: AlertComment[]): void
54 }>()
55
56 +const { comments, alertId } = toRefs(props)
57 +
58 const CommentsIcon = "carbon:chat"
59 const commentsList = ref<AlertComment[]>(comments.value)
60 const commentMessage = ref<string | null>(null)
frontend/src/components/incidentManagement/alerts/AlertCreateCaseButton.vue
+12 -9
@@ -1,24 +1,26 @@
1 <template>
2 - <n-button secondary type="primary" @click="createCase()" :loading="creating">
3 - <template #icon><Icon :name="DangerIcon" /></template>
2 + <n-button secondary type="primary" :loading="creating" @click="createCase()">
3 + <template #icon>
4 + <Icon :name="DangerIcon" />
5 + </template>
6 Create case
7 </n-button>
8 </template>
9
10 <script setup lang="ts">
9 -import { ref, toRefs } from "vue"
10 -import { NButton, useMessage } from "naive-ui"
11 -import Icon from "@/components/common/Icon.vue"
12 -import Api from "@/api"
11 import type { Alert } from "@/types/incidentManagement/alerts.d"
12 +import Api from "@/api"
13 +import Icon from "@/components/common/Icon.vue"
14 +import { NButton, useMessage } from "naive-ui"
15 +import { ref, toRefs } from "vue"
16
17 const props = defineProps<{ alert: Alert }>()
16 -const { alert } = toRefs(props)
17 -
18 const emit = defineEmits<{
19 (e: "updated", value: Alert): void
20 }>()
21
22 +const { alert } = toRefs(props)
23 +
24 const DangerIcon = "majesticons:exclamation-line"
25
26 const message = useMessage()
@@ -44,7 +46,8 @@ function createCase() {
46 case_description: "",
47 case_creation_time: new Date(),
48 assigned_to: null,
47 - case_status: null
49 + case_status: null,
50 + customer_code: null
51 }
52 ]
53 })
frontend/src/components/incidentManagement/alerts/AlertDetailTimeline.vue
+6 -6
@@ -9,20 +9,20 @@
9 :time="formatDateTime(item._source.timestamp)"
10 class="pb-4"
11 >
12 - <AlertDetailTimelineItem :timelineData="item" embedded class="-mt-3" />
12 + <AlertDetailTimelineItem :timeline-data="item" embedded class="-mt-3" />
13 </n-timeline-item>
14 </n-timeline>
15 </n-spin>
16 </template>
17
18 <script setup lang="ts">
19 -import { onBeforeMount, ref } from "vue"
20 -import { NTimeline, NTimelineItem, NSpin, useMessage } from "naive-ui"
21 -import AlertDetailTimelineItem from "./AlertDetailTimelineItem.vue"
19 +import type { AlertAsset, AlertTimeline } from "@/types/incidentManagement/alerts.d"
20 +import Api from "@/api"
21 import { useSettingsStore } from "@/stores/settings"
22 import { formatDate } from "@/utils"
24 -import Api from "@/api"
25 -import type { AlertAsset, AlertTimeline } from "@/types/incidentManagement/alerts.d"
23 +import { NSpin, NTimeline, NTimelineItem, useMessage } from "naive-ui"
24 +import { onBeforeMount, ref } from "vue"
25 +import AlertDetailTimelineItem from "./AlertDetailTimelineItem.vue"
26
27 const { asset } = defineProps<{ asset: AlertAsset }>()
28
frontend/src/components/incidentManagement/alerts/AlertDetailTimelineItem.vue
+13 -8
@@ -23,7 +23,9 @@
23 <n-tab-pane name="Info" tab="Info" display-directive="show">
24 <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
25 <KVCard v-for="(value, key) of timelineDetailsInfo" :key="key">
26 - <template #key>{{ key }}</template>
26 + <template #key>
27 + {{ key }}
28 + </template>
29 <template #value>
30 <div v-if="key === '_index'">
31 <code
@@ -34,13 +36,15 @@
36 <Icon :name="LinkIcon" :size="14" class="top-0.5 relative" />
37 </code>
38 </div>
37 - <div v-else>{{ value === "" ? "-" : value ?? "-" }}</div>
39 + <div v-else>
40 + {{ value === "" ? "-" : value ?? "-" }}
41 + </div>
42 </template>
43 </KVCard>
44 </div>
45 </n-tab-pane>
46 <n-tab-pane name="Source" tab="Source" display-directive="show">
43 - <div class="p-7 pt-4" v-if="timelineDetailsSource">
47 + <div v-if="timelineDetailsSource" class="p-7 pt-4">
48 <CodeSource :code="timelineDetailsSource" lang="json" />
49 </div>
50 </n-tab-pane>
@@ -50,17 +54,18 @@
54 </template>
55
56 <script setup lang="ts">
53 -import { computed, defineAsyncComponent, ref, toRefs } from "vue"
54 -import { NModal, NTabs, NTabPane } from "naive-ui"
55 -import KVCard from "@/components/common/KVCard.vue"
57 +import type { AlertTimeline } from "@/types/incidentManagement/alerts.d"
58 import Icon from "@/components/common/Icon.vue"
59 +import KVCard from "@/components/common/KVCard.vue"
60 import { useGoto } from "@/composables/useGoto"
61 import _omit from "lodash/omit"
59 -import type { AlertTimeline } from "@/types/incidentManagement/alerts.d"
62 +import { NModal, NTabPane, NTabs } from "naive-ui"
63 +import { computed, defineAsyncComponent, ref, toRefs } from "vue"
64 +
65 +const props = defineProps<{ timelineData: AlertTimeline; embedded?: boolean }>()
66
67 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
68
63 -const props = defineProps<{ timelineData: AlertTimeline; embedded?: boolean }>()
69 const { timelineData, embedded } = toRefs(props)
70
71 const LinkIcon = "carbon:launch"
frontend/src/components/incidentManagement/alerts/AlertDetails.vue
+12 -13
@@ -1,10 +1,10 @@
1 <template>
2 <n-spin :show="loading" class="flex flex-col grow" content-class="flex flex-col grow">
3 <n-tabs
4 + v-if="alert"
5 type="line"
6 animated
7 :tabs-padding="24"
7 - v-if="alert"
8 class="grow"
9 pane-wrapper-class="flex flex-col grow"
10 >
@@ -27,7 +27,7 @@
27 <div class="p-7 pt-4">
28 <AlertCommentsList
29 :comments="alert.comments"
30 - :alertId="alert.id"
30 + :alert-id="alert.id"
31 @updated="updateComments($event)"
32 />
33 </div>
@@ -37,27 +37,26 @@
37 </template>
38
39 <script setup lang="ts">
40 -import { defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
41 -import { NTabs, NTabPane, NSpin, useMessage } from "naive-ui"
42 -import _clone from "lodash/cloneDeep"
43 -import Api from "@/api"
40 import type { Alert, AlertComment } from "@/types/incidentManagement/alerts.d"
45 -
46 -const AlertTimeline = defineAsyncComponent(() => import("./AlertTimeline.vue"))
47 -const AlertAssetsList = defineAsyncComponent(() => import("./AlertAssetsList.vue"))
48 -const AlertCommentsList = defineAsyncComponent(() => import("./AlertCommentsList.vue"))
49 -const AlertOverview = defineAsyncComponent(() => import("./AlertOverview.vue"))
41 +import Api from "@/api"
42 +import _clone from "lodash/cloneDeep"
43 +import { NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
44 +import { defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
45
46 const props = defineProps<{
47 alertData?: Alert
48 alertId?: number
49 }>()
55 -const { alertData, alertId } = toRefs(props)
56 -
50 const emit = defineEmits<{
51 (e: "deleted"): void
52 (e: "updated", value: Alert): void
53 }>()
54 +const AlertTimeline = defineAsyncComponent(() => import("./AlertTimeline.vue"))
55 +const AlertAssetsList = defineAsyncComponent(() => import("./AlertAssetsList.vue"))
56 +const AlertCommentsList = defineAsyncComponent(() => import("./AlertCommentsList.vue"))
57 +const AlertOverview = defineAsyncComponent(() => import("./AlertOverview.vue"))
58 +
59 +const { alertData, alertId } = toRefs(props)
60
61 const message = useMessage()
62 const loading = ref(false)
frontend/src/components/incidentManagement/alerts/AlertItem.vue
+40 -31
@@ -1,24 +1,24 @@
1 <template>
2 <div
3 class="alert-item"
4 - :class="['status-' + alert?.status, { compact, embedded, 'cursor-pointer': compact, highlight }]"
4 + :class="[`status-${alert?.status}`, { compact, embedded, 'cursor-pointer': compact, highlight }]"
5 @click="compact ? openDetails() : undefined"
6 >
7 <n-spin :show="loading">
8 - <div class="flex flex-col" v-if="alert">
8 + <div v-if="alert" class="flex flex-col">
9 <div class="header-box px-5 py-3 pb-0 flex justify-between items-center">
10 <div class="id flex items-center gap-2 cursor-pointer" @click="openDetails()">
11 <span>#{{ alert.id }} - {{ alert.source }}</span>
12 - <Icon :name="InfoIcon" :size="16" v-if="!compact"></Icon>
12 + <Icon v-if="!compact" :name="InfoIcon" :size="16"></Icon>
13 </div>
14 <div class="time">
15 <n-popover
16 + v-if="!compact"
17 overlap
18 placement="top-end"
19 style="max-height: 240px"
20 scrollable
21 to="body"
21 - v-if="!compact"
22 >
23 <template #trigger>
24 <div class="flex items-center gap-2 cursor-help">
@@ -29,7 +29,7 @@
29 </div>
30 </template>
31 <div class="flex flex-col py-2 px-1">
32 - <AlertTimeline :alert v-if="alert" />
32 + <AlertTimeline v-if="alert" :alert />
33 </div>
34 </n-popover>
35 <span v-if="compact && alert.alert_creation_time">
@@ -45,7 +45,7 @@
45 </div>
46 </div>
47
48 - <div class="badges-box flex flex-wrap items-center gap-3" v-if="compact">
48 + <div v-if="compact" class="badges-box flex flex-wrap items-center gap-3">
49 <Badge
50 type="splitted"
51 class="cursor-pointer"
@@ -86,8 +86,8 @@
86 </template>
87 </Badge>
88 </div>
89 - <div class="badges-box flex flex-wrap items-center gap-3" v-else>
90 - <AlertStatusSwitch :alert v-slot="{ loading: loadingStatus }" @updated="updateAlert($event)">
89 + <div v-else class="badges-box flex flex-wrap items-center gap-3">
90 + <AlertStatusSwitch v-slot="{ loading: loadingStatus }" :alert @updated="updateAlert($event)">
91 <Badge
92 type="splitted"
93 class="cursor-pointer"
@@ -119,7 +119,7 @@
119 </Badge>
120 </AlertStatusSwitch>
121
122 - <AlertAssignUser :alert v-slot="{ loading: loadingAssignee }" @updated="updateAlert($event)">
122 + <AlertAssignUser v-slot="{ loading: loadingAssignee }" :alert @updated="updateAlert($event)">
123 <Badge
124 type="splitted"
125 class="cursor-pointer"
@@ -145,7 +145,7 @@
145 </Badge>
146 </AlertAssignUser>
147
148 - <Badge type="splitted" class="!hidden sm:!flex">
148 + <Badge v-if="alert.customer_code" type="splitted" class="!hidden sm:!flex">
149 <template #label>Customer</template>
150 <template #value>
151 <div class="flex items-center h-full">
@@ -162,13 +162,15 @@
162 </div>
163 </div>
164
165 - <div class="footer-box px-5 py-3 flex justify-between items-center" v-if="!compact">
165 + <div v-if="!compact" class="footer-box px-5 py-3 flex justify-between items-center">
166 <div class="details flex gap-3 items-center">
167 - <Badge type="splitted" v-if="alert.alert_creation_time" class="time">
167 + <Badge v-if="alert.alert_creation_time" type="splitted" class="time">
168 <template #iconLeft>
169 <Icon :name="TimeIcon" :size="16" />
170 </template>
171 - <template #value>{{ formatDate(alert.alert_creation_time, dFormats.datetime) }}</template>
171 + <template #value>
172 + {{ formatDate(alert.alert_creation_time, dFormats.datetime) }}
173 + </template>
174 </Badge>
175
176 <n-tooltip trigger="hover">
@@ -191,7 +193,9 @@
193 <template #iconLeft>
194 <Icon :name="CommentsIcon" :size="16" />
195 </template>
194 - <template #value>{{ alert.comments?.length || 0 }}</template>
196 + <template #value>
197 + {{ alert.comments?.length || 0 }}
198 + </template>
199 </Badge>
200 </template>
201 Comments
@@ -202,7 +206,7 @@
206 </span>
207 </div>
208 <div class="actions-box">
205 - <n-button quaternary size="tiny" @click="handleDelete()">Delete</n-button>
209 + <n-button quaternary size="tiny" @click.stop="handleDelete()">Delete</n-button>
210 </div>
211 </div>
212 </div>
@@ -217,36 +221,36 @@
221 content-class="flex flex-col !p-0"
222 :title="alertNameTruncated"
223 closable
220 - @close="closeDetails()"
224 :bordered="false"
225 segmented
226 role="modal"
227 + @close="closeDetails()"
228 >
225 - <AlertDetails v-if="alert" :alertData="alert" @deleted="emitDelete()" @updated="updateAlert($event)" />
229 + <AlertDetails v-if="alert" :alert-data="alert" @deleted="emitDelete()" @updated="updateAlert($event)" />
230 </n-card>
231 </n-modal>
232 </div>
233 </template>
234
235 <script setup lang="ts">
232 -import { computed, onBeforeMount, onMounted, ref, toRefs } from "vue"
233 -import { NModal, NPopover, NButton, NSpin, NTooltip, NCard, useMessage, useDialog } from "naive-ui"
234 -import { useSettingsStore } from "@/stores/settings"
236 +import type { Alert } from "@/types/incidentManagement/alerts.d"
237 +import Api from "@/api"
238 +import Badge from "@/components/common/Badge.vue"
239 +import Icon from "@/components/common/Icon.vue"
240 import { useGoto } from "@/composables/useGoto"
241 +import { useSettingsStore } from "@/stores/settings"
242 import { formatDate } from "@/utils"
243 import _clone from "lodash/cloneDeep"
238 -import Api from "@/api"
239 -import Icon from "@/components/common/Icon.vue"
240 -import Badge from "@/components/common/Badge.vue"
241 -import AlertTimeline from "./AlertTimeline.vue"
242 -import AlertAssignUser from "./AlertAssignUser.vue"
243 -import AlertStatusSwitch from "./AlertStatusSwitch.vue"
244 -import StatusIcon from "../common/StatusIcon.vue"
244 +import _truncate from "lodash/truncate"
245 +import { NButton, NCard, NModal, NPopover, NSpin, NTooltip, useDialog, useMessage } from "naive-ui"
246 +import { computed, onBeforeMount, onMounted, ref, toRefs } from "vue"
247 import AssigneeIcon from "../common/AssigneeIcon.vue"
248 +import StatusIcon from "../common/StatusIcon.vue"
249 +import AlertAssignUser from "./AlertAssignUser.vue"
250 import AlertDetails from "./AlertDetails.vue"
251 +import AlertStatusSwitch from "./AlertStatusSwitch.vue"
252 +import AlertTimeline from "./AlertTimeline.vue"
253 import { handleDeleteAlert } from "./utils"
248 -import _truncate from "lodash/truncate"
249 -import type { Alert } from "@/types/incidentManagement/alerts.d"
254
255 const props = defineProps<{
256 alertData?: Alert
@@ -256,14 +260,14 @@ const props = defineProps<{
260 detailsOnMounted?: boolean
261 highlight?: boolean
262 }>()
259 -const { alertData, alertId, compact, embedded, detailsOnMounted, highlight } = toRefs(props)
260 -
263 const emit = defineEmits<{
264 (e: "opened"): void
265 (e: "deleted"): void
266 (e: "updated", value: Alert): void
267 }>()
268
269 +const { alertData, alertId, compact, embedded, detailsOnMounted, highlight } = toRefs(props)
270 +
271 const InfoIcon = "carbon:information"
272 const LinkIcon = "carbon:launch"
273 const TimeIcon = "carbon:time"
@@ -391,6 +395,11 @@ onMounted(() => {
395
396 &.embedded {
397 background-color: var(--bg-secondary-color);
398 + border: var(--border-small-100);
399 +
400 + .footer-box {
401 + background-color: var(--bg-body);
402 + }
403 }
404
405 &:hover {
frontend/src/components/incidentManagement/alerts/AlertLinkedCases.vue
+3 -3
@@ -1,8 +1,8 @@
1 <template>
2 <code
3 - class="cursor-pointer text-primary-color"
3 v-for="linkedCase of linkedCases"
4 :key="linkedCase.id"
5 + class="cursor-pointer text-primary-color"
6 @click="gotoIncidentManagementCases(linkedCase.id)"
7 >
8 #{{ linkedCase.id }}
@@ -11,10 +11,10 @@
11 </template>
12
13 <script setup lang="ts">
14 -import { computed, toRefs } from "vue"
14 +import type { Alert } from "@/types/incidentManagement/alerts.d"
15 import Icon from "@/components/common/Icon.vue"
16 import { useGoto } from "@/composables/useGoto"
17 -import type { Alert } from "@/types/incidentManagement/alerts.d"
17 +import { computed, toRefs } from "vue"
18
19 const props = defineProps<{ alert: Alert }>()
20 const { alert } = toRefs(props)
frontend/src/components/incidentManagement/alerts/AlertMergeCaseButton.vue
+20 -15
@@ -1,6 +1,8 @@
1 <template>
2 - <n-button secondary @click="openDialog()" :loading="merging">
3 - <template #icon><Icon :name="MergeIcon" /></template>
2 + <n-button secondary :loading="merging" @click="openDialog()">
3 + <template #icon>
4 + <Icon :name="MergeIcon" />
5 + </template>
6 Merge into Case
7 </n-button>
8
@@ -24,15 +26,15 @@
26 <CaseItem
27 v-for="item of linkableCases"
28 :key="item.id"
27 - :caseData="item"
29 + :case-data="item"
30 compact
31 embedded
30 - @click="toggleSelectedCase(item)"
32 :class="{ active: selectedCase?.id === item.id }"
33 + @click="toggleSelectedCase(item)"
34 />
35 </template>
36 <template v-else>
35 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingCases" />
37 + <n-empty v-if="!loadingCases" description="No items found" class="justify-center h-48" />
38 </template>
39 </div>
40 </n-scrollbar>
@@ -41,7 +43,9 @@
43 <template #footer>
44 <div class="flex justify-end">
45 <n-button type="success" :disabled="!selectedCase" :loading="merging" @click="linkCase()">
44 - <template #icon><Icon :name="MergeIcon" /></template>
46 + <template #icon>
47 + <Icon :name="MergeIcon" />
48 + </template>
49 Confirm Merge {{ selectedCase ? `with Case #${selectedCase.id}` : "" }}
50 </n-button>
51 </div>
@@ -50,22 +54,22 @@
54 </template>
55
56 <script setup lang="ts">
53 -import { inject, ref, toRefs, watch, type Ref } from "vue"
54 -import { NButton, NModal, NSpin, NEmpty, NScrollbar, useMessage } from "naive-ui"
55 -import Icon from "@/components/common/Icon.vue"
56 -import CaseItem from "../cases/CaseItem.vue"
57 -import _orderBy from "lodash/orderBy"
58 -import Api from "@/api"
57 import type { Alert } from "@/types/incidentManagement/alerts.d"
58 import type { Case } from "@/types/incidentManagement/cases.d"
59 +import Api from "@/api"
60 +import Icon from "@/components/common/Icon.vue"
61 +import _orderBy from "lodash/orderBy"
62 +import { NButton, NEmpty, NModal, NScrollbar, NSpin, useMessage } from "naive-ui"
63 +import { inject, ref, type Ref, toRefs, watch } from "vue"
64 +import CaseItem from "../cases/CaseItem.vue"
65
66 const props = defineProps<{ alert: Alert }>()
63 -const { alert } = toRefs(props)
64 -
67 const emit = defineEmits<{
68 (e: "updated", value: Alert): void
69 }>()
70
71 +const { alert } = toRefs(props)
72 +
73 const MergeIcon = "carbon:ibm-cloud-direct-link-1-connect"
74
75 const message = useMessage()
@@ -139,7 +143,8 @@ function linkCase() {
143 case_description: "",
144 case_creation_time: new Date(),
145 assigned_to: null,
142 - case_status: null
146 + case_status: null,
147 + customer_code: null
148 }
149 ]
150 })
frontend/src/components/incidentManagement/alerts/AlertOverview.vue
+31 -21
@@ -19,8 +19,8 @@
19 <template #value>
20 <div class="flex">
21 <AlertStatusSwitch
22 - :alert
22 v-slot="{ loading: loadingStatus }"
23 + :alert
24 @updated="updateAlert($event)"
25 >
26 <div
@@ -54,8 +54,8 @@
54 <template #value>
55 <div class="flex">
56 <AlertAssignUser
57 - :alert
57 v-slot="{ loading: loadingAssignee }"
58 + :alert
59 @updated="updateAlert($event)"
60 >
61 <div
@@ -83,7 +83,9 @@
83 <div class="px-7">
84 <KVCard>
85 <template #key>description</template>
86 - <template #value>{{ alert.alert_description ?? "-" }}</template>
86 + <template #value>
87 + {{ alert.alert_description ?? "-" }}
88 + </template>
89 </KVCard>
90 </div>
91
@@ -95,7 +97,9 @@
97
98 <KVCard>
99 <template #key>source</template>
98 - <template #value>{{ alert.source ?? "-" }}</template>
100 + <template #value>
101 + {{ alert.source ?? "-" }}
102 + </template>
103 </KVCard>
104
105 <KVCard>
@@ -113,12 +117,16 @@
117
118 <KVCard>
119 <template #key>assets</template>
116 - <template #value>{{ alert.assets.length }}</template>
120 + <template #value>
121 + {{ alert.assets.length }}
122 + </template>
123 </KVCard>
124
125 <KVCard>
126 <template #key>comments</template>
121 - <template #value>{{ alert.comments.length }}</template>
127 + <template #value>
128 + {{ alert.comments.length }}
129 + </template>
130 </KVCard>
131
132 <KVCard>
@@ -131,9 +139,9 @@
139 </div>
140
141 <div class="footer-box px-7 py-4 flex items-center gap-2">
134 - <AlertCreateCaseButton :alert @updated="updateAlert" v-if="!linkedCases.length" />
142 + <AlertCreateCaseButton v-if="!linkedCases.length" :alert @updated="updateAlert" />
143
136 - <AlertMergeCaseButton :alert @updated="updateAlert" v-if="!linkedCases.length" />
144 + <AlertMergeCaseButton v-if="!linkedCases.length" :alert @updated="updateAlert" />
145
146 <div v-if="linkedCases.length" class="flex flex-wrap gap-3 items-center">
147 <span>Linked Cases:</span>
@@ -143,7 +151,9 @@
151 <div class="grow"></div>
152
153 <n-button type="error" secondary @click="handleDelete()">
146 - <template #icon><Icon :name="TrashIcon" /></template>
154 + <template #icon>
155 + <Icon :name="TrashIcon" />
156 + </template>
157 Delete
158 </n-button>
159 </div>
@@ -152,29 +162,29 @@
162 </template>
163
164 <script setup lang="ts">
165 +import type { Alert } from "@/types/incidentManagement/alerts.d"
166 +import Icon from "@/components/common/Icon.vue"
167 +import KVCard from "@/components/common/KVCard.vue"
168 +import { useGoto } from "@/composables/useGoto"
169 +import { NButton, NSpin, useDialog, useMessage } from "naive-ui"
170 import { computed, defineAsyncComponent, ref, toRefs } from "vue"
156 -import { NButton, NSpin, useMessage, useDialog } from "naive-ui"
171 +import AssigneeIcon from "../common/AssigneeIcon.vue"
172 +import StatusIcon from "../common/StatusIcon.vue"
173 import AlertAssignUser from "./AlertAssignUser.vue"
174 import AlertStatusSwitch from "./AlertStatusSwitch.vue"
175 import AlertTags from "./AlertTags.vue"
160 -import StatusIcon from "../common/StatusIcon.vue"
161 -import AssigneeIcon from "../common/AssigneeIcon.vue"
162 -import KVCard from "@/components/common/KVCard.vue"
163 -import Icon from "@/components/common/Icon.vue"
164 -import { useGoto } from "@/composables/useGoto"
176 import { handleDeleteAlert } from "./utils"
166 -import type { Alert } from "@/types/incidentManagement/alerts.d"
167 -const AlertCreateCaseButton = defineAsyncComponent(() => import("./AlertCreateCaseButton.vue"))
168 -const AlertMergeCaseButton = defineAsyncComponent(() => import("./AlertMergeCaseButton.vue"))
169 -const AlertLinkedCases = defineAsyncComponent(() => import("./AlertLinkedCases.vue"))
177
178 const props = defineProps<{ alert: Alert }>()
172 -const { alert } = toRefs(props)
173 -
179 const emit = defineEmits<{
180 (e: "deleted"): void
181 (e: "updated", value: Alert): void
182 }>()
183 +const AlertCreateCaseButton = defineAsyncComponent(() => import("./AlertCreateCaseButton.vue"))
184 +const AlertMergeCaseButton = defineAsyncComponent(() => import("./AlertMergeCaseButton.vue"))
185 +const AlertLinkedCases = defineAsyncComponent(() => import("./AlertLinkedCases.vue"))
186 +
187 +const { alert } = toRefs(props)
188
189 const TrashIcon = "carbon:trash-can"
190 const LinkIcon = "carbon:launch"
frontend/src/components/incidentManagement/alerts/AlertStatusSwitch.vue
+5 -5
@@ -13,20 +13,20 @@
13 </template>
14
15 <script setup lang="ts">
16 -import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
17 -import { useMessage, NPopselect } from "naive-ui"
18 -import Api from "@/api"
16 import type { Alert, AlertStatus } from "@/types/incidentManagement/alerts.d"
17 +import Api from "@/api"
18 +import { NPopselect, useMessage } from "naive-ui"
19 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
20
21 const props = defineProps<{
22 alert: Alert
23 }>()
24 -const { alert } = toRefs(props)
25 -
24 const emit = defineEmits<{
25 (e: "updated", value: Alert): void
26 }>()
27
28 +const { alert } = toRefs(props)
29 +
30 const loading = ref(false)
31 const message = useMessage()
32 const listVisible = ref(false)
frontend/src/components/incidentManagement/alerts/AlertTags.vue
+8 -8
@@ -1,10 +1,10 @@
1 <template>
2 <n-spin :show="deletingTag" size="small" content-class="flex flex-wrap gap-2">
3 - <n-tag closable @close="deleteTag(tag.id)" v-for="tag of alert.tags" :key="tag.id" size="small">
3 + <n-tag v-for="tag of alert.tags" :key="tag.id" closable size="small" @close="deleteTag(tag.id)">
4 {{ tag.tag }}
5 </n-tag>
6
7 - <n-dynamic-tags @create="newAlertTag" size="small" :value="[]">
7 + <n-dynamic-tags size="small" :value="[]" @create="newAlertTag">
8 <template #trigger="{ activate, disabled }">
9 <n-button
10 size="tiny"
@@ -25,20 +25,20 @@
25 </template>
26
27 <script setup lang="ts">
28 -import { ref, toRefs } from "vue"
29 -import { NButton, NSpin, NTag, NDynamicTags, useMessage } from "naive-ui"
28 +import type { Alert } from "@/types/incidentManagement/alerts.d"
29 +import Api from "@/api"
30 import Icon from "@/components/common/Icon.vue"
31 import _trim from "lodash/trim"
32 -import Api from "@/api"
33 -import type { Alert } from "@/types/incidentManagement/alerts.d"
32 +import { NButton, NDynamicTags, NSpin, NTag, useMessage } from "naive-ui"
33 +import { ref, toRefs } from "vue"
34
35 const props = defineProps<{ alert: Alert }>()
36 -const { alert } = toRefs(props)
37 -
36 const emit = defineEmits<{
37 (e: "updated", value: Alert): void
38 }>()
39
40 +const { alert } = toRefs(props)
41 +
42 const AddIcon = "carbon:add"
43
44 const message = useMessage()
frontend/src/components/incidentManagement/alerts/AlertTimeline.vue
+1 -1
@@ -6,10 +6,10 @@
6 </template>
7
8 <script setup lang="ts">
9 +import type { Alert } from "@/types/incidentManagement/alerts.d"
10 import { useSettingsStore } from "@/stores/settings"
11 import { formatDate } from "@/utils"
12 import { NTimeline, NTimelineItem } from "naive-ui"
12 -import type { Alert } from "@/types/incidentManagement/alerts.d"
13
14 const { alert } = defineProps<{ alert: Alert }>()
15
frontend/src/components/incidentManagement/alerts/AlertsList.vue
+94 -31
@@ -1,6 +1,6 @@
1 <template>
2 <div class="alerts-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2 lg:!hidden">
5 <n-popover overlap placement="left">
6 <template #trigger>
@@ -55,14 +55,14 @@
55 :simple="simpleMode"
56 />
57 <n-select
58 - size="small"
58 v-model:value="sort"
59 + size="small"
60 :options="sortOptions"
61 :show-checkmark="false"
62 class="max-w-20"
63 :disabled="loading"
64 />
65 - <n-popover :show="showFilters" trigger="manual" overlap placement="right" class="!px-0" v-if="!hideFilters">
65 + <n-popover v-if="!hideFilters" :show="showFilters" trigger="manual" overlap placement="right" class="!px-0">
66 <template #trigger>
67 <div class="bg-color border-radius">
68 <n-badge :show="filtered" dot type="success" :offset="[-4, 0]">
@@ -82,7 +82,7 @@
82 :options="typeOptions"
83 placeholder="Filter by..."
84 clearable
85 - class="!w-36"
85 + class="!w-40"
86 />
87
88 <n-select
@@ -101,6 +101,27 @@
101 placeholder="Value..."
102 :disabled="!filters.type"
103 clearable
104 + filterable
105 + class="!w-56"
106 + />
107 + <n-select
108 + v-else-if="filters.type === 'customerCode'"
109 + v-model:value="filters.value"
110 + :options="customersOptions"
111 + placeholder="Value..."
112 + :disabled="!filters.type"
113 + clearable
114 + filterable
115 + class="!w-56"
116 + />
117 + <n-select
118 + v-else-if="filters.type === 'source'"
119 + v-model:value="filters.value"
120 + :options="sourcesOptions"
121 + placeholder="Value..."
122 + :disabled="!filters.type"
123 + clearable
124 + filterable
125 class="!w-56"
126 />
127 <n-input
@@ -115,11 +136,11 @@
136 </div>
137 <div class="px-3 flex justify-between gap-2">
138 <div class="flex justify-start gap-2">
118 - <n-button size="small" @click="showFilters = false" quaternary>Close</n-button>
139 + <n-button size="small" quaternary @click="showFilters = false">Close</n-button>
140 </div>
141 <div class="flex justify-end gap-2">
121 - <n-button size="small" @click="resetFilters()" secondary>Reset</n-button>
122 - <n-button size="small" @click="getData()" type="primary" secondary :loading>
142 + <n-button size="small" secondary @click="resetFilters()">Reset</n-button>
143 + <n-button size="small" type="primary" secondary :loading @click="getData()">
144 Submit
145 </n-button>
146 </div>
@@ -133,9 +154,9 @@
154 <AlertItem
155 v-for="alert of alertsList"
156 :key="alert.id"
136 - :alertData="alert"
157 + :alert-data="alert"
158 :highlight="highlight === alert.id.toString()"
138 - :detailsOnMounted="highlight === alert.id.toString() && !highlightedItemOpened"
159 + :details-on-mounted="highlight === alert.id.toString() && !highlightedItemOpened"
160 class="item-appear item-appear-bottom item-appear-005"
161 @opened="highlightedItemOpened = true"
162 @deleted="getData()"
@@ -143,49 +164,51 @@
164 />
165 </template>
166 <template v-else>
146 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
167 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
168 </template>
169 </div>
170 </n-spin>
171 <div class="footer flex justify-end">
172 <n-pagination
173 + v-if="alertsList.length > 3"
174 v-model:page="currentPage"
175 :page-size="pageSize"
176 :item-count="total"
177 :page-slot="6"
156 - v-if="alertsList.length > 3"
178 />
179 </div>
180 </div>
181 </template>
182
183 <script setup lang="ts">
163 -import { ref, onBeforeMount, computed, watch, provide, toRefs, nextTick } from "vue"
184 +import type { AlertsFilterTypes, AlertsQuery } from "@/api/endpoints/incidentManagement"
185 +import type { Customer } from "@/types/customers.d"
186 +import type { Alert, AlertStatus } from "@/types/incidentManagement/alerts.d"
187 +import type { Case } from "@/types/incidentManagement/cases.d"
188 +import type { SourceName } from "@/types/incidentManagement/sources.d"
189 +import Api from "@/api"
190 +import Icon from "@/components/common/Icon.vue"
191 +import { useResizeObserver } from "@vueuse/core"
192 +import axios from "axios"
193 +import _cloneDeep from "lodash/cloneDeep"
194 +import _orderBy from "lodash/orderBy"
195 import {
165 - useMessage,
166 - NSpin,
167 - NPopover,
196 + NBadge,
197 NButton,
198 NEmpty,
170 - NSelect,
171 - NPagination,
199 + NInput,
200 NInputGroup,
173 - NBadge,
174 - NInput
201 + NPagination,
202 + NPopover,
203 + NSelect,
204 + NSpin,
205 + useMessage
206 } from "naive-ui"
176 -import Api from "@/api"
207 +import { computed, nextTick, onBeforeMount, provide, ref, toRefs, watch } from "vue"
208 import AlertItem from "./AlertItem.vue"
178 -import Icon from "@/components/common/Icon.vue"
179 -import _cloneDeep from "lodash/cloneDeep"
180 -import _orderBy from "lodash/orderBy"
181 -import axios from "axios"
182 -import { useResizeObserver } from "@vueuse/core"
183 -import type { Alert, AlertStatus } from "@/types/incidentManagement/alerts.d"
184 -import type { AlertsQuery } from "@/api/endpoints/incidentManagement"
185 -import type { Case } from "@/types/incidentManagement/cases.d"
209
210 export interface AlertsListFilter {
188 - type: "status" | "assetName" | "assignedTo" | "tag" | "title"
211 + type: AlertsFilterTypes
212 value: string | AlertStatus
213 }
214
@@ -200,6 +223,8 @@ const loading = ref(false)
223 const showFilters = ref(false)
224 const alertsList = ref<Alert[]>([])
225 const availableUsers = ref<string[]>([])
226 +const configuredSourcesList = ref<SourceName[]>([])
227 +const customersList = ref<Customer[]>([])
228 const linkableCases = ref<Case[]>([])
229 let abortController: AbortController | null = null
230
@@ -228,12 +253,14 @@ const filtered = computed<boolean>(() => {
253 return !!filters.value.type && !!filters.value.value
254 })
255
231 -const typeOptions = [
256 +const typeOptions: { label: string; value: AlertsFilterTypes }[] = [
257 { label: "Status", value: "status" },
258 { label: "Asset Name", value: "assetName" },
259 { label: "Assigned To", value: "assignedTo" },
260 { label: "Tag", value: "tag" },
236 - { label: "Title", value: "title" }
261 + { label: "Title", value: "title" },
262 + { label: "Customer Code", value: "customerCode" },
263 + { label: "Source", value: "source" }
264 ]
265
266 const statusOptions: { label: string; value: AlertStatus }[] = [
@@ -243,6 +270,10 @@ const statusOptions: { label: string; value: AlertStatus }[] = [
270 ]
271
272 const usersOptions = computed(() => availableUsers.value.map(o => ({ label: o, value: o })))
273 +const customersOptions = computed(() =>
274 + customersList.value.map(o => ({ label: `#${o.customer_code} - ${o.customer_name}`, value: o.customer_code }))
275 +)
276 +const sourcesOptions = computed(() => configuredSourcesList.value.map(o => ({ label: o, value: o })))
277
278 const highlightedItemFound = ref(!highlight.value)
279 const highlightedItemOpened = ref(!highlight.value)
@@ -371,6 +402,36 @@ function getAvailableUsers() {
402 })
403 }
404
405 +function getCustomers() {
406 + Api.customers
407 + .getCustomers()
408 + .then(res => {
409 + if (res.data.success) {
410 + customersList.value = res.data?.customers || []
411 + } else {
412 + message.warning(res.data?.message || "An error occurred. Please try again later.")
413 + }
414 + })
415 + .catch(err => {
416 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
417 + })
418 +}
419 +
420 +function getConfiguredSources() {
421 + Api.incidentManagement
422 + .getConfiguredSources()
423 + .then(res => {
424 + if (res.data.success) {
425 + configuredSourcesList.value = res.data?.sources || []
426 + } else {
427 + message.warning(res.data?.message || "An error occurred. Please try again later.")
428 + }
429 + })
430 + .catch(err => {
431 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
432 + })
433 +}
434 +
435 function getCases() {
436 Api.incidentManagement
437 .getCasesList()
@@ -402,7 +463,9 @@ onBeforeMount(() => {
463
464 getData()
465 getAvailableUsers()
466 + getCustomers()
467 getCases()
468 + getConfiguredSources()
469 })
470 </script>
471
frontend/src/components/incidentManagement/alerts/utils.ts
+4 -4
@@ -1,8 +1,8 @@
1 -import { h } from "vue"
2 -import Api from "@/api"
3 -import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
4 -import type { DialogApiInjection } from "naive-ui/es/dialog/src/DialogProvider"
1 import type { Alert } from "@/types/incidentManagement/alerts.d"
2 +import type { DialogApiInjection } from "naive-ui/es/dialog/src/DialogProvider"
3 +import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
4 +import Api from "@/api"
5 +import { h } from "vue"
6
7 export interface DeleteAlertParams {
8 alert: Alert
frontend/src/components/incidentManagement/cases/CaseAssignUser.vue
+5 -5
@@ -13,20 +13,20 @@
13 </template>
14
15 <script setup lang="ts">
16 -import { computed, inject, onBeforeMount, ref, toRefs, watch, type Ref } from "vue"
17 -import { useMessage, NPopselect } from "naive-ui"
18 -import Api from "@/api"
16 import type { Case } from "@/types/incidentManagement/cases.d"
17 +import Api from "@/api"
18 +import { NPopselect, useMessage } from "naive-ui"
19 +import { computed, inject, onBeforeMount, ref, type Ref, toRefs, watch } from "vue"
20
21 const props = defineProps<{
22 caseData: Case
23 }>()
24 -const { caseData } = toRefs(props)
25 -
24 const emit = defineEmits<{
25 (e: "updated", value: Case): void
26 }>()
27
28 +const { caseData } = toRefs(props)
29 +
30 const loadingUsers = ref(false)
31 const users = inject<Ref<string[]>>("assignable-users", ref([]))
32 const message = useMessage()
frontend/src/components/incidentManagement/cases/CaseCreationButton.vue
+9 -7
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button size="small" type="primary" @click="showForm = true" :loading="loading">
2 + <n-button :size type="primary" :loading="loading" @click="showForm = true">
3 <template #icon>
4 <Icon :name="NewCaseIcon" :size="14"></Icon>
5 </template>
@@ -16,26 +16,28 @@
16 segmented
17 >
18 <CaseCreationForm
19 - @mounted="formCTX = $event"
19 v-model:loading="loading"
20 + @mounted="formCTX = $event"
21 @submitted="emit('submitted', $event)"
22 />
23 </n-modal>
24 </template>
25
26 <script setup lang="ts">
27 -import { ref, toRefs, watch } from "vue"
28 -import { NButton, NModal } from "naive-ui"
27 +import type { Case } from "@/types/incidentManagement/cases"
28 +import type { Size } from "naive-ui/es/button/src/interface"
29 import Icon from "@/components/common/Icon.vue"
30 +import { NButton, NModal } from "naive-ui"
31 +import { ref, toRefs, watch } from "vue"
32 import CaseCreationForm from "./CaseCreationForm.vue"
31 -import type { Case } from "@/types/incidentManagement/cases"
33 +
34 +const props = defineProps<{ onlyIcon?: boolean; size?: Size }>()
35
36 const emit = defineEmits<{
37 (e: "submitted", value: Case): void
38 }>()
39
37 -const props = defineProps<{ onlyIcon?: boolean }>()
38 -const { onlyIcon } = toRefs(props)
40 +const { onlyIcon, size } = toRefs(props)
41
42 const NewCaseIcon = "carbon:fetch-upload-cloud"
43 const formCTX = ref<{ load: () => void } | null>(null)
frontend/src/components/incidentManagement/cases/CaseCreationForm.vue
+63 -18
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="customer-provisioning-default-settings-form">
3 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-3">
5 <div>
6 <n-form-item label="Name" path="case_name">
@@ -41,14 +41,25 @@
41 />
42 </n-form-item>
43 </div>
44 + <div>
45 + <n-form-item label="Customer" path="customer_code">
46 + <n-select
47 + v-model:value="form.customer_code"
48 + :options="customersOptions"
49 + placeholder="Value..."
50 + clearable
51 + to="body"
52 + />
53 + </n-form-item>
54 + </div>
55
56 <div class="flex justify-between gap-4">
57 <div class="flex gap-4">
58 <slot name="additionalActions"></slot>
59 </div>
60 <div class="flex gap-4">
50 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
51 - <n-button type="primary" :disabled="!isValid" @click="validate()" :loading="submitting">
61 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
62 + <n-button type="primary" :disabled="!isValid" :loading="submitting" @click="validate()">
63 Submit
64 </n-button>
65 </div>
@@ -59,24 +70,24 @@
70 </template>
71
72 <script setup lang="ts">
62 -import { computed, inject, onBeforeMount, onMounted, ref, watch, type Ref } from "vue"
73 +import type { Customer } from "@/types/customers.d"
74 +import type { Case, CasePayload, CaseStatus } from "@/types/incidentManagement/cases.d"
75 +import Api from "@/api"
76 +import _get from "lodash/get"
77 +import _trim from "lodash/trim"
78 import {
79 + type FormInst,
80 + type FormRules,
81 + type FormValidationError,
82 + NButton,
83 NForm,
84 NFormItem,
85 NInput,
67 - NButton,
68 - NSpin,
86 NSelect,
70 - useMessage,
71 - type FormValidationError,
72 - type FormInst,
73 - type FormRules
87 + NSpin,
88 + useMessage
89 } from "naive-ui"
75 -import Api from "@/api"
76 -import _trim from "lodash/trim"
77 -import _get from "lodash/get"
78 -import type { AlertStatus } from "@/types/incidentManagement/alerts.d"
79 -import type { Case, CasePayload } from "@/types/incidentManagement/cases.d"
90 +import { computed, inject, onBeforeMount, onMounted, ref, type Ref, watch } from "vue"
91
92 const emit = defineEmits<{
93 (e: "update:loading", value: boolean): void
@@ -90,12 +101,14 @@ const emit = defineEmits<{
101 }>()
102
103 const loadingAvailableUsers = ref(false)
104 +const loadingCustomersList = ref(false)
105 const submitting = ref(false)
94 -const loading = computed(() => loadingAvailableUsers.value || submitting.value)
106 +const loading = computed(() => loadingAvailableUsers.value || loadingCustomersList.value || submitting.value)
107 const message = useMessage()
108 const form = ref<CasePayload>(getForm())
109 const formRef = ref<FormInst | null>(null)
110 const availableUsers = inject<Ref<string[]>>("assignable-users", ref([]))
111 +const customersList = inject<Ref<Customer[]>>("customers-list", ref([]))
112
113 const rules: FormRules = {
114 case_name: {
@@ -117,16 +130,24 @@ const rules: FormRules = {
130 message: "Please input the Status",
131 required: true,
132 trigger: ["input", "blur"]
133 + },
134 + customer_code: {
135 + message: "Please input the Customer",
136 + required: true,
137 + trigger: ["input", "blur"]
138 }
139 }
140
123 -const statusOptions: { label: string; value: AlertStatus }[] = [
141 +const statusOptions: { label: string; value: CaseStatus }[] = [
142 { label: "Open", value: "OPEN" },
143 { label: "Closed", value: "CLOSED" },
144 { label: "In progress", value: "IN_PROGRESS" }
145 ]
146
147 const usersOptions = computed(() => availableUsers.value.map(o => ({ label: o, value: o })))
148 +const customersOptions = computed(() =>
149 + customersList.value.map(o => ({ label: `#${o.customer_code} - ${o.customer_name}`, value: o.customer_code }))
150 +)
151
152 const isValid = computed(() => {
153 let valid = true
@@ -161,7 +182,8 @@ function getForm() {
182 case_creation_time: new Date(),
183 case_description: "",
184 assigned_to: null,
164 - case_status: null
185 + case_status: null,
186 + customer_code: null
187 }
188 return payload
189 }
@@ -219,10 +241,33 @@ function getAvailableUsers() {
241 })
242 }
243
244 +function getCustomers() {
245 + loadingCustomersList.value = true
246 +
247 + Api.customers
248 + .getCustomers()
249 + .then(res => {
250 + if (res.data.success) {
251 + customersList.value = res.data?.customers || []
252 + } else {
253 + message.warning(res.data?.message || "An error occurred. Please try again later.")
254 + }
255 + })
256 + .catch(err => {
257 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
258 + })
259 + .finally(() => {
260 + loadingCustomersList.value = false
261 + })
262 +}
263 +
264 function load() {
265 if (!availableUsers.value.length) {
266 getAvailableUsers()
267 }
268 + if (!customersList.value.length) {
269 + getCustomers()
270 + }
271 reset()
272 }
273
frontend/src/components/incidentManagement/cases/CaseDataStore.vue new
+178
@@ -0,0 +1,178 @@
1 +<template>
2 + <n-spin :show="loading" class="flex flex-col grow min-h-48" content-class="flex flex-col grow gap-4">
3 + <div>
4 + <n-collapse-transition :show="!showUploadForm">
5 + <n-button v-if="dataStore.length" :loading="uploading" type="primary" @click="openUploadForm()">
6 + <template #icon>
7 + <Icon :name="UploadIcon" />
8 + </template>
9 + Upload Data Store file
10 + </n-button>
11 + </n-collapse-transition>
12 +
13 + <n-collapse-transition :show="showUploadForm">
14 + <div class="flex flex-col gap-2">
15 + <n-upload v-model:file-list="fileList" :max="1" :disabled="uploading">
16 + <n-upload-dragger>
17 + <div>
18 + <Icon :name="UploadIcon" :size="28" :depth="3"></Icon>
19 + </div>
20 + <div class="font-semibold">Click or drag a file to this area to upload</div>
21 + </n-upload-dragger>
22 + </n-upload>
23 +
24 + <div class="flex items-center justify-end gap-3">
25 + <n-button quaternary :disabled="uploading" @click="closeUploadForm()">Close</n-button>
26 +
27 + <n-button
28 + :disabled="!isValid"
29 + :loading="uploading"
30 + type="primary"
31 + @click="uploadDataStoreFile()"
32 + >
33 + <template #icon>
34 + <Icon :name="UploadIcon" />
35 + </template>
36 +
37 + Upload
38 + </n-button>
39 + </div>
40 + </div>
41 + </n-collapse-transition>
42 + </div>
43 +
44 + <div class="flex flex-col gap-2">
45 + <template v-if="dataStore.length">
46 + <CaseDataStoreItem
47 + v-for="dataStoreFile of dataStore"
48 + :key="dataStoreFile.id"
49 + :data-store-file="dataStoreFile"
50 + embedded
51 + @deleted="deleteDataStoreFile(dataStoreFile)"
52 + />
53 + </template>
54 + <template v-else>
55 + <n-collapse-transition :show="!showUploadForm">
56 + <n-empty v-if="!loading" class="min-h-48">
57 + <div class="flex flex-col items-center gap-4">
58 + <p>No files found</p>
59 + <n-button type="primary" :loading="uploading" @click="openUploadForm()">
60 + <template #icon>
61 + <Icon :name="UploadIcon" />
62 + </template>
63 + Upload a Data Store file
64 + </n-button>
65 + </div>
66 + </n-empty>
67 + </n-collapse-transition>
68 + </template>
69 + </div>
70 + </n-spin>
71 +</template>
72 +
73 +<script setup lang="ts">
74 +import type { CaseDataStore } from "@/types/incidentManagement/cases.d"
75 +import Api from "@/api"
76 +import Icon from "@/components/common/Icon.vue"
77 +import {
78 + NButton,
79 + NCollapseTransition,
80 + NEmpty,
81 + NSpin,
82 + NUpload,
83 + NUploadDragger,
84 + type UploadFileInfo,
85 + useMessage
86 +} from "naive-ui"
87 +import { computed, onBeforeMount, ref } from "vue"
88 +import CaseDataStoreItem from "./CaseDataStoreItem.vue"
89 +
90 +const { caseId } = defineProps<{
91 + caseId: number
92 +}>()
93 +
94 +const emit = defineEmits<{
95 + (e: "deleted"): void
96 + (e: "updated"): void
97 +}>()
98 +
99 +const UploadIcon = "carbon:cloud-upload"
100 +const message = useMessage()
101 +const loading = ref(false)
102 +const uploading = ref(false)
103 +const showUploadForm = ref(false)
104 +const dataStore = ref<CaseDataStore[]>([])
105 +const fileList = ref<UploadFileInfo[]>([])
106 +const newFile = computed<File | null>(() => fileList.value?.[0]?.file || null)
107 +
108 +const isValid = computed(() => {
109 + if (!newFile.value) return false
110 + return true
111 +})
112 +
113 +function deleteDataStoreFile(dataStoreFile: CaseDataStore) {
114 + dataStore.value = dataStore.value.filter(o => o.id !== dataStoreFile.id)
115 + emit("deleted")
116 +}
117 +
118 +function updateDataStore(dataStoreFile: CaseDataStore) {
119 + fileList.value = []
120 + dataStore.value.push(dataStoreFile)
121 + emit("updated")
122 +}
123 +
124 +function openUploadForm() {
125 + showUploadForm.value = true
126 +}
127 +
128 +function closeUploadForm() {
129 + showUploadForm.value = false
130 +}
131 +
132 +function getCaseDataStore(caseId: number) {
133 + loading.value = true
134 +
135 + Api.incidentManagement
136 + .getCaseDataStoreFiles(caseId)
137 + .then(res => {
138 + if (res.data.success) {
139 + dataStore.value = res.data?.case_data_store || []
140 + } else {
141 + message.warning(res.data?.message || "An error occurred. Please try again later.")
142 + }
143 + })
144 + .catch(err => {
145 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
146 + })
147 + .finally(() => {
148 + loading.value = false
149 + })
150 +}
151 +
152 +function uploadDataStoreFile() {
153 + if (!newFile.value) return
154 +
155 + uploading.value = true
156 +
157 + Api.incidentManagement
158 + .uploadCaseDataStoreFile(caseId, newFile.value)
159 + .then(res => {
160 + if (res.data.success) {
161 + message.success(res.data?.message || "Case Data Store File uploaded successfully")
162 + updateDataStore(res.data.case_data_store)
163 + } else {
164 + message.warning(res.data?.message || "An error occurred. Please try again later.")
165 + }
166 + })
167 + .catch(err => {
168 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
169 + })
170 + .finally(() => {
171 + uploading.value = false
172 + })
173 +}
174 +
175 +onBeforeMount(() => {
176 + getCaseDataStore(caseId)
177 +})
178 +</script>
frontend/src/components/incidentManagement/cases/CaseDataStoreItem.vue new
+201
@@ -0,0 +1,201 @@
1 +<template>
2 + <div class="data-store-file" :class="{ embedded }">
3 + <n-spin :show="canceling">
4 + <div class="flex flex-col">
5 + <div class="header-box px-5 py-3 pb-0 flex justify-between items-center gap-3">
6 + <div class="id flex items-center">
7 + <span>
8 + #{{ dataStoreFile.id }}
9 + <span class="hidden xs:inline">- {{ dataStoreFile.bucket_name }}</span>
10 + </span>
11 + </div>
12 + <div class="time">
13 + <span v-if="dataStoreFile.upload_time">
14 + {{ formatDate(dataStoreFile.upload_time, dFormats.datetime) }}
15 + </span>
16 + </div>
17 + </div>
18 +
19 + <div class="main-box flex flex-col gap-3 px-5 py-3">
20 + <div class="content flex flex-col gap-1">
21 + <span>
22 + {{ dataStoreFile.file_name }}
23 + </span>
24 +
25 + <small v-if="dataStoreFile.file_hash" class="text-secondary-color font-mono hidden sm:inline">
26 + hash:
27 + {{ dataStoreFile.file_hash }}
28 + </small>
29 + </div>
30 + </div>
31 +
32 + <div class="footer-box px-5 py-3 flex justify-between items-center">
33 + <div class="details gap-3 items-center hidden sm:flex">
34 + <Badge v-if="dataStoreFile.content_type" type="splitted">
35 + <template #label>type</template>
36 + <template #value>
37 + {{ dataStoreFile.content_type }}
38 + </template>
39 + </Badge>
40 + <Badge v-if="dataStoreFile.file_size" type="splitted">
41 + <template #label>size</template>
42 + <template #value>
43 + {{ prettyBytes }}
44 + </template>
45 + </Badge>
46 + </div>
47 + <div class="actions-box flex flex-wrap gap-3 justify-end w-full sm:w-auto">
48 + <n-popconfirm
49 + v-model:show="showDeleteConfirm"
50 + trigger="manual"
51 + to="body"
52 + @positive-click="deleteDataStoreFile()"
53 + @clickoutside="showDeleteConfirm = false"
54 + >
55 + <template #trigger>
56 + <n-button
57 + quaternary
58 + size="tiny"
59 + :loading="canceling"
60 + @click.stop="showDeleteConfirm = true"
61 + >
62 + Delete
63 + </n-button>
64 + </template>
65 + Are you sure you want to delete this file?
66 + </n-popconfirm>
67 +
68 + <n-button size="tiny" type="primary" :loading="downloading" @click.stop="downloadFile()">
69 + <template #icon>
70 + <Icon :name="DownloadIcon" />
71 + </template>
72 +
73 + Download
74 + </n-button>
75 + </div>
76 + </div>
77 + </div>
78 + </n-spin>
79 + </div>
80 +</template>
81 +
82 +<script setup lang="ts">
83 +import type { CaseDataStore } from "@/types/incidentManagement/cases"
84 +import Api from "@/api"
85 +import Badge from "@/components/common/Badge.vue"
86 +import Icon from "@/components/common/Icon.vue"
87 +import { useSettingsStore } from "@/stores/settings"
88 +import { formatDate } from "@/utils"
89 +import bytes from "bytes"
90 +import { saveAs } from "file-saver"
91 +import { NButton, NPopconfirm, NSpin, useMessage } from "naive-ui"
92 +import { computed, ref } from "vue"
93 +
94 +const { dataStoreFile, embedded } = defineProps<{
95 + dataStoreFile: CaseDataStore
96 + embedded?: boolean
97 +}>()
98 +
99 +const emit = defineEmits<{
100 + (e: "opened"): void
101 + (e: "deleted"): void
102 +}>()
103 +
104 +const DownloadIcon = "carbon:cloud-download"
105 +const message = useMessage()
106 +const canceling = ref(false)
107 +const downloading = ref(false)
108 +const showDeleteConfirm = ref(false)
109 +const dFormats = useSettingsStore().dateFormat
110 +const prettyBytes = computed(() => bytes(dataStoreFile.file_size))
111 +
112 +function downloadFile() {
113 + downloading.value = true
114 +
115 + Api.incidentManagement
116 + .downloadCaseDataStoreFile(dataStoreFile.case_id, dataStoreFile.file_name)
117 + .then(res => {
118 + if (res.data) {
119 + saveAs(res.data, dataStoreFile.file_name)
120 + } else {
121 + message.warning("An error occurred. Please try again later.")
122 + }
123 + })
124 + .catch(err => {
125 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
126 + })
127 + .finally(() => {
128 + downloading.value = false
129 + })
130 +}
131 +
132 +function deleteDataStoreFile() {
133 + canceling.value = true
134 +
135 + Api.incidentManagement
136 + .deleteCaseDataStoreFile(dataStoreFile.case_id, dataStoreFile.file_name)
137 + .then(res => {
138 + if (res.data.success) {
139 + message.success(res.data?.message || "Case Data Store File deleted successfully")
140 + emit("deleted")
141 + } else {
142 + message.warning(res.data?.message || "An error occurred. Please try again later.")
143 + }
144 + })
145 + .catch(err => {
146 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
147 + })
148 + .finally(() => {
149 + canceling.value = false
150 + })
151 +}
152 +</script>
153 +
154 +<style lang="scss" scoped>
155 +.data-store-file {
156 + border-radius: var(--border-radius);
157 + background-color: var(--bg-color);
158 + transition: all 0.2s var(--bezier-ease);
159 + border: var(--border-small-050);
160 + overflow: hidden;
161 +
162 + .header-box {
163 + font-family: var(--font-family-mono);
164 + font-size: 13px;
165 + color: var(--fg-secondary-color);
166 +
167 + .id {
168 + word-break: break-word;
169 + line-height: 1.2;
170 + }
171 +
172 + .time {
173 + text-align: right;
174 + }
175 + }
176 + .main-box {
177 + .content {
178 + word-break: break-word;
179 + }
180 + }
181 +
182 + .footer-box {
183 + border-top: var(--border-small-100);
184 + font-size: 13px;
185 + background-color: var(--bg-secondary-color);
186 + }
187 +
188 + &.embedded {
189 + background-color: var(--bg-secondary-color);
190 + border: var(--border-small-100);
191 +
192 + .footer-box {
193 + background-color: var(--bg-body);
194 + }
195 + }
196 +
197 + &:hover {
198 + box-shadow: 0px 0px 0px 1px var(--primary-color);
199 + }
200 +}
201 +</style>
frontend/src/components/incidentManagement/cases/CaseDetails.vue
+18 -13
@@ -1,16 +1,16 @@
1 <template>
2 <n-spin :show="loading" class="flex flex-col grow" content-class="flex flex-col grow">
3 <n-tabs
4 + v-if="caseEntity"
5 type="line"
6 animated
7 :tabs-padding="24"
7 - v-if="caseEntity"
8 class="grow"
9 pane-wrapper-class="flex flex-col grow"
10 >
11 <n-tab-pane name="Overview" tab="Overview" display-directive="show:lazy" class="flex flex-col grow">
12 <div class="pt-1">
13 - <CaseOverview :caseData="caseEntity" @updated="updateCase($event)" @deleted="emit('deleted')" />
13 + <CaseOverview :case-data="caseEntity" @updated="updateCase($event)" @deleted="emit('deleted')" />
14 </div>
15 </n-tab-pane>
16 <n-tab-pane name="Alerts" tab="Alerts" display-directive="show:lazy">
@@ -19,41 +19,46 @@
19 <AlertItem
20 v-for="alert of caseEntity.alerts"
21 :key="alert.id"
22 - :alertData="alert"
22 + :alert-data="alert"
23 embedded
24 @deleted="getCase(caseEntity.id)"
25 @updated="getCase(caseEntity.id)"
26 />
27 </template>
28 <template v-else>
29 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
29 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
30 </template>
31 </div>
32 </n-tab-pane>
33 + <n-tab-pane name="Data Store" tab="Data Store" display-directive="show:lazy">
34 + <div class="p-7 pt-4">
35 + <CaseDataStore :case-id="caseEntity.id" />
36 + </div>
37 + </n-tab-pane>
38 </n-tabs>
39 </n-spin>
40 </template>
41
42 <script setup lang="ts">
38 -import { defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
39 -import { NTabs, NTabPane, NSpin, NEmpty, useMessage } from "naive-ui"
40 -import _clone from "lodash/cloneDeep"
41 -import Api from "@/api"
43 import type { Case } from "@/types/incidentManagement/cases.d"
43 -
44 -const CaseOverview = defineAsyncComponent(() => import("./CaseOverview.vue"))
45 -const AlertItem = defineAsyncComponent(() => import("../alerts/AlertItem.vue"))
44 +import Api from "@/api"
45 +import _clone from "lodash/cloneDeep"
46 +import { NEmpty, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
47 +import { defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
48
49 const props = defineProps<{
50 caseData?: Case
51 caseId?: number
52 }>()
51 -const { caseData, caseId } = toRefs(props)
52 -
53 const emit = defineEmits<{
54 (e: "deleted"): void
55 (e: "updated", value: Case): void
56 }>()
57 +const CaseOverview = defineAsyncComponent(() => import("./CaseOverview.vue"))
58 +const CaseDataStore = defineAsyncComponent(() => import("./CaseDataStore.vue"))
59 +const AlertItem = defineAsyncComponent(() => import("../alerts/AlertItem.vue"))
60 +
61 +const { caseData, caseId } = toRefs(props)
62
63 const message = useMessage()
64 const loading = ref(false)
frontend/src/components/incidentManagement/cases/CaseItem.vue
+45 -27
@@ -1,16 +1,16 @@
1 <template>
2 <div
3 class="case-item"
4 - :class="['status-' + caseEntity?.case_status, { compact, embedded, 'cursor-pointer': compact, highlight }]"
4 + :class="[`status-${caseEntity?.case_status}`, { compact, embedded, 'cursor-pointer': compact, highlight }]"
5 >
6 <n-spin :show="loading">
7 - <div class="flex flex-col" v-if="caseEntity">
7 + <div v-if="caseEntity" class="flex flex-col">
8 <div class="header-box px-5 py-3 pb-0 flex justify-between items-center">
9 <div class="id flex items-center gap-2 cursor-pointer" @click="compact ? undefined : openDetails()">
10 <span>#{{ caseEntity.id }}</span>
11 - <Icon :name="InfoIcon" :size="16" v-if="!compact"></Icon>
11 + <Icon v-if="!compact" :name="InfoIcon" :size="16"></Icon>
12 </div>
13 - <div class="time" v-if="caseEntity.case_creation_time">
13 + <div v-if="caseEntity.case_creation_time" class="time">
14 {{ formatDate(caseEntity.case_creation_time, dFormats.datetime) }}
15 </div>
16 </div>
@@ -22,7 +22,7 @@
22 </div>
23 </div>
24
25 - <div class="badges-box flex flex-wrap items-center gap-3" v-if="compact">
25 + <div v-if="compact" class="badges-box flex flex-wrap items-center gap-3">
26 <Badge
27 type="splitted"
28 class="cursor-pointer"
@@ -66,10 +66,10 @@
66 </Badge>
67 </div>
68
69 - <div class="badges-box flex flex-wrap items-center gap-3" v-else>
69 + <div v-else class="badges-box flex flex-wrap items-center gap-3">
70 <CaseStatusSwitch
71 - :caseData="caseEntity"
71 v-slot="{ loading: loadingStatus }"
72 + :case-data="caseEntity"
73 @updated="updateCase($event)"
74 >
75 <Badge
@@ -106,8 +106,8 @@
106 </CaseStatusSwitch>
107
108 <CaseAssignUser
109 - :caseData="caseEntity"
109 v-slot="{ loading: loadingAssignee }"
110 + :case-data="caseEntity"
111 @updated="updateCase($event)"
112 >
113 <Badge
@@ -134,10 +134,25 @@
134 </template>
135 </Badge>
136 </CaseAssignUser>
137 +
138 + <Badge v-if="caseEntity.customer_code" type="splitted" class="!hidden sm:!flex">
139 + <template #label>Customer</template>
140 + <template #value>
141 + <div class="flex items-center h-full">
142 + <code
143 + class="cursor-pointer text-primary-color leading-none"
144 + @click.stop="gotoCustomer({ code: caseEntity.customer_code })"
145 + >
146 + #{{ caseEntity.customer_code }}
147 + <Icon :name="LinkIcon" :size="14" class="top-0.5 relative" />
148 + </code>
149 + </div>
150 + </template>
151 + </Badge>
152 </div>
153 </div>
154
140 - <div class="footer-box px-5 py-3 flex justify-between items-center" v-if="!compact">
155 + <div v-if="!compact" class="footer-box px-5 py-3 flex justify-between items-center">
156 <n-collapse :trigger-areas="['main', 'arrow']">
157 <n-collapse-item name="alerts-list">
158 <template #header>
@@ -154,7 +169,7 @@
169 <AlertItem
170 v-for="alert of caseEntity.alerts"
171 :key="alert.id"
157 - :alertData="alert"
172 + :alert-data="alert"
173 compact
174 @deleted="getCase(caseEntity.id)"
175 @updated="getCase(caseEntity.id)"
@@ -162,9 +177,9 @@
177 </template>
178 <template v-else>
179 <n-empty
180 + v-if="!loading"
181 description="No alerts attached"
182 class="justify-center h-24"
167 - v-if="!loading"
183 />
184 </template>
185 </div>
@@ -182,14 +197,14 @@
197 content-class="flex flex-col !p-0"
198 :title="caseNameTruncated"
199 closable
185 - @close="closeDetails()"
200 :bordered="false"
201 segmented
202 role="modal"
203 + @close="closeDetails()"
204 >
205 <CaseDetails
206 v-if="caseEntity"
192 - :caseData="caseEntity"
207 + :case-data="caseEntity"
208 @deleted="emitDelete()"
209 @updated="updateCase($event)"
210 />
@@ -199,23 +214,24 @@
214 </template>
215
216 <script setup lang="ts">
202 -import { computed, onBeforeMount, onMounted, ref, toRefs } from "vue"
203 -import { NModal, NButton, NCollapse, NCollapseItem, NSpin, NCard, NEmpty, useMessage, useDialog } from "naive-ui"
204 -import _clone from "lodash/cloneDeep"
217 +import type { Case } from "@/types/incidentManagement/cases.d"
218 import Api from "@/api"
206 -import Icon from "@/components/common/Icon.vue"
219 import Badge from "@/components/common/Badge.vue"
220 +import Icon from "@/components/common/Icon.vue"
221 +import { useGoto } from "@/composables/useGoto"
222 +import { useSettingsStore } from "@/stores/settings"
223 +import { formatDate } from "@/utils"
224 +import _clone from "lodash/cloneDeep"
225 +import _truncate from "lodash/truncate"
226 +import { NButton, NCard, NCollapse, NCollapseItem, NEmpty, NModal, NSpin, useDialog, useMessage } from "naive-ui"
227 +import { computed, onBeforeMount, onMounted, ref, toRefs } from "vue"
228 +import AlertItem from "../alerts/AlertItem.vue"
229 +import AssigneeIcon from "../common/AssigneeIcon.vue"
230 +import StatusIcon from "../common/StatusIcon.vue"
231 import CaseAssignUser from "./CaseAssignUser.vue"
209 -import CaseStatusSwitch from "./CaseStatusSwitch.vue"
232 import CaseDetails from "./CaseDetails.vue"
211 -import StatusIcon from "../common/StatusIcon.vue"
212 -import AssigneeIcon from "../common/AssigneeIcon.vue"
213 -import AlertItem from "../alerts/AlertItem.vue"
214 -import { formatDate } from "@/utils"
233 +import CaseStatusSwitch from "./CaseStatusSwitch.vue"
234 import { handleDeleteCase } from "./utils"
216 -import _truncate from "lodash/truncate"
217 -import type { Case } from "@/types/incidentManagement/cases.d"
218 -import { useSettingsStore } from "@/stores/settings"
235
236 const props = defineProps<{
237 caseData?: Case
@@ -225,17 +241,19 @@ const props = defineProps<{
241 detailsOnMounted?: boolean
242 highlight?: boolean
243 }>()
228 -const { caseData, caseId, compact, embedded, detailsOnMounted, highlight } = toRefs(props)
229 -
244 const emit = defineEmits<{
245 (e: "opened"): void
246 (e: "deleted"): void
247 (e: "updated", value: Case): void
248 }>()
249
250 +const { caseData, caseId, compact, embedded, detailsOnMounted, highlight } = toRefs(props)
251 +
252 +const LinkIcon = "carbon:launch"
253 const InfoIcon = "carbon:information"
254 const EditIcon = "uil:edit-alt"
255
256 +const { gotoCustomer } = useGoto()
257 const dialog = useDialog()
258 const message = useMessage()
259 const dFormats = useSettingsStore().dateFormat
frontend/src/components/incidentManagement/cases/CaseOverview.vue
+40 -14
@@ -25,8 +25,8 @@
25 <template #value>
26 <div class="flex">
27 <CaseStatusSwitch
28 - :caseData
28 v-slot="{ loading: loadingStatus }"
29 + :case-data
30 @updated="updateCase($event)"
31 >
32 <div
@@ -60,8 +60,8 @@
60 <template #value>
61 <div class="flex">
62 <CaseAssignUser
63 - :caseData
63 v-slot="{ loading: loadingAssignee }"
64 + :case-data
65 @updated="updateCase($event)"
66 >
67 <div
@@ -105,14 +105,37 @@
105
106 <KVCard>
107 <template #key>creation time</template>
108 - <template #value>{{ formatDate(caseData.case_creation_time, dFormats.datetime) }}</template>
108 + <template #value>
109 + {{
110 + caseData.case_creation_time
111 + ? formatDate(caseData.case_creation_time, dFormats.datetime)
112 + : "-"
113 + }}
114 + </template>
115 + </KVCard>
116 +
117 + <KVCard>
118 + <template #key>customer code</template>
119 + <template #value>
120 + <code
121 + v-if="caseData.customer_code"
122 + class="cursor-pointer text-primary-color"
123 + @click="gotoCustomer({ code: caseData.customer_code })"
124 + >
125 + {{ caseData.customer_code }}
126 + <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
127 + </code>
128 + <span v-else>-</span>
129 + </template>
130 </KVCard>
131 </div>
132 </div>
133
134 <div class="footer-box px-7 py-4 flex justify-end">
135 <n-button type="error" secondary @click="handleDelete()">
115 - <template #icon><Icon :name="TrashIcon" /></template>
136 + <template #icon>
137 + <Icon :name="TrashIcon" />
138 + </template>
139 Delete
140 </n-button>
141 </div>
@@ -121,30 +144,33 @@
144 </template>
145
146 <script setup lang="ts">
147 +import type { Case } from "@/types/incidentManagement/cases.d"
148 +import Icon from "@/components/common/Icon.vue"
149 +import KVCard from "@/components/common/KVCard.vue"
150 +import { useGoto } from "@/composables/useGoto"
151 +import { useSettingsStore } from "@/stores/settings"
152 +import { formatDate } from "@/utils"
153 +import { NButton, NSpin, useDialog, useMessage } from "naive-ui"
154 import { ref, toRefs } from "vue"
125 -import { NButton, NSpin, useMessage, useDialog } from "naive-ui"
155 +import AssigneeIcon from "../common/AssigneeIcon.vue"
156 +import StatusIcon from "../common/StatusIcon.vue"
157 import CaseAssignUser from "./CaseAssignUser.vue"
158 import CaseStatusSwitch from "./CaseStatusSwitch.vue"
128 -import StatusIcon from "../common/StatusIcon.vue"
129 -import AssigneeIcon from "../common/AssigneeIcon.vue"
130 -import KVCard from "@/components/common/KVCard.vue"
131 -import Icon from "@/components/common/Icon.vue"
159 import { handleDeleteCase } from "./utils"
133 -import { formatDate } from "@/utils"
134 -import { useSettingsStore } from "@/stores/settings"
135 -import type { Case } from "@/types/incidentManagement/cases.d"
160
161 const props = defineProps<{ caseData: Case }>()
138 -const { caseData } = toRefs(props)
139 -
162 const emit = defineEmits<{
163 (e: "deleted"): void
164 (e: "updated", value: Case): void
165 }>()
166
167 +const { caseData } = toRefs(props)
168 +
169 const TrashIcon = "carbon:trash-can"
170 +const LinkIcon = "carbon:launch"
171 const EditIcon = "uil:edit-alt"
172
173 +const { gotoCustomer } = useGoto()
174 const dialog = useDialog()
175 const message = useMessage()
176 const dFormats = useSettingsStore().dateFormat
frontend/src/components/incidentManagement/cases/CaseStatusSwitch.vue
+7 -8
@@ -13,21 +13,20 @@
13 </template>
14
15 <script setup lang="ts">
16 -import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
17 -import { useMessage, NPopselect } from "naive-ui"
16 +import type { Case, CaseStatus } from "@/types/incidentManagement/cases.d"
17 import Api from "@/api"
19 -import type { AlertStatus } from "@/types/incidentManagement/alerts.d"
20 -import type { Case } from "@/types/incidentManagement/cases.d"
18 +import { NPopselect, useMessage } from "naive-ui"
19 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
20
21 const props = defineProps<{
22 caseData: Case
23 }>()
25 -const { caseData } = toRefs(props)
26 -
24 const emit = defineEmits<{
25 (e: "updated", value: Case): void
26 }>()
27
28 +const { caseData } = toRefs(props)
29 +
30 const loading = ref(false)
31 const message = useMessage()
32 const listVisible = ref(false)
@@ -35,14 +34,14 @@ const status = computed(() => caseData.value.case_status)
34 const statusOptions = ref<
35 {
36 label: string
38 - value: AlertStatus
37 + value: CaseStatus
38 }[]
39 >([
40 { label: "Open", value: "OPEN" },
41 { label: "In progress", value: "IN_PROGRESS" },
42 { label: "Closed", value: "CLOSED" }
43 ])
45 -const statusSelected = ref<AlertStatus | null>(null)
44 +const statusSelected = ref<CaseStatus | null>(null)
45
46 function updateStatus() {
47 if (statusSelected.value && statusSelected.value !== status.value) {
frontend/src/components/incidentManagement/cases/CasesExport.vue new
+102
@@ -0,0 +1,102 @@
1 +<template>
2 + <n-dropdown placement="bottom-start" trigger="click" :options="customersOptions" @select="exportCases">
3 + <n-button :size :loading="exporting" secondary @click="load()">
4 + <template #icon>
5 + <Icon :name="DownloadIcon" :size="14" />
6 + </template>
7 + Export
8 + </n-button>
9 + </n-dropdown>
10 +</template>
11 +
12 +<script setup lang="ts">
13 +import type { Customer } from "@/types/customers.d"
14 +import type { Size } from "naive-ui/es/button/src/interface"
15 +import Api from "@/api"
16 +import Icon from "@/components/common/Icon.vue"
17 +import { useSettingsStore } from "@/stores/settings"
18 +import { formatDate } from "@/utils"
19 +import { saveAs } from "file-saver"
20 +import { NButton, NDropdown, useMessage } from "naive-ui"
21 +import { computed, inject, ref, type Ref } from "vue"
22 +
23 +const { size } = defineProps<{ size?: Size }>()
24 +
25 +const DownloadIcon = "carbon:cloud-download"
26 +const loadingCustomersList = ref(false)
27 +const dFormats = useSettingsStore().dateFormat
28 +const exporting = ref(false)
29 +const message = useMessage()
30 +const customersList = inject<Ref<Customer[]>>("customers-list", ref([]))
31 +
32 +const customersOptions = computed(() => {
33 + return [
34 + {
35 + label: "Export All Cases",
36 + key: "--all--"
37 + },
38 + {
39 + label: `Export by Customer${loadingCustomersList.value ? "..." : ""}`,
40 + key: "customer",
41 + disabled: loadingCustomersList.value,
42 + children: loadingCustomersList.value
43 + ? undefined
44 + : customersList.value.map(o => ({
45 + label: `#${o.customer_code} - ${o.customer_name}`,
46 + key: o.customer_code
47 + }))
48 + }
49 + ]
50 +})
51 +
52 +function exportCases(key: string) {
53 + exporting.value = true
54 +
55 + const fileName =
56 + key === "--all--"
57 + ? `cases_${formatDate(new Date(), dFormats.datetimesec)}.csv`
58 + : `cases_customer:${key}_${formatDate(new Date(), dFormats.datetimesec)}.csv`
59 +
60 + Api.incidentManagement
61 + .exportCases(key === "--all--" ? undefined : key)
62 + .then(res => {
63 + if (res.data) {
64 + saveAs(new Blob([res.data], { type: "text/csv;charset=utf-8" }), fileName)
65 + } else {
66 + message.warning("An error occurred. Please try again later.")
67 + }
68 + })
69 + .catch(err => {
70 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
71 + })
72 + .finally(() => {
73 + exporting.value = false
74 + })
75 +}
76 +
77 +function getCustomers() {
78 + loadingCustomersList.value = true
79 +
80 + Api.customers
81 + .getCustomers()
82 + .then(res => {
83 + if (res.data.success) {
84 + customersList.value = res.data?.customers || []
85 + } else {
86 + message.warning(res.data?.message || "An error occurred. Please try again later.")
87 + }
88 + })
89 + .catch(err => {
90 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
91 + })
92 + .finally(() => {
93 + loadingCustomersList.value = false
94 + })
95 +}
96 +
97 +function load() {
98 + if (!customersList.value.length) {
99 + getCustomers()
100 + }
101 +}
102 +</script>
frontend/src/components/incidentManagement/cases/CasesList.vue
+69 -33
@@ -1,6 +1,6 @@
1 <template>
2 <div class="cases-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="left">
6 <template #trigger>
@@ -36,7 +36,9 @@
36 </div>
37 </n-popover>
38
39 - <CaseCreationButton @submitted="getData()" :only-icon="caseCreationButtonOnlyIcon" />
39 + <CasesExport size="small" />
40 +
41 + <CaseCreationButton :only-icon="caseCreationButtonOnlyIcon" size="small" @submitted="getData()" />
42 </div>
43 <n-pagination
44 v-model:page="currentPage"
@@ -47,7 +49,7 @@
49 :item-count="total"
50 :simple="simpleMode"
51 />
50 - <n-popover :show="showFilters" trigger="manual" overlap placement="right" class="!px-0" v-if="!hideFilters">
52 + <n-popover v-if="!hideFilters" :show="showFilters" trigger="manual" overlap placement="right" class="!px-0">
53 <template #trigger>
54 <div class="bg-color border-radius">
55 <n-badge :show="filtered" dot type="success" :offset="[-4, 0]">
@@ -67,7 +69,7 @@
69 :options="typeOptions"
70 placeholder="Filter by..."
71 clearable
70 - class="!w-36"
72 + class="!w-40"
73 />
74
75 <n-select
@@ -86,6 +88,17 @@
88 placeholder="Value..."
89 :disabled="!filters.type"
90 clearable
91 + filterable
92 + class="!w-56"
93 + />
94 + <n-select
95 + v-else-if="filters.type === 'customerCode'"
96 + v-model:value="filters.value"
97 + :options="customersOptions"
98 + placeholder="Value..."
99 + :disabled="!filters.type"
100 + clearable
101 + filterable
102 class="!w-56"
103 />
104 <n-input
@@ -100,11 +113,11 @@
113 </div>
114 <div class="px-3 flex justify-between gap-2">
115 <div class="flex justify-start gap-2">
103 - <n-button size="small" @click="showFilters = false" quaternary>Close</n-button>
116 + <n-button size="small" quaternary @click="showFilters = false">Close</n-button>
117 </div>
118 <div class="flex justify-end gap-2">
106 - <n-button size="small" @click="resetFilters()" secondary>Reset</n-button>
107 - <n-button size="small" @click="getData()" type="primary" secondary :loading>
119 + <n-button size="small" secondary @click="resetFilters()">Reset</n-button>
120 + <n-button size="small" type="primary" secondary :loading @click="getData()">
121 Submit
122 </n-button>
123 </div>
@@ -118,9 +131,9 @@
131 <CaseItem
132 v-for="item of itemsPaginated"
133 :key="item.id"
121 - :caseData="item"
134 + :case-data="item"
135 :highlight="highlight === item.id.toString()"
123 - :detailsOnMounted="highlight === item.id.toString() && !highlightedItemOpened"
136 + :details-on-mounted="highlight === item.id.toString() && !highlightedItemOpened"
137 class="item-appear item-appear-bottom item-appear-005"
138 @opened="highlightedItemOpened = true"
139 @deleted="getData()"
@@ -128,50 +141,51 @@
141 />
142 </template>
143 <template v-else>
131 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
144 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
145 </template>
146 </div>
147 </n-spin>
148 <div class="footer flex justify-end">
149 <n-pagination
150 + v-if="itemsPaginated.length > 3"
151 v-model:page="currentPage"
152 :page-size="pageSize"
153 :item-count="total"
154 :page-slot="6"
141 - v-if="itemsPaginated.length > 3"
155 />
156 </div>
157 </div>
158 </template>
159
160 <script setup lang="ts">
148 -import { ref, onBeforeMount, computed, watch, toRefs, provide, nextTick } from "vue"
161 +import type { CasesFilter, CasesFilterTypes } from "@/api/endpoints/incidentManagement"
162 +import type { Customer } from "@/types/customers.d"
163 +import type { Case, CaseStatus } from "@/types/incidentManagement/cases.d"
164 +import Api from "@/api"
165 +import Icon from "@/components/common/Icon.vue"
166 +import { useResizeObserver } from "@vueuse/core"
167 +import _cloneDeep from "lodash/cloneDeep"
168 +import _orderBy from "lodash/orderBy"
169 import {
150 - useMessage,
151 - NSpin,
152 - NPopover,
170 + NBadge,
171 NButton,
172 NEmpty,
155 - NSelect,
156 - NPagination,
173 + NInput,
174 NInputGroup,
158 - NBadge,
159 - NInput
175 + NPagination,
176 + NPopover,
177 + NSelect,
178 + NSpin,
179 + useMessage
180 } from "naive-ui"
161 -import Api from "@/api"
162 -import _cloneDeep from "lodash/cloneDeep"
163 -import _orderBy from "lodash/orderBy"
164 -import Icon from "@/components/common/Icon.vue"
181 +import { computed, nextTick, onBeforeMount, provide, ref, toRefs, watch } from "vue"
182 import CaseCreationButton from "./CaseCreationButton.vue"
166 -import { useResizeObserver } from "@vueuse/core"
167 -import type { AlertStatus } from "@/types/incidentManagement/alerts.d"
168 -import type { Case } from "@/types/incidentManagement/cases.d"
169 -import type { CasesFilter } from "@/api/endpoints/incidentManagement"
183 import CaseItem from "./CaseItem.vue"
184 +import CasesExport from "./CasesExport.vue"
185
186 export interface CasesListFilter {
173 - type: "status" | "assignedTo" | "hostname"
174 - value: string | AlertStatus
187 + type: CasesFilterTypes
188 + value: string | CaseStatus
189 }
190
191 const props = defineProps<{ highlight?: string | null; preset?: CasesListFilter; hideFilters?: boolean }>()
@@ -182,6 +196,7 @@ const loading = ref(false)
196 const showFilters = ref(false)
197 const casesList = ref<Case[]>([])
198 const availableUsers = ref<string[]>([])
199 +const customersList = ref<Customer[]>([])
200
201 const pageSize = ref(25)
202 const currentPage = ref(1)
@@ -227,19 +242,23 @@ const filtered = computed<boolean>(() => {
242 return !!filters.value.type && !!filters.value.value
243 })
244
230 -const typeOptions = [
245 +const typeOptions: { label: string; value: CasesFilterTypes }[] = [
246 { label: "Status", value: "status" },
247 { label: "Assigned To", value: "assignedTo" },
233 - { label: "Hostname", value: "hostname" }
248 + { label: "Hostname", value: "hostname" },
249 + { label: "Customer Code", value: "customerCode" }
250 ]
251
236 -const statusOptions: { label: string; value: AlertStatus }[] = [
252 +const statusOptions: { label: string; value: CaseStatus }[] = [
253 { label: "Open", value: "OPEN" },
254 { label: "Closed", value: "CLOSED" },
255 { label: "In progress", value: "IN_PROGRESS" }
256 ]
257
258 const usersOptions = computed(() => availableUsers.value.map(o => ({ label: o, value: o })))
259 +const customersOptions = computed(() =>
260 + customersList.value.map(o => ({ label: `#${o.customer_code} - ${o.customer_name}`, value: o.customer_code }))
261 +)
262
263 const highlightedItemFound = ref(!highlight.value)
264 const highlightedItemOpened = ref(!highlight.value)
@@ -289,6 +308,7 @@ watch(
308 )
309
310 provide("assignable-users", availableUsers)
311 +provide("customers-list", customersList)
312
313 function resetFilters() {
314 filters.value.type = undefined
@@ -309,7 +329,7 @@ function getData() {
329
330 lastFilters.value = _cloneDeep(filters.value)
331
312 - let query: CasesFilter | undefined = undefined
332 + let query: CasesFilter | undefined
333 if (filtered.value) {
334 // @ts-expect-error filters properties infer are ignored
335 query = { [filters.value.type]: filters.value.value }
@@ -349,6 +369,21 @@ function getAvailableUsers() {
369 })
370 }
371
372 +function getCustomers() {
373 + Api.customers
374 + .getCustomers()
375 + .then(res => {
376 + if (res.data.success) {
377 + customersList.value = res.data?.customers || []
378 + } else {
379 + message.warning(res.data?.message || "An error occurred. Please try again later.")
380 + }
381 + })
382 + .catch(err => {
383 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
384 + })
385 +}
386 +
387 useResizeObserver(header, entries => {
388 const entry = entries[0]
389 const { width } = entry.contentRect
@@ -367,6 +402,7 @@ onBeforeMount(() => {
402
403 getData()
404 getAvailableUsers()
405 + getCustomers()
406 })
407 </script>
408
frontend/src/components/incidentManagement/cases/utils.ts
+4 -4
@@ -1,8 +1,8 @@
1 -import { h } from "vue"
2 -import Api from "@/api"
3 -import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
4 -import type { DialogApiInjection } from "naive-ui/es/dialog/src/DialogProvider"
1 import type { Case } from "@/types/incidentManagement/cases.d"
2 +import type { DialogApiInjection } from "naive-ui/es/dialog/src/DialogProvider"
3 +import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
4 +import Api from "@/api"
5 +import { h } from "vue"
6
7 export interface DeleteCaseParams {
8 caseData: Case
frontend/src/components/incidentManagement/common/AssigneeIcon.vue
+1 -1
@@ -3,8 +3,8 @@
3 </template>
4
5 <script setup lang="ts">
6 -import { toRefs } from "vue"
6 import Icon from "@/components/common/Icon.vue"
7 +import { toRefs } from "vue"
8
9 const props = defineProps<{ assignee: string | null; size?: number }>()
10 const { assignee, size } = toRefs(props)
frontend/src/components/incidentManagement/common/StatusIcon.vue
+2 -2
@@ -14,9 +14,9 @@
14 </template>
15
16 <script setup lang="ts">
17 -import { toRefs } from "vue"
18 -import Icon from "@/components/common/Icon.vue"
17 import type { AlertStatus } from "@/types/incidentManagement/alerts.d"
18 +import Icon from "@/components/common/Icon.vue"
19 +import { toRefs } from "vue"
20
21 const props = defineProps<{ status: AlertStatus | null; size?: number }>()
22 const { status, size } = toRefs(props)
frontend/src/components/incidentManagement/sources/ConfiguredSourceItem.vue
+6 -6
@@ -6,13 +6,13 @@
6 </div>
7 <div class="footer-box flex justify-end">
8 <n-popconfirm
9 - @positive-click="deleteSourceConfiguration()"
9 v-model:show="showConfirm"
10 trigger="manual"
11 + @positive-click="deleteSourceConfiguration()"
12 @clickoutside="showConfirm = false"
13 >
14 <template #trigger>
15 - <div @click.stop="showConfirm = true" class="delete-btn">delete</div>
15 + <div class="delete-btn" @click.stop="showConfirm = true">delete</div>
16 </template>
17 Are you sure you want to delete the source configuration?
18 </n-popconfirm>
@@ -34,18 +34,18 @@
34 </template>
35
36 <script setup lang="ts">
37 +import type { SourceName } from "@/types/incidentManagement/sources.d"
38 import Api from "@/api"
39 +import { NModal, NPopconfirm, NSpin, useMessage } from "naive-ui"
40 import { ref } from "vue"
41 import SourceConfigurationDetails from "./SourceConfigurationDetails.vue"
40 -import { useMessage, NPopconfirm, NSpin, NModal } from "naive-ui"
41 -import type { SourceName } from "@/types/incidentManagement/sources.d"
42 +
43 +const { source } = defineProps<{ source: SourceName }>()
44
45 const emit = defineEmits<{
46 (e: "deleted"): void
47 }>()
48
47 -const { source } = defineProps<{ source: SourceName }>()
48 -
49 const message = useMessage()
50 const canceling = ref(false)
51 const showDetails = ref(false)
frontend/src/components/incidentManagement/sources/ConfiguredSourcesList.vue
+8 -7
@@ -30,7 +30,7 @@
30 </div>
31 </div>
32 <n-spin :show="loading" class="min-h-32">
33 - <div class="list gap-4 grid grid-auto-fill-250" v-if="configuredSourcesList.length">
33 + <div v-if="configuredSourcesList.length" class="list gap-4 grid grid-auto-fill-250">
34 <ConfiguredSourceItem
35 v-for="source of configuredSourcesList"
36 :key="source"
@@ -40,7 +40,7 @@
40 />
41 </div>
42 <template v-else>
43 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
43 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
44 </template>
45 </n-spin>
46
@@ -54,18 +54,19 @@
54 content-class="flex flex-col !p-0"
55 segmented
56 >
57 - <SourceConfigurationWizard @submitted="getConfiguredSources()" :disabledSources="configuredSourcesList" />
57 + <SourceConfigurationWizard :disabled-sources="configuredSourcesList" @submitted="getConfiguredSources()" />
58 </n-modal>
59 </div>
60 </template>
61 +
62 <script setup lang="ts">
62 -import { computed, onBeforeMount, ref, watch } from "vue"
63 -import { NSpin, NEmpty, NPopover, NButton, NModal, useMessage } from "naive-ui"
63 +import type { SourceName } from "@/types/incidentManagement/sources.d"
64 +import Api from "@/api"
65 import Icon from "@/components/common/Icon.vue"
66 +import { NButton, NEmpty, NModal, NPopover, NSpin, useMessage } from "naive-ui"
67 +import { computed, onBeforeMount, ref, watch } from "vue"
68 import ConfiguredSourceItem from "./ConfiguredSourceItem.vue"
69 import SourceConfigurationWizard from "./SourceConfigurationWizard.vue"
67 -import type { SourceName } from "@/types/incidentManagement/sources.d"
68 -import Api from "@/api"
70
71 const InfoIcon = "carbon:information"
72 const NewSourceConfigurationIcon = "carbon:fetch-upload-cloud"
frontend/src/components/incidentManagement/sources/SourceConfigurationDetails.vue
+8 -8
@@ -1,8 +1,8 @@
1 <template>
2 <n-spin :show="loading || submitting" class="min-h-20">
3 - <SourceConfigurationViewer v-if="sourceConfiguration" v-show="!editing" :sourceConfiguration />
3 + <SourceConfigurationViewer v-if="sourceConfiguration" v-show="!editing" :source-configuration />
4
5 - <div class="flex gap-2 justify-end items-center mt-4" v-if="!editing && !loading">
5 + <div v-if="!editing && !loading" class="flex gap-2 justify-end items-center mt-4">
6 <n-button @click="setEditMode()">
7 <template #icon>
8 <Icon :name="EditIcon" :size="16"></Icon>
@@ -14,7 +14,7 @@
14 <SourceConfigurationForm
15 v-if="sourceConfiguration"
16 v-show="editing"
17 - :sourceConfigurationModel="sourceConfiguration"
17 + :source-configuration-model="sourceConfiguration"
18 show-index-name-field
19 @mounted="formCTX = $event"
20 @submitted="updateSourceConfiguration($event)"
@@ -32,14 +32,14 @@
32 </template>
33
34 <script setup lang="ts">
35 +import type { ApiError } from "@/types/common.d"
36 +import type { SourceConfiguration, SourceName } from "@/types/incidentManagement/sources.d"
37 import Api from "@/api"
38 +import Icon from "@/components/common/Icon.vue"
39 +import { NButton, NSpin, useMessage } from "naive-ui"
40 import { onBeforeMount, ref } from "vue"
37 -import { useMessage, NSpin, NButton } from "naive-ui"
38 -import type { SourceConfiguration, SourceName } from "@/types/incidentManagement/sources.d"
39 -import SourceConfigurationViewer from "./SourceConfigurationViewer.vue"
41 import SourceConfigurationForm from "./SourceConfigurationForm.vue"
41 -import Icon from "@/components/common/Icon.vue"
42 -import type { ApiError } from "@/types/common.d"
42 +import SourceConfigurationViewer from "./SourceConfigurationViewer.vue"
43
44 const { source } = defineProps<{ source: SourceName }>()
45
frontend/src/components/incidentManagement/sources/SourceConfigurationForm.vue
+28 -27
@@ -1,9 +1,9 @@
1 <template>
2 <n-spin :show="loading" class="creation-report-form">
3 - <n-form :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :model="form" :rules="rules">
4 <div class="flex flex-col gap-8">
5 <div class="flex flex-col gap-2">
6 - <n-form-item label="Index name" path="index_name" v-if="showIndexNameField">
6 + <n-form-item v-if="showIndexNameField" label="Index name" path="index_name">
7 <n-select
8 v-model:value="form.index_name"
9 :options="indexNamesOptions"
@@ -15,7 +15,7 @@
15 :loading="loadingIndexNames"
16 />
17 </n-form-item>
18 - <n-form-item path="source" :show-require-mark="false" v-if="showSourceField" class="source-field">
18 + <n-form-item v-if="showSourceField" path="source" :show-require-mark="false" class="source-field">
19 <template #label>
20 <div class="flex justify-between items-end gap-2">
21 <span>
@@ -25,11 +25,11 @@
25
26 <span v-if="isSocfortressRecommendsAvailable">
27 <n-button
28 - @click="getSocfortressRecommendsWazuh()"
28 :loading="loadingSocfortressRecommendsWazuh"
29 size="tiny"
30 ghost
31 type="primary"
32 + @click="getSocfortressRecommendsWazuh()"
33 >
34 SOCFortress Recommends
35 </n-button>
@@ -40,9 +40,9 @@
40 v-model:value.trim="form.source"
41 placeholder="Please insert Source"
42 clearable
43 - @update:value="resetIndexAvailable()"
43 :disabled="disableSourceField"
44 :loading="loadingSource"
45 + @update:value="resetIndexAvailable()"
46 />
47 </n-form-item>
48
@@ -109,12 +109,12 @@
109 <slot name="additionalActions"></slot>
110 </div>
111 <div class="flex gap-3 items-center">
112 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
112 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
113 <n-button
114 type="primary"
115 :disabled="!isValid"
116 - @click="validate(() => submit())"
116 :loading="submitting"
117 + @click="validate(() => submit())"
118 >
119 Submit
120 </n-button>
@@ -126,25 +126,34 @@
126 </template>
127
128 <script setup lang="ts">
129 -import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
129 +import type { SourceConfiguration, SourceConfigurationModel, SourceName } from "@/types/incidentManagement/sources.d"
130 import Api from "@/api"
131 +import _intersection from "lodash/intersection"
132 import {
133 + type FormInst,
134 + type FormItemRule,
135 + type FormRules,
136 + type FormValidationError,
137 + type MessageReactive,
138 + NAlert,
139 + NButton,
140 NForm,
141 NFormItem,
142 NInput,
135 - NButton,
136 - NSpin,
143 NSelect,
138 - NAlert,
139 - useMessage,
140 - type FormValidationError,
141 - type FormInst,
142 - type FormRules,
143 - type MessageReactive,
144 - type FormItemRule
144 + NSpin,
145 + useMessage
146 } from "naive-ui"
146 -import type { SourceConfiguration, SourceName, SourceConfigurationModel } from "@/types/incidentManagement/sources.d"
147 -import _intersection from "lodash/intersection"
147 +import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
148 +
149 +const props = defineProps<{
150 + sourceConfigurationModel?: SourceConfigurationModel
151 + showSourceField?: boolean
152 + disableSourceField?: boolean
153 + showIndexNameField?: boolean
154 + disableIndexNameField?: boolean
155 + disabledSources?: SourceName[]
156 +}>()
157
158 const emit = defineEmits<{
159 (e: "submitted", value: SourceConfiguration): void
@@ -157,14 +166,6 @@ const emit = defineEmits<{
166 ): void
167 }>()
168
160 -const props = defineProps<{
161 - sourceConfigurationModel?: SourceConfigurationModel
162 - showSourceField?: boolean
163 - disableSourceField?: boolean
164 - showIndexNameField?: boolean
165 - disableIndexNameField?: boolean
166 - disabledSources?: SourceName[]
167 -}>()
169 const {
170 sourceConfigurationModel,
171 showSourceField,
frontend/src/components/incidentManagement/sources/SourceConfigurationViewer.vue
+2 -2
@@ -10,7 +10,7 @@
10 <template #key>Field names</template>
11 <template #value>
12 <div class="flex flex-wrap gap-2">
13 - <Badge type="splitted" v-for="field of sourceConfiguration.field_names" :key="field" fluid>
13 + <Badge v-for="field of sourceConfiguration.field_names" :key="field" type="splitted" fluid>
14 <template #value>
15 {{ field }}
16 </template>
@@ -40,9 +40,9 @@
40 </template>
41
42 <script setup lang="ts">
43 +import type { SourceConfiguration } from "@/types/incidentManagement/sources.d"
44 import Badge from "@/components/common/Badge.vue"
45 import KVCard from "@/components/common/KVCard.vue"
45 -import type { SourceConfiguration } from "@/types/incidentManagement/sources.d"
46
47 const { sourceConfiguration, showSource } = defineProps<{
48 sourceConfiguration: SourceConfiguration
frontend/src/components/incidentManagement/sources/SourceConfigurationWizard.vue
+11 -11
@@ -27,10 +27,10 @@
27
28 <div class="flex gap-4 justify-end">
29 <n-button
30 - @click="next()"
30 v-if="isNextStepEnabled"
31 icon-placement="right"
32 :disabled="!selectedIndex"
33 + @click="next()"
34 >
35 <template #icon>
36 <Icon :name="ArrowRightIcon"></Icon>
@@ -43,17 +43,17 @@
43 <div v-else-if="current === 2" class="px-7 grow flex flex-col pb-7" style="min-height: 401px">
44 <SourceConfigurationForm
45 v-if="sourceConfigurationModel"
46 - :sourceConfigurationModel
46 + :source-configuration-model
47 show-source-field
48 disable-source-field
49 show-index-name-field
50 disable-index-name-field
51 - :disabledSources
51 + :disabled-sources
52 @mounted="formCTX = $event"
53 @submitted="createSourceConfiguration($event)"
54 >
55 <template #additionalActions>
56 - <n-button @click="prev()" :disabled="submitting" v-if="isPrevStepEnabled">
56 + <n-button v-if="isPrevStepEnabled" :disabled="submitting" @click="prev()">
57 <template #icon>
58 <Icon :name="ArrowLeftIcon"></Icon>
59 </template>
@@ -70,14 +70,15 @@
70 </template>
71
72 <script setup lang="ts">
73 -import { computed, onMounted, ref, toRefs, watch } from "vue"
74 -import { NSteps, NStep, useMessage, NScrollbar, NButton, NSpin, NSelect, type StepsProps } from "naive-ui"
75 -import Icon from "@/components/common/Icon.vue"
76 -import Api from "@/api"
77 -import { onBeforeMount } from "vue"
78 -import SourceConfigurationForm from "./SourceConfigurationForm.vue"
73 import type { ApiError } from "@/types/common.d"
74 import type { SourceConfiguration, SourceConfigurationModel, SourceName } from "@/types/incidentManagement/sources.d"
75 +import Api from "@/api"
76 +import Icon from "@/components/common/Icon.vue"
77 +import { NButton, NScrollbar, NSelect, NSpin, NStep, NSteps, type StepsProps, useMessage } from "naive-ui"
78 +import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue"
79 +import SourceConfigurationForm from "./SourceConfigurationForm.vue"
80 +
81 +const props = defineProps<{ disabledSources?: SourceName[] }>()
82
83 const emit = defineEmits<{
84 (e: "update:loading", value: boolean): void
@@ -90,7 +91,6 @@ const emit = defineEmits<{
91 ): void
92 }>()
93
93 -const props = defineProps<{ disabledSources?: SourceName[] }>()
94 const { disabledSources } = toRefs(props)
95
96 const ArrowLeftIcon = "carbon:arrow-left"
frontend/src/components/indices/ClusterHealth.vue
+12 -8
@@ -7,10 +7,10 @@
7 </div>
8 </template>
9 <n-spin :show="loading">
10 - <div class="info" v-if="cluster">
10 + <div v-if="cluster" class="info">
11 <n-scrollbar style="max-height: 500px" trigger="none">
12 <div class="card-wrap">
13 - <div class="box" v-for="prop of propsOrder" :key="prop">
13 + <div v-for="prop of propsOrder" :key="prop" class="box">
14 <template v-if="prop === 'status'">
15 <div class="value uppercase flex align-center gap-2">
16 <IndexIcon :health="cluster.status" color />
@@ -18,9 +18,13 @@
18 </div>
19 </template>
20 <template v-else>
21 - <div class="value">{{ cluster[prop] }}</div>
21 + <div class="value">
22 + {{ cluster[prop] }}
23 + </div>
24 </template>
23 - <div class="label">{{ sanitizeLabel(prop) }}</div>
25 + <div class="label">
26 + {{ sanitizeLabel(prop) }}
27 + </div>
28 </div>
29 </div>
30 </n-scrollbar>
@@ -30,11 +34,11 @@
34 </template>
35
36 <script setup lang="ts">
33 -import { onBeforeMount, ref } from "vue"
34 -import IndexIcon from "@/components/indices/IndexIcon.vue"
37 import type { ClusterHealth } from "@/types/indices.d"
38 import Api from "@/api"
37 -import { useMessage, NSpin, NScrollbar, NCard } from "naive-ui"
39 +import IndexIcon from "@/components/indices/IndexIcon.vue"
40 +import { NCard, NScrollbar, NSpin, useMessage } from "naive-ui"
41 +import { onBeforeMount, ref } from "vue"
42
43 const message = useMessage()
44 const cluster = ref<ClusterHealth | null>(null)
@@ -61,7 +65,7 @@ const propsOrder = ref<Array<keyof ClusterHealth>>([
65 ])
66
67 function sanitizeLabel(label: string) {
64 - return label.replace(/_/gim, " ")
68 + return label.replace(/_/g, " ")
69 }
70
71 function getClusterHealth() {
frontend/src/components/indices/Details.vue
+21 -20
@@ -7,7 +7,7 @@
7 <span v-if="currentIndex">Below the details for index</span>
8 <span v-else>Select an index to see the details</span>
9 </div>
10 - <div class="select-box" v-if="indices && indices.length">
10 + <div v-if="indices && indices.length" class="select-box">
11 <n-select
12 v-model:value="selectValue"
13 placeholder="Indices list"
@@ -19,9 +19,9 @@
19 </div>
20 </template>
21
22 - <div class="details-box" v-if="currentIndex">
22 + <div v-if="currentIndex" class="details-box">
23 <div class="info">
24 - <IndexCard :index="currentIndex" showActions @delete="clearCurrentIndex()" />
24 + <IndexCard :index="currentIndex" show-actions @delete="clearCurrentIndex()" />
25 </div>
26 <n-card class="shards overflow-hidden" content-style="padding:0">
27 <n-scrollbar x-scrollable style="width: 100%">
@@ -55,23 +55,24 @@
55 </template>
56
57 <script setup lang="ts">
58 -import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
59 -import type { IndexStats, IndexShard } from "@/types/indices.d"
60 -import IndexCard from "@/components/indices/IndexCard.vue"
58 +import type { IndexShard, IndexStats } from "@/types/indices.d"
59 import Api from "@/api"
60 +import IndexCard from "@/components/indices/IndexCard.vue"
61 +import { NCard, NScrollbar, NSelect, NSpin, NTable, useMessage } from "naive-ui"
62 import { nanoid } from "nanoid"
63 -import { useMessage, NScrollbar, NSpin, NTable, NSelect, NCard } from "naive-ui"
63 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
64
65 type IndexModel = IndexStats | null | ""
66
67 -const emit = defineEmits<{
68 - (e: "update:modelValue", value: IndexModel): void
69 -}>()
70 -
67 const props = defineProps<{
68 indices: IndexStats[] | null
69 modelValue: IndexModel
70 }>()
71 +
72 +const emit = defineEmits<{
73 + (e: "update:modelValue", value: IndexModel): void
74 +}>()
75 +
76 const { indices, modelValue } = toRefs(props)
77
78 const message = useMessage()
@@ -79,6 +80,15 @@ const shards = ref<IndexShard[]>([])
80 const loadingShards = ref(false)
81 const loading = computed(() => !indices?.value || indices.value === null || loadingShards.value)
82
83 +const currentIndex = computed<IndexModel>({
84 + get() {
85 + return modelValue.value
86 + },
87 + set(value) {
88 + emit("update:modelValue", value)
89 + }
90 +})
91 +
92 const filteredShards = computed(() =>
93 shards.value.filter((shard: IndexShard) => {
94 if (!currentIndex.value || typeof currentIndex.value === "string") return false
@@ -99,15 +109,6 @@ watch(selectValue, val => {
109 currentIndex.value = (indices.value || []).find(o => o.index === val) || null
110 })
111
102 -const currentIndex = computed<IndexModel>({
103 - get() {
104 - return modelValue.value
105 - },
106 - set(value) {
107 - emit("update:modelValue", value)
108 - }
109 -})
110 -
112 function clearCurrentIndex() {
113 currentIndex.value = null
114 }
frontend/src/components/indices/IndexCard.vue
+23 -14
@@ -4,7 +4,9 @@
4 <div class="wrapper">
5 <div class="group">
6 <div class="box">
7 - <div class="value">{{ index.index }}</div>
7 + <div class="value">
8 + {{ index.index }}
9 + </div>
10 <div class="label">name</div>
11 </div>
12 <div class="box">
@@ -17,19 +19,25 @@
19 </div>
20 <div class="group">
21 <div class="box">
20 - <div class="value">{{ index.store_size }}</div>
22 + <div class="value">
23 + {{ index.store_size }}
24 + </div>
25 <div class="label">store_size</div>
26 </div>
27 <div class="box">
24 - <div class="value">{{ index.docs_count }}</div>
28 + <div class="value">
29 + {{ index.docs_count }}
30 + </div>
31 <div class="label">docs_count</div>
32 </div>
33 <div class="box">
28 - <div class="value">{{ index.replica_count }}</div>
34 + <div class="value">
35 + {{ index.replica_count }}
36 + </div>
37 <div class="label">replica_count</div>
38 </div>
39 </div>
32 - <div class="group actions" v-if="showActions">
40 + <div v-if="showActions" class="group actions">
41 <div class="box">
42 <!--
43 <el-tooltip content="Rotate" placement="top" :show-arrow="false">
@@ -54,30 +62,31 @@
62 </template>
63
64 <script setup lang="ts">
57 -import { h, ref, toRefs } from "vue"
58 -import IndexIcon from "@/components/indices/IndexIcon.vue"
65 import type { IndexStats } from "@/types/indices.d"
66 import Api from "@/api"
61 -import { useMessage, useDialog, NTooltip, NButton, NSpin } from "naive-ui"
67 import Icon from "@/components/common/Icon.vue"
68 +import IndexIcon from "@/components/indices/IndexIcon.vue"
69 +import { NButton, NSpin, NTooltip, useDialog, useMessage } from "naive-ui"
70 +import { h, ref, toRefs } from "vue"
71
64 -const DeleteIcon = "ph:trash"
72 +const props = defineProps<{
73 + index: IndexStats
74 + showActions?: boolean
75 +}>()
76
77 const emit = defineEmits<{
78 (e: "delete"): void
79 }>()
80
70 -const props = defineProps<{
71 - index: IndexStats
72 - showActions?: boolean
73 -}>()
81 +const DeleteIcon = "ph:trash"
82 +
83 const { index, showActions } = toRefs(props)
84
85 const dialog = useDialog()
86 const message = useMessage()
87 const loading = ref(false)
88
80 -const handleDelete = () => {
89 +function handleDelete() {
90 dialog.warning({
91 title: "Confirm",
92 content: () =>
frontend/src/components/indices/IndexIcon.vue
+9 -9
@@ -1,24 +1,24 @@
1 <template>
2 <span class="index-icon" :class="[`health-${health}`, { color }]">
3 - <Icon :name="ShieldIcon" v-if="health === IndexHealth.GREEN" :size="18"></Icon>
4 - <Icon :name="WarningIcon" v-if="health === IndexHealth.YELLOW" :size="18"></Icon>
5 - <Icon :name="DangerIcon" v-if="health === IndexHealth.RED" :size="18"></Icon>
3 + <Icon v-if="health === IndexHealth.GREEN" :name="ShieldIcon" :size="18"></Icon>
4 + <Icon v-if="health === IndexHealth.YELLOW" :name="WarningIcon" :size="18"></Icon>
5 + <Icon v-if="health === IndexHealth.RED" :name="DangerIcon" :size="18"></Icon>
6 </span>
7 </template>
8
9 <script setup lang="ts">
10 -import { toRefs } from "vue"
11 -import { type IndexStats, IndexHealth } from "@/types/indices.d"
10 import Icon from "@/components/common/Icon.vue"
13 -
14 -const ShieldIcon = "majesticons:shield-check-line"
15 -const WarningIcon = "majesticons:shield-exclamation-line"
16 -const DangerIcon = "majesticons:exclamation-line"
11 +import { IndexHealth, type IndexStats } from "@/types/indices.d"
12 +import { toRefs } from "vue"
13
14 const props = defineProps<{
15 health: IndexStats["health"]
16 color?: boolean
17 }>()
18 +const ShieldIcon = "majesticons:shield-check-line"
19 +const WarningIcon = "majesticons:shield-exclamation-line"
20 +const DangerIcon = "majesticons:exclamation-line"
21 +
22 const { health, color } = toRefs(props)
23 </script>
24
frontend/src/components/indices/Marquee.vue
+11 -10
@@ -5,7 +5,7 @@
5 <Vue3Marquee
6 class="marquee-wrap"
7 :duration="(list?.length || 0) * 1"
8 - :pauseOnHover="true"
8 + :pause-on-hover="true"
9 :clone="false"
10 :gradient="true"
11 :gradient-color="gradientColor"
@@ -16,8 +16,8 @@
16 :key="item.index"
17 class="item flex items-center gap-2"
18 :class="item.health"
19 - @click="emit('click', item)"
19 title="Click to select"
20 + @click="emit('click', item)"
21 >
22 <IndexIcon :health="item.health" color />
23 {{ item.index }}
@@ -25,7 +25,7 @@
25 </Vue3Marquee>
26 </n-spin>
27 </n-card>
28 - <div class="info" v-if="list?.length">
28 + <div v-if="list?.length" class="info">
29 <i class="mdi mdi-information-outline"></i>
30 Click on an index to select
31 </div>
@@ -33,21 +33,22 @@
33 </template>
34
35 <script setup lang="ts">
36 -import { computed, ref, toRefs, watch, onBeforeMount } from "vue"
36 import type { IndexStats } from "@/types/indices.d"
38 -import { Vue3Marquee } from "vue3-marquee"
37 +import Api from "@/api"
38 import IndexIcon from "@/components/indices/IndexIcon.vue"
40 -import { NSpin, NCard, useMessage } from "naive-ui"
39 import { useThemeStore } from "@/stores/theme"
42 -import Api from "@/api"
40 +import { NCard, NSpin, useMessage } from "naive-ui"
41 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
42 +import { Vue3Marquee } from "vue3-marquee"
43 +
44 +const props = defineProps<{
45 + indices?: IndexStats[] | null
46 +}>()
47
48 const emit = defineEmits<{
49 (e: "click", value: IndexStats): void
50 }>()
51
48 -const props = defineProps<{
49 - indices?: IndexStats[] | null
50 -}>()
52 const { indices } = toRefs(props)
53
54 const list = ref(indices.value)
frontend/src/components/indices/NodeAllocation.vue
+19 -11
@@ -18,25 +18,33 @@
18 >
19 <div class="group">
20 <div class="box">
21 - <div class="value">{{ node.node }}</div>
21 + <div class="value">
22 + {{ node.node }}
23 + </div>
24 <div class="label">node</div>
25 </div>
26 </div>
27 <div class="group">
28 <div class="box">
27 - <div class="value">{{ node.disk_total || "-" }}</div>
29 + <div class="value">
30 + {{ node.disk_total || "-" }}
31 + </div>
32 <div class="label">disk_total</div>
33 </div>
34 <div class="box">
31 - <div class="value">{{ node.disk_used || "-" }}</div>
35 + <div class="value">
36 + {{ node.disk_used || "-" }}
37 + </div>
38 <div class="label">disk_used</div>
39 </div>
40 <div class="box">
35 - <div class="value">{{ node.disk_available || "-" }}</div>
41 + <div class="value">
42 + {{ node.disk_available || "-" }}
43 + </div>
44 <div class="label">disk_available</div>
45 </div>
46 </div>
39 - <div class="group disk-percent" v-if="node.disk_percent">
47 + <div v-if="node.disk_percent" class="group disk-percent">
48 <div class="box w-full">
49 <n-progress
50 type="line"
@@ -57,19 +65,19 @@
65 </template>
66
67 <script setup lang="ts">
60 -import { onBeforeMount, ref } from "vue"
61 -import { type IndexAllocation } from "@/types/indices.d"
68 +import type { IndexAllocation } from "@/types/indices.d"
69 import Api from "@/api"
70 +import { NCard, NProgress, NScrollbar, NSpin, useMessage } from "naive-ui"
71 import { nanoid } from "nanoid"
64 -import { useMessage, NSpin, NScrollbar, NProgress, NCard } from "naive-ui"
72 +import { onBeforeMount, ref } from "vue"
73
74 const message = useMessage()
75 const indicesAllocation = ref<IndexAllocation[]>([])
76 const loading = ref(true)
77
78 function getStatusPercent(percent: string | number | undefined | null) {
71 - if (parseFloat(percent?.toString() || "") > 90) return "error"
72 - if (parseFloat(percent?.toString() || "") > 80) return "warning"
79 + if (Number.parseFloat(percent?.toString() || "") > 90) return "error"
80 + if (Number.parseFloat(percent?.toString() || "") > 80) return "warning"
81 return "success"
82 }
83
@@ -81,7 +89,7 @@ function getIndicesAllocation() {
89 if (res.data.success) {
90 indicesAllocation.value = (res.data?.node_allocation || []).map(obj => {
91 obj.id = nanoid()
84 - obj.disk_percent_value = parseFloat(obj.disk_percent || "")
92 + obj.disk_percent_value = Number.parseFloat(obj.disk_percent || "")
93 return obj
94 })
95 } else {
frontend/src/components/indices/TopIndices.vue
+10 -13
@@ -9,17 +9,17 @@
9 </template>
10
11 <script setup lang="ts">
12 -import { type ECharts, init as echartsInit, use as echartsUse } from "echarts/core"
13 -import { CanvasRenderer } from "echarts/renderers"
12 +import { useThemeStore } from "@/stores/theme"
13 +import { IndexHealth, type IndexStats } from "@/types/indices.d"
14 +import bytes from "bytes"
15 import { PieChart } from "echarts/charts"
15 -import { TooltipComponent, LegendComponent, GridComponent } from "echarts/components"
16
17 -import { computed, onMounted, ref, toRefs, watch } from "vue"
18 -import { type IndexStats, IndexHealth } from "@/types/indices.d"
19 -import bytes from "bytes"
17 +import { GridComponent, LegendComponent, TooltipComponent } from "echarts/components"
18 +import { type ECharts, init as echartsInit, use as echartsUse } from "echarts/core"
19 +import { CanvasRenderer } from "echarts/renderers"
20 import _ from "lodash"
21 -import { NSpin, NCard } from "naive-ui"
22 -import { useThemeStore } from "@/stores/theme"
21 +import { NCard, NSpin } from "naive-ui"
22 +import { computed, onMounted, ref, toRefs, watch } from "vue"
23
24 const props = defineProps<{
25 indices: IndexStats[] | null
@@ -136,7 +136,7 @@ function getOptions() {
136 minMargin: 10,
137 edgeDistance: 10,
138 lineHeight: 15,
139 - //alignTo: "edge",
139 + // alignTo: "edge",
140 rich: {
141 name: {
142 color: style.value["fg-color"],
@@ -149,10 +149,7 @@ function getOptions() {
149 }
150 }
151 },
152 - labelLayout: function (params: {
153 - labelLinePoints: Array<number[]>
154 - labelRect: { x: number; width: number }
155 - }) {
152 + labelLayout(params: { labelLinePoints: Array<number[]>; labelRect: { x: number; width: number } }) {
153 const isLeft = chartCtx.value ? params.labelRect.x < chartCtx.value.getWidth() / 2 : false
154 const points = params.labelLinePoints
155 // Update the end point.
frontend/src/components/indices/UnhealthyIndices.vue
+11 -10
@@ -15,13 +15,13 @@
15 :key="item.index"
16 class="item"
17 :class="item.health"
18 - @click="emit('click', item)"
18 title="Click for details"
19 + @click="emit('click', item)"
20 >
21 <IndexCard :index="item" />
22 </div>
23 </template>
24 - <n-empty description="No Unhealthy Indices found" v-else>
24 + <n-empty v-else description="No Unhealthy Indices found">
25 <template #icon>
26 <Icon :name="ShieldIcon"></Icon>
27 </template>
@@ -34,21 +34,22 @@
34 </template>
35
36 <script setup lang="ts">
37 -import { computed, toRefs } from "vue"
38 -import { type IndexStats, IndexHealth } from "@/types/indices.d"
39 -import IndexCard from "@/components/indices/IndexCard.vue"
40 -import { NSpin, NCard, NEmpty, NScrollbar } from "naive-ui"
37 import Icon from "@/components/common/Icon.vue"
38 +import IndexCard from "@/components/indices/IndexCard.vue"
39 +import { IndexHealth, type IndexStats } from "@/types/indices.d"
40 +import { NCard, NEmpty, NScrollbar, NSpin } from "naive-ui"
41 +import { computed, toRefs } from "vue"
42
43 -const ShieldIcon = "fluent:shield-task-20-regular"
43 +const props = defineProps<{
44 + indices: IndexStats[] | null
45 +}>()
46
47 const emit = defineEmits<{
48 (e: "click", value: IndexStats): void
49 }>()
50
49 -const props = defineProps<{
50 - indices: IndexStats[] | null
51 -}>()
51 +const ShieldIcon = "fluent:shield-task-20-regular"
52 +
53 const { indices } = toRefs(props)
54
55 const loading = computed(() => !indices?.value || indices.value === null)
frontend/src/components/integrations/IntegrationsList.vue
+6 -6
@@ -1,22 +1,22 @@
1 <template>
2 <ServicesList
3 + v-model:selected="selected"
4 type="integration"
5 :embedded="embedded"
5 - :hideTotals="hideTotals"
6 + :hide-totals="hideTotals"
7 :selectable="selectable"
7 - :disabledIdsList="disabledIdsList"
8 + :disabled-ids-list="disabledIdsList"
9 :loading="loading"
10 :list="list"
10 - v-model:selected="selected"
11 />
12 </template>
13
14 <script setup lang="ts">
15 -import { ref, onBeforeMount } from "vue"
16 -import { useMessage } from "naive-ui"
15 +import type { ServiceItemData } from "../services/types"
16 import Api from "@/api"
17 import ServicesList from "@/components/services/List.vue"
19 -import type { ServiceItemData } from "../services/types"
18 +import { useMessage } from "naive-ui"
19 +import { onBeforeMount, ref } from "vue"
20
21 const { embedded, hideTotals, selectable, disabledIdsList } = defineProps<{
22 embedded?: boolean
frontend/src/components/license/LicenseCheckoutResponse.vue
+3 -3
@@ -30,12 +30,12 @@
30 </template>
31
32 <script setup lang="ts">
33 -import { onBeforeMount, ref, toRefs } from "vue"
33 +import type { LicenseKey } from "@/types/license.d"
34 import Api from "@/api"
35 -import { NButton, NCard, NSpin, useMessage } from "naive-ui"
35 import Icon from "@/components/common/Icon.vue"
37 -import type { LicenseKey } from "@/types/license.d"
36 import { useGoto } from "@/composables/useGoto"
37 +import { NButton, NCard, NSpin, useMessage } from "naive-ui"
38 +import { onBeforeMount, ref, toRefs } from "vue"
39
40 const props = defineProps<{ type: "success" | "error"; data?: { email?: string } }>()
41 const { type, data } = toRefs(props)
frontend/src/components/license/LicenseCheckoutWizard.vue
+18 -18
@@ -1,11 +1,13 @@
1 <template>
2 <n-spin :show="loading" class="min-h-48">
3 - <n-empty :description="errorMessage" class="justify-center h-48" v-if="errorMessage">
4 - <template #icon><Icon :name="WarningIcon"></Icon></template>
3 + <n-empty v-if="errorMessage" :description="errorMessage" class="justify-center h-48">
4 + <template #icon>
5 + <Icon :name="WarningIcon"></Icon>
6 + </template>
7 </n-empty>
8 <template v-else>
9 <template v-if="!selectedSubscription">
8 - <div class="list flex flex-col gap-2" v-if="availableSubscriptions.length">
10 + <div v-if="availableSubscriptions.length" class="list flex flex-col gap-2">
11 <SubscriptionCard
12 v-for="subscription of availableSubscriptions"
13 :key="subscription.id"
@@ -18,9 +20,9 @@
20 </div>
21 <template v-else>
22 <n-empty
23 + v-if="!loading"
24 description="Congratulations, you have already unlocked all available features"
25 class="justify-center h-48"
23 - v-if="!loading"
26 >
27 <template #icon>
28 <Icon :name="CheckIcon"></Icon>
@@ -32,7 +34,7 @@
34 <SubscriptionCard :subscription="selectedSubscription" embedded hide-details />
35 <div class="checkout-form item-appear item-appear-bottom item-appear-005 mt-8">
36 <n-spin :show="loadingLicense || loadingSession">
35 - <n-form :label-width="80" :model="checkoutForm" :rules="rules" ref="formRef">
37 + <n-form :label-width="80" :model="checkoutForm" :rules="rules">
38 <div class="flex flex-col gap-1">
39 <n-form-item label="Company Name" path="company_name">
40 <n-input
@@ -58,8 +60,8 @@
60 <n-button
61 type="success"
62 :disabled="!isValid"
61 - @click="createCheckoutSession()"
63 :loading="loadingSession"
64 + @click="createCheckoutSession()"
65 >
66 <template #icon>
67 <Icon :name="CartIcon"></Icon>
@@ -77,25 +79,23 @@
79 </template>
80
81 <script setup lang="ts">
82 +import type { CheckoutPayload, License, LicenseCustomer, LicenseFeatures, SubscriptionFeature } from "@/types/license.d"
83 +import Api from "@/api"
84 +import Icon from "@/components/common/Icon.vue"
85 import {
81 - NSpin,
86 + type FormItemRule,
87 + type FormRules,
88 + NButton,
89 NEmpty,
90 NForm,
91 NFormItem,
92 NInput,
86 - NButton,
87 - useMessage,
88 - type FormItemRule,
89 - type FormRules
93 + NSpin,
94 + useMessage
95 } from "naive-ui"
91 -import Icon from "@/components/common/Icon.vue"
92 -import Api from "@/api"
93 -import { onBeforeMount, ref, toRefs } from "vue"
94 -import { computed } from "vue"
95 -import SubscriptionCard from "./SubscriptionCard.vue"
96 -import type { CheckoutPayload, License, LicenseCustomer, LicenseFeatures, SubscriptionFeature } from "@/types/license.d"
96 import isEmail from "validator/es/lib/isEmail"
98 -import { watch } from "vue"
97 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
98 +import SubscriptionCard from "./SubscriptionCard.vue"
99
100 const props = defineProps<{
101 featuresData?: LicenseFeatures[]
frontend/src/components/license/LicenseDetails.vue
+28 -20
@@ -8,7 +8,9 @@
8 <span>Key</span>
9 </span>
10 </template>
11 - <template #value>{{ license.key }}</template>
11 + <template #value>
12 + {{ license.key }}
13 + </template>
14 </KVCard>
15 <KVCard v-if="!hideFeatures">
16 <template #key>
@@ -53,18 +55,22 @@
55 <template #value>
56 <div class="flex flex-wrap gap-2">
57 <Badge
56 - type="splitted"
57 - color="primary"
58 v-for="(value, key) of license.customer"
59 :key="key"
60 + type="splitted"
61 + color="primary"
62 fluid
63 >
62 - <template #label>{{ sanitizeKey(key) }}</template>
64 + <template #label>
65 + {{ sanitizeKey(key) }}
66 + </template>
67 <template #value>
68 <template v-if="key === 'created'">
69 {{ formatDate(value, dFormats.datetime) }}
70 </template>
67 - <template v-else>{{ value ?? "-" }}</template>
71 + <template v-else>
72 + {{ value ?? "-" }}
73 + </template>
74 </template>
75 </Badge>
76 </div>
@@ -79,7 +85,7 @@
85 </template>
86 <template #value>
87 <Suspense>
82 - <Markdown :source="dockerCompose" codeBgTransparent />
88 + <Markdown :source="dockerCompose" code-bg-transparent />
89 </Suspense>
90 </template>
91 </KVCard>
@@ -88,17 +94,23 @@
94 </template>
95
96 <script setup lang="ts">
91 -import { NSpin, useMessage } from "naive-ui"
92 -import Icon from "@/components/common/Icon.vue"
97 +import type { License, LicenseFeatures } from "@/types/license.d"
98 import Api from "@/api"
94 -import { onBeforeMount, onMounted, ref, toRefs, computed, defineAsyncComponent } from "vue"
95 -import { type LicenseFeatures, type License } from "@/types/license.d"
96 -import { formatDate } from "@/utils"
97 -import { useSettingsStore } from "@/stores/settings"
98 -import _startCase from "lodash/startCase"
99 import Badge from "@/components/common/Badge.vue"
100 +import Icon from "@/components/common/Icon.vue"
101 import KVCard from "@/components/common/KVCard.vue"
101 -const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
102 +import { useSettingsStore } from "@/stores/settings"
103 +import { formatDate } from "@/utils"
104 +import _startCase from "lodash/startCase"
105 +import { NSpin, useMessage } from "naive-ui"
106 +import { computed, defineAsyncComponent, onBeforeMount, onMounted, ref, toRefs } from "vue"
107 +
108 +const props = defineProps<{
109 + licenseData?: License
110 + featuresData?: LicenseFeatures[]
111 + hideKey?: boolean
112 + hideFeatures?: boolean
113 +}>()
114
115 const emit = defineEmits<{
116 (e: "licenseLoaded", value: License): void
@@ -110,12 +122,8 @@ const emit = defineEmits<{
122 ): void
123 }>()
124
113 -const props = defineProps<{
114 - licenseData?: License
115 - featuresData?: LicenseFeatures[]
116 - hideKey?: boolean
117 - hideFeatures?: boolean
118 -}>()
125 +const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
126 +
127 const { licenseData, featuresData, hideKey, hideFeatures } = toRefs(props)
128
129 const KeyIcon = "ph:key"
frontend/src/components/license/LicenseFeatureOverlay.vue
+5 -6
@@ -1,5 +1,5 @@
1 <template>
2 - <div class="over-layer feature-layer" v-if="showBanner">
2 + <div v-if="showBanner" class="over-layer feature-layer">
3 <n-alert title="Feature required">
4 <template #icon>
5 <Icon :name="AlertIcon" :size="18"></Icon>
@@ -23,13 +23,12 @@
23 </template>
24
25 <script setup lang="ts">
26 -import { ref } from "vue"
27 -import { NAlert, NButton } from "naive-ui"
28 -import Icon from "@/components/common/Icon.vue"
29 -import Api from "@/api"
30 -import { onBeforeMount } from "vue"
26 import type { LicenseFeatures } from "@/types/license.d"
27 +import Api from "@/api"
28 +import Icon from "@/components/common/Icon.vue"
29 import { useGoto } from "@/composables/useGoto"
30 +import { NAlert, NButton } from "naive-ui"
31 +import { onBeforeMount, ref } from "vue"
32
33 const { feature } = defineProps<{ feature: LicenseFeatures }>()
34
frontend/src/components/license/LicenseFeatures.vue
+21 -20
@@ -2,12 +2,12 @@
2 <div class="license-features" :class="{ loading }">
3 <div class="license-features-box flex items-center justify-center">
4 <n-spin :show="loading" class="h-full w-full" content-class="h-full">
5 - <div class="wrapper h-full flex flex-col gap-4" v-if="!loading">
5 + <div v-if="!loading" class="wrapper h-full flex flex-col gap-4">
6 <div class="flex justify-between items-center gap-4">
7 <h3>
8 {{ features.length ? "Your features" : "Unlock features" }}
9 </h3>
10 - <n-popover class="max-w-80" trigger="hover" v-if="features.length">
10 + <n-popover v-if="features.length" class="max-w-80" trigger="hover">
11 <template #trigger>
12 <Icon :name="InfoIcon" :size="20" class="cursor-help"></Icon>
13 </template>
@@ -17,19 +17,19 @@
17 </div>
18 <div class="grow overflow-hidden">
19 <n-scrollbar v-if="!loading">
20 - <div class="features-list flex flex-col gap-2" v-if="license">
20 + <div v-if="license" class="features-list flex flex-col gap-2">
21 <template v-if="activeSubscriptions.length">
22 <SubscriptionCard
23 v-for="subscription of activeSubscriptions"
24 :key="subscription.id"
25 :subscription="subscription"
26 - :licenseData="licenseData"
26 + :license-data="licenseData"
27 embedded
28 - showDeleteOnDialog
28 + show-delete-on-dialog
29 @deleted="load()"
30 />
31 </template>
32 - <n-empty description="No features unlocked" class="justify-center h-48" v-else>
32 + <n-empty v-else description="No features unlocked" class="justify-center h-48">
33 <template #icon>
34 <Icon :name="NoFeaturesIcon"></Icon>
35 </template>
@@ -38,8 +38,8 @@
38 <LicenseCheckoutWizard v-else :features-data="features" />
39 </n-scrollbar>
40 </div>
41 - <div class="cta-section" v-if="license">
42 - <n-button type="primary" @click="openCheckout()" class="!w-full" size="large">
41 + <div v-if="license" class="cta-section">
42 + <n-button type="primary" class="!w-full" size="large" @click="openCheckout()">
43 <template #icon>
44 <Icon :name="ExtendIcon"></Icon>
45 </template>
@@ -50,7 +50,7 @@
50 </n-spin>
51 </div>
52
53 - <div class="footer mt-3" v-if="!hideKey && !loading">
53 + <div v-if="!hideKey && !loading" class="footer mt-3">
54 <template v-if="license">
55 <span class="cursor-pointer" @click="showLicenseDetails = true">
56 Your license:
@@ -86,7 +86,7 @@
86 content-class="flex flex-col"
87 segmented
88 >
89 - <LicenseDetails :features-data="features" hide-features v-if="license" />
89 + <LicenseDetails v-if="license" :features-data="features" hide-features />
90 </n-modal>
91
92 <n-modal
@@ -104,15 +104,20 @@
104 </template>
105
106 <script setup lang="ts">
107 -import { NScrollbar, NSpin, NModal, NButton, NEmpty, NPopover, useMessage } from "naive-ui"
108 -import Icon from "@/components/common/Icon.vue"
107 +import type { License, LicenseFeatures, LicenseKey, SubscriptionFeature } from "@/types/license.d"
108 import Api from "@/api"
110 -import { onBeforeMount, onMounted, ref, toRefs, computed } from "vue"
111 -import { type License, type LicenseFeatures, type LicenseKey, type SubscriptionFeature } from "@/types/license.d"
112 -import SubscriptionCard from "./SubscriptionCard.vue"
109 +import Icon from "@/components/common/Icon.vue"
110 +import { NButton, NEmpty, NModal, NPopover, NScrollbar, NSpin, useMessage } from "naive-ui"
111 +import { computed, onBeforeMount, onMounted, ref, toRefs } from "vue"
112 import LicenseCheckoutWizard from "./LicenseCheckoutWizard.vue"
114 -import LicenseLoadForm from "./LicenseLoadForm.vue"
113 import LicenseDetails from "./LicenseDetails.vue"
114 +import LicenseLoadForm from "./LicenseLoadForm.vue"
115 +import SubscriptionCard from "./SubscriptionCard.vue"
116 +
117 +const props = defineProps<{
118 + hideKey?: boolean
119 + licenseData?: License
120 +}>()
121
122 const emit = defineEmits<{
123 (e: "licenseKeyLoaded", value: LicenseKey): void
@@ -124,10 +129,6 @@ const emit = defineEmits<{
129 ): void
130 }>()
131
127 -const props = defineProps<{
128 - hideKey?: boolean
129 - licenseData?: License
130 -}>()
132 const { hideKey, licenseData } = toRefs(props)
133
134 const InfoIcon = "carbon:information"
frontend/src/components/license/LicenseLoadForm.vue
+4 -5
@@ -13,12 +13,11 @@
13 </template>
14
15 <script setup lang="ts">
16 -import { NInput, NButton, NSpin, useMessage } from "naive-ui"
17 -import Icon from "@/components/common/Icon.vue"
18 -import Api from "@/api"
19 -import { ref } from "vue"
20 -import { computed } from "vue"
16 import type { LicenseKey } from "@/types/license.d"
17 +import Api from "@/api"
18 +import Icon from "@/components/common/Icon.vue"
19 +import { NButton, NInput, NSpin, useMessage } from "naive-ui"
20 +import { computed, ref } from "vue"
21
22 const emit = defineEmits<{
23 (e: "uploaded"): void
frontend/src/components/license/LicenseViewer.vue
+5 -5
@@ -2,27 +2,27 @@
2 <div class="license-viewer flex justify-center items-center gap-4" :class="{ 'has-side': !!key }">
3 <div class="main-box">
4 <LicenseFeatures
5 - @license-key-loaded="licenseKeyLoaded"
5 :hide-key="!!key"
6 :license-data="details"
7 class="h-full"
8 :class="{ 'mt-8': !key }"
9 + @license-key-loaded="licenseKeyLoaded"
10 />
11 </div>
12 <div class="side-box">
13 <n-scrollbar style="max-width: 100%">
14 - <LicenseDetails hide-features @license-loaded="licenseLoaded" v-if="key" class="min-h-full" />
14 + <LicenseDetails v-if="key" hide-features class="min-h-full" @license-loaded="licenseLoaded" />
15 </n-scrollbar>
16 </div>
17 </div>
18 </template>
19
20 <script setup lang="ts">
21 -import { ref } from "vue"
21 +import type { License, LicenseKey } from "@/types/license.d"
22 import { NScrollbar } from "naive-ui"
23 -import LicenseFeatures from "./LicenseFeatures.vue"
23 +import { ref } from "vue"
24 import LicenseDetails from "./LicenseDetails.vue"
25 -import type { License, LicenseKey } from "@/types/license.d"
25 +import LicenseFeatures from "./LicenseFeatures.vue"
26
27 const emit = defineEmits<{
28 (e: "licenseKeyLoaded", value: LicenseKey): void
frontend/src/components/license/SubscriptionCard.vue
+20 -15
@@ -8,7 +8,7 @@
8 <div class="header-box flex justify-between items-center">
9 <div class="flex items-center gap-2 cursor-pointer">
10 <span>{{ subscription.name }}</span>
11 - <span class="info-btn pt-0.5" @click.stop="showDetails = true" v-if="selectable">
11 + <span v-if="selectable" class="info-btn pt-0.5" @click.stop="showDetails = true">
12 <Icon :name="InfoIcon" :size="14"></Icon>
13 </span>
14 </div>
@@ -16,13 +16,15 @@
16 {{ price(subscription.price) }}
17 </div>
18 </div>
19 - <div class="main-box flex items-center gap-3" v-if="!hideDetails">
19 + <div v-if="!hideDetails" class="main-box flex items-center gap-3">
20 <div class="content flex flex-col gap-2 grow">
21 - <div class="title">{{ subscription.info }}</div>
21 + <div class="title">
22 + {{ subscription.info }}
23 + </div>
24 <div class="description">
25 {{ subscription.short_description }}
26 </div>
25 - <div class="flex items-center justify-end" v-if="showDeleteOnCard && licenseData">
27 + <div v-if="showDeleteOnCard && licenseData" class="flex items-center justify-end">
28 <n-popconfirm @positive-click="cancelSubscription()">
29 <template #trigger>
30 <n-button text size="small" class="opacity-50">
@@ -55,8 +57,10 @@
57 {{ price(subscription.price) }}
58 </div>
59 </div>
58 - <div class="grow">{{ subscription.full_description }}</div>
59 - <div class="flex items-center justify-end" v-if="showDeleteOnDialog && licenseData">
60 + <div class="grow">
61 + {{ subscription.full_description }}
62 + </div>
63 + <div v-if="showDeleteOnDialog && licenseData" class="flex items-center justify-end">
64 <n-popconfirm to="body" @positive-click="cancelSubscription()">
65 <template #trigger>
66 <n-button text size="small" class="opacity-50">
@@ -75,17 +79,13 @@
79 </template>
80
81 <script setup lang="ts">
78 -import Icon from "@/components/common/Icon.vue"
79 -import { ref, toRefs } from "vue"
80 -import { NSpin, NModal, NButton, NPopconfirm, useMessage } from "naive-ui"
81 -import Api from "@/api"
82 +import type { CancelSubscriptionPayload } from "@/api/endpoints/license"
83 import type { License, SubscriptionFeature } from "@/types/license.d"
84 +import Api from "@/api"
85 +import Icon from "@/components/common/Icon.vue"
86 import { price } from "@/utils"
84 -import type { CancelSubscriptionPayload } from "@/api/endpoints/license"
85 -
86 -const emit = defineEmits<{
87 - (e: "deleted"): void
88 -}>()
87 +import { NButton, NModal, NPopconfirm, NSpin, useMessage } from "naive-ui"
88 +import { ref, toRefs } from "vue"
89
90 const props = defineProps<{
91 subscription: SubscriptionFeature
@@ -97,6 +97,11 @@ const props = defineProps<{
97 showDeleteOnDialog?: boolean
98 licenseData?: License
99 }>()
100 +
101 +const emit = defineEmits<{
102 + (e: "deleted"): void
103 +}>()
104 +
105 const { subscription, embedded, selectable, disabled, hideDetails, showDeleteOnCard, showDeleteOnDialog, licenseData } =
106 toRefs(props)
107
frontend/src/components/license/bkp/LicenseCheckout.vue
+6 -6
@@ -3,7 +3,7 @@
3 <div class="flex flex-col gap-1">
4 <p class="flex gap-4 items-center">
5 <span>license</span>
6 - <Icon :name="LoadingIcon" v-if="loadingLicense"></Icon>
6 + <Icon v-if="loadingLicense" :name="LoadingIcon"></Icon>
7 </p>
8
9 <h3 v-if="!loadingLicense">
@@ -11,7 +11,7 @@
11 </h3>
12 </div>
13
14 - <div class="actions-box flex gap-2 mr-2" v-if="!loadingLicense">
14 + <div v-if="!loadingLicense" class="actions-box flex gap-2 mr-2">
15 <n-button type="primary" @click="openCheckout()">
16 <template #icon>
17 <Icon :name="licenseKey ? ExtendIcon : LicenseIcon"></Icon>
@@ -35,12 +35,12 @@
35 </template>
36
37 <script setup lang="ts">
38 -import { NButton, NModal, useMessage } from "naive-ui"
39 -import Icon from "@/components/common/Icon.vue"
38 +import type { LicenseKey } from "@/types/license.d"
39 import Api from "@/api"
40 +import Icon from "@/components/common/Icon.vue"
41 +import { NButton, NModal, useMessage } from "naive-ui"
42 import { onBeforeMount, ref } from "vue"
43 import LicenseCheckoutWizard from "./LicenseCheckoutWizard.vue"
43 -import type { LicenseKey } from "@/types/license.d"
44
45 const emit = defineEmits<{
46 (e: "loaded"): void
@@ -67,7 +67,7 @@ function getLicense() {
67 .getLicense()
68 .then(res => {
69 if (res.data.success) {
70 - //licenseKey.value = res.data?.license_key || ""
70 + // licenseKey.value = res.data?.license_key || ""
71 if (licenseKey.value) {
72 emit("loaded")
73 }
frontend/src/components/license/bkp/LicenseEditor.vue
+26 -27
@@ -1,20 +1,20 @@
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">
4 + <p v-if="loadingLicense || licenseKey" class="flex gap-4 items-center">
5 <span>your license:</span>
6 - <Icon :name="LoadingIcon" v-if="loadingLicense"></Icon>
6 + <Icon v-if="loadingLicense" :name="LoadingIcon"></Icon>
7 </p>
8 <div v-if="!loadingLicense && !licenseKey">
9 - <p class="flex gap-4 items-center" v-if="!creationEnabled">no license found</p>
9 + <p v-if="!creationEnabled" class="flex gap-4 items-center">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 + v-if="!creationEnabled"
14 type="primary"
15 :loading="loadingCreation"
15 - @click="enableCreation()"
16 - v-if="!creationEnabled"
16 size="small"
17 + @click="enableCreation()"
18 >
19 <template #icon>
20 <Icon :name="LicenseIcon"></Icon>
@@ -24,21 +24,21 @@
24 </div>
25 </div>
26 </div>
27 - <div class="flex items-center gap-4 mt-1" v-if="!replaceEnabled && licenseKey">
27 + <div v-if="!replaceEnabled && licenseKey" class="flex items-center gap-4 mt-1">
28 <h3>{{ licenseKey }}</h3>
29 <div class="actions-box flex gap-2">
30 - <n-button secondary :disabled="replaceEnabled" @click="enableReplace()" size="small">
30 + <n-button secondary :disabled="replaceEnabled" size="small" @click="enableReplace()">
31 <template #icon>
32 <Icon :name="EditIcon"></Icon>
33 </template>
34 Edit
35 </n-button>
36 <n-button
37 + v-if="!extendEnabled"
38 type="primary"
39 :loading="loadingExtend"
39 - @click="enableExtend()"
40 - v-if="!extendEnabled"
40 size="small"
41 + @click="enableExtend()"
42 >
43 <template #icon>
44 <Icon :name="ExtendIcon"></Icon>
@@ -49,7 +49,7 @@
49 </div>
50 </div>
51
52 - <div class="replace-box mt-2 flex gap-2" v-if="replaceEnabled">
52 + <div v-if="replaceEnabled" class="replace-box mt-2 flex gap-2">
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()">
@@ -60,7 +60,7 @@
60 </n-button>
61 </div>
62
63 - <div class="extend-box mt-5 flex gap-2" v-if="extendEnabled">
63 + <div v-if="extendEnabled" class="extend-box mt-5 flex gap-2">
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>
@@ -75,8 +75,8 @@
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">
78 + <div v-if="creationEnabled" class="create-box flex flex-col gap-2">
79 + <n-form ref="formRef" :label-width="80" :model="creationForm" :rules="rules">
80 <div class="grid gap-2 grid-auto-fit-200">
81 <n-form-item label="Name" path="name">
82 <n-input v-model:value.trim="creationForm.name" placeholder="Input name..." clearable />
@@ -112,26 +112,25 @@
112 </template>
113
114 <script setup lang="ts">
115 +import type { NewLicensePayload } from "@/api/endpoints/license"
116 +import type { LicenseKey } from "@/types/license.d"
117 +import Api from "@/api"
118 +import Icon from "@/components/common/Icon.vue"
119 import {
120 + type FormInst,
121 + type FormItemRule,
122 + type FormRules,
123 + type FormValidationError,
124 + NButton,
125 + NForm,
126 + NFormItem,
127 NInput,
128 NInputNumber,
118 - NButton,
129 NSpin,
120 - NFormItem,
121 - NForm,
122 - useMessage,
123 - type FormRules,
124 - type FormItemRule,
125 - type FormInst,
126 - type FormValidationError
130 + useMessage
131 } from "naive-ui"
128 -import Icon from "@/components/common/Icon.vue"
129 -import Api from "@/api"
130 -import { onBeforeMount, ref } from "vue"
132 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/endpoints/license"
133 +import { computed, onBeforeMount, ref } from "vue"
134
135 const emit = defineEmits<{
136 (e: "updated"): void
frontend/src/components/logs/LogItem.vue
+16 -8
@@ -14,10 +14,16 @@
14 <div class="method" :class="methodLower">
15 <strong>{{ log.method }}</strong>
16 </div>
17 - <div class="route">{{ log.route }}</div>
17 + <div class="route">
18 + {{ log.route }}
19 + </div>
20 + </div>
21 + <div class="title px-1">
22 + {{ log.message }}
23 + </div>
24 + <div v-if="log.additional_info" class="description px-1">
25 + {{ log.additional_info }}
26 </div>
19 - <div class="title px-1">{{ log.message }}</div>
20 - <div class="description px-1" v-if="log.additional_info">{{ log.additional_info }}</div>
27
28 <div class="badges-box flex flex-wrap items-center gap-3 mt-2">
29 <Badge type="splitted" :color="log.event_type === LogEventType.ERROR ? 'danger' : 'primary'">
@@ -28,9 +34,11 @@
34 ></Icon>
35 </template>
36 <template #label>Type</template>
31 - <template #value>{{ log.event_type }}</template>
37 + <template #value>
38 + {{ log.event_type }}
39 + </template>
40 </Badge>
33 - <Badge type="splitted" color="primary" v-if="log.user_id">
41 + <Badge v-if="log.user_id" type="splitted" color="primary">
42 <template #iconLeft>
43 <Icon :name="UserIcon" :size="14"></Icon>
44 </template>
@@ -53,13 +61,13 @@
61 </template>
62
63 <script setup lang="ts">
64 +import type { AuthUser } from "@/types/auth.d"
65 +import Badge from "@/components/common/Badge.vue"
66 import Icon from "@/components/common/Icon.vue"
67 import { useSettingsStore } from "@/stores/settings"
68 +import { type Log, LogEventType } from "@/types/logs.d"
69 import dayjs from "@/utils/dayjs"
59 -import { LogEventType, type Log } from "@/types/logs.d"
60 -import Badge from "@/components/common/Badge.vue"
70 import { computed } from "vue"
62 -import type { AuthUser } from "@/types/auth.d"
71
72 const { log, users } = defineProps<{ log: Log; users?: AuthUser[] }>()
73
frontend/src/components/logs/LogsFilters.vue
+14 -13
@@ -12,7 +12,7 @@
12 />
13 </div>
14 <div class="px-3 !w-72">
15 - <div class="flex grow" v-if="filterType === 'userId'">
15 + <div v-if="filterType === 'userId'" class="flex grow">
16 <n-select
17 v-if="userIdOptions.length"
18 v-model:value="filterUserId"
@@ -23,12 +23,12 @@
23 class="grow"
24 />
25 <n-input
26 + v-else
27 v-model:value="filterUserId"
28 :loading="loadingUsers"
29 :disabled="loadingUsers"
30 :placeholder="loadingUsers ? 'Loading users...' : 'Insert User ID'"
31 class="grow"
31 - v-else
32 />
33 </div>
34 <n-select
@@ -49,26 +49,28 @@
49 </n-input-group>
50 </div>
51 <div class="px-3 flex justify-end gap-2">
52 - <n-button size="small" @click="close()" secondary>Close</n-button>
53 - <n-button size="small" @click="submit()" type="primary" secondary>Submit</n-button>
52 + <n-button size="small" secondary @click="close()">Close</n-button>
53 + <n-button size="small" type="primary" secondary @click="submit()">Submit</n-button>
54 </div>
55 </div>
56 </template>
57
58 <script setup lang="ts">
59 -import { ref, onBeforeMount, computed, watch, toRefs } from "vue"
60 -import { NButton, NSelect, NInputGroup, NInputNumber, NInput } from "naive-ui"
61 -import _cloneDeep from "lodash/cloneDeep"
62 -import _toSafeInteger from "lodash/toSafeInteger"
59 +import type { AuthUser } from "@/types/auth.d"
60 import Api from "@/api"
61 import {
62 + LogEventType,
63 type LogsQueryEventType,
64 type LogsQueryTimeRange,
65 type LogsQueryTypes,
68 - type LogsQueryValues,
69 - LogEventType
66 + type LogsQueryValues
67 } from "@/types/logs.d"
71 -import type { AuthUser } from "@/types/auth.d"
68 +import _cloneDeep from "lodash/cloneDeep"
69 +import _toSafeInteger from "lodash/toSafeInteger"
70 +import { NButton, NInput, NInputGroup, NInputNumber, NSelect } from "naive-ui"
71 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
72 +
73 +const props = defineProps<{ users?: AuthUser[]; fetchingUsers?: boolean }>()
74
75 const emit = defineEmits<{
76 (e: "close"): void
@@ -79,7 +81,6 @@ const emit = defineEmits<{
81 const type = defineModel<LogsQueryTypes | null>("type", { default: null })
82 const value = defineModel<LogsQueryValues | null>("value", { default: null })
83
82 -const props = defineProps<{ users?: AuthUser[]; fetchingUsers?: boolean }>()
84 const { users, fetchingUsers } = toRefs(props)
85
86 const loadingUsers = ref(false)
@@ -171,7 +172,7 @@ function getUsers() {
172 function setUsers(users: AuthUser[]) {
173 userIdOptions.value = (users || []).map(o => ({
174 label: `#${o.id} - ${o.username}`,
174 - value: o.id + ""
175 + value: `${o.id}`
176 }))
177 }
178
frontend/src/components/logs/LogsList.vue
+16 -16
@@ -1,6 +1,6 @@
1 <template>
2 <div class="logs-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -28,7 +28,7 @@
28 </div>
29 </n-popover>
30
31 - <n-button size="small" type="error" ghost @click="showPurgeConfirm = true" :loading="loadingPurge">
31 + <n-button size="small" type="error" ghost :loading="loadingPurge" @click="showPurgeConfirm = true">
32 <div class="flex items-center gap-2">
33 <Icon :name="TrashIcon" :size="16"></Icon>
34 <span class="hidden xs:block">Purge</span>
@@ -61,7 +61,7 @@
61 v-model:value="filterValue"
62 v-model:filtered="filtered"
63 :users="usersList"
64 - :loadingUsers="loadingUsers"
64 + :loading-users="loadingUsers"
65 @submit="getData()"
66 @close="showFilters = false"
67 />
@@ -79,17 +79,17 @@
79 />
80 </template>
81 <template v-else>
82 - <n-empty description="No Logs found" class="justify-center h-48" v-if="!loading" />
82 + <n-empty v-if="!loading" description="No Logs found" class="justify-center h-48" />
83 </template>
84 </div>
85 </n-spin>
86 <div class="footer flex justify-end">
87 <n-pagination
88 + v-if="itemsPaginated.length > 3"
89 v-model:page="currentPage"
90 :page-size="pageSize"
91 :item-count="total"
92 :page-slot="6"
92 - v-if="itemsPaginated.length > 3"
93 />
94 </div>
95
@@ -106,7 +106,7 @@
106 <template #action>
107 <div class="flex gap-3">
108 <n-button size="small" ghost @click="showPurgeConfirm = false">Cancel</n-button>
109 - <n-button size="small" type="warning" @click="purge()" :loading="loadingPurge">
109 + <n-button size="small" type="warning" :loading="loadingPurge" @click="purge()">
110 Yes I'm sure
111 </n-button>
112 </div>
@@ -116,24 +116,24 @@
116 </template>
117
118 <script setup lang="ts">
119 -import { ref, onBeforeMount, computed, toRefs } from "vue"
120 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination, NBadge, NModal, NSelect } from "naive-ui"
119 +import type { AuthUser } from "@/types/auth.d"
120 import Api from "@/api"
122 -import _orderBy from "lodash/orderBy"
121 import Icon from "@/components/common/Icon.vue"
124 -import { nanoid } from "nanoid"
125 -import { useResizeObserver } from "@vueuse/core"
126 -import LogsFilters from "./LogsFilters.vue"
127 -import LogItem from "./LogItem.vue"
122 import {
123 type Log,
124 + LogEventType,
125 type LogsQuery,
126 type LogsQueryTimeRange,
127 type LogsQueryTypes,
133 - type LogsQueryValues,
134 - LogEventType
128 + type LogsQueryValues
129 } from "@/types/logs.d"
136 -import type { AuthUser } from "@/types/auth.d"
130 +import { useResizeObserver } from "@vueuse/core"
131 +import _orderBy from "lodash/orderBy"
132 +import { NBadge, NButton, NEmpty, NModal, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
133 +import { nanoid } from "nanoid"
134 +import { computed, onBeforeMount, ref, toRefs } from "vue"
135 +import LogItem from "./LogItem.vue"
136 +import LogsFilters from "./LogsFilters.vue"
137
138 interface LogExt extends Log {
139 id?: string
frontend/src/components/monitoringAlerts/Item.vue
+18 -14
@@ -6,7 +6,9 @@
6 </div>
7 <div class="main-box flex items-center justify-between gap-4">
8 <div class="content">
9 - <div class="title">{{ alert.alert_id }}</div>
9 + <div class="title">
10 + {{ alert.alert_id }}
11 + </div>
12
13 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
14 <Badge type="active" class="cursor-pointer" @click="gotoIndex(alert.alert_index)">
@@ -29,7 +31,9 @@
31
32 <Badge type="splitted" color="primary">
33 <template #label>Source</template>
32 - <template #value>{{ alert.alert_source }}</template>
34 + <template #value>
35 + {{ alert.alert_source }}
36 + </template>
37 </Badge>
38 </div>
39 </div>
@@ -39,10 +43,10 @@
43 :alert="alert"
44 @deleted="emit('deleted')"
45 @invoked="emit('invoked')"
42 - @startDeleting="loadingDelete = true"
43 - @stopDeleting="loadingDelete = false"
44 - @startInvoking="loadingInvoke = true"
45 - @stopInvoking="loadingInvoke = false"
46 + @start-deleting="loadingDelete = true"
47 + @stop-deleting="loadingDelete = false"
48 + @start-invoking="loadingInvoke = true"
49 + @stop-invoking="loadingInvoke = false"
50 />
51 </div>
52 <div class="footer-box flex justify-between items-center gap-3">
@@ -53,10 +57,10 @@
57 inline
58 @deleted="emit('deleted')"
59 @invoked="emit('invoked')"
56 - @startDeleting="loadingDelete = true"
57 - @stopDeleting="loadingDelete = false"
58 - @startInvoking="loadingInvoke = true"
59 - @stopInvoking="loadingInvoke = false"
60 + @start-deleting="loadingDelete = true"
61 + @stop-deleting="loadingDelete = false"
62 + @start-invoking="loadingInvoke = true"
63 + @stop-invoking="loadingInvoke = false"
64 />
65 </div>
66 </div>
@@ -64,13 +68,13 @@
68 </template>
69
70 <script setup lang="ts">
67 -import Icon from "@/components/common/Icon.vue"
71 +import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
72 import Badge from "@/components/common/Badge.vue"
73 +import Icon from "@/components/common/Icon.vue"
74 +import { useGoto } from "@/composables/useGoto"
75 +import { NSpin } from "naive-ui"
76 import { computed, ref } from "vue"
77 import AlertActions from "./ItemActions.vue"
71 -import { NSpin } from "naive-ui"
72 -import { useGoto } from "@/composables/useGoto"
73 -import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
78
79 const { alert, embedded } = defineProps<{
80 alert: MonitoringAlert
frontend/src/components/monitoringAlerts/ItemActions.vue
+17 -13
@@ -1,23 +1,33 @@
1 <template>
2 <div class="alert-actions flex flex-col gap-3 justify-center" :class="{ '!flex-row': inline }">
3 <n-button :loading="loadingInvoke" type="success" secondary :size="size" @click="invoke()">
4 - <template #icon><Icon :name="InvokeIcon"></Icon></template>
4 + <template #icon>
5 + <Icon :name="InvokeIcon"></Icon>
6 + </template>
7 Invoke
8 </n-button>
9 <n-button :loading="loadingDelete" :size="size" type="error" secondary @click="handleDelete()">
8 - <template #icon><Icon :name="DeleteIcon"></Icon></template>
10 + <template #icon>
11 + <Icon :name="DeleteIcon"></Icon>
12 + </template>
13 Delete
14 </n-button>
15 </div>
16 </template>
17
18 <script setup lang="ts">
15 -import { NButton, useDialog, useMessage } from "naive-ui"
16 -import Icon from "@/components/common/Icon.vue"
17 -import Api from "@/api"
18 -import { computed, watch, ref } from "vue"
19 -import type { Size } from "naive-ui/es/button/src/interface"
19 import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
20 +import type { Size } from "naive-ui/es/button/src/interface"
21 +import Api from "@/api"
22 +import Icon from "@/components/common/Icon.vue"
23 +import { NButton, useDialog, useMessage } from "naive-ui"
24 +import { computed, ref, watch } from "vue"
25 +
26 +const { alert, size, inline } = defineProps<{
27 + alert: MonitoringAlert
28 + size?: Size
29 + inline?: boolean
30 +}>()
31
32 const emit = defineEmits<{
33 (e: "startInvoking"): void
@@ -30,12 +40,6 @@ const emit = defineEmits<{
40 (e: "deleted"): void
41 }>()
42
33 -const { alert, size, inline } = defineProps<{
34 - alert: MonitoringAlert
35 - size?: Size
36 - inline?: boolean
37 -}>()
38 -
43 const DeleteIcon = "ph:trash"
44 const InvokeIcon = "carbon:play"
45
frontend/src/components/monitoringAlerts/List.vue
+9 -9
@@ -1,6 +1,6 @@
1 <template>
2 <div class="monitoring-alerts-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -21,12 +21,12 @@
21 </n-popover>
22
23 <n-button
24 + v-if="monitoringAlerts.length"
25 size="small"
26 type="error"
27 ghost
27 - @click="handlePurge()"
28 :loading="loadingPurge"
29 - v-if="monitoringAlerts.length"
29 + @click="handlePurge()"
30 >
31 <div class="flex items-center gap-2">
32 <Icon :name="TrashIcon" :size="16"></Icon>
@@ -51,36 +51,36 @@
51 v-for="alert of itemsPaginated"
52 :key="alert.id"
53 :alert="alert"
54 + class="item-appear item-appear-bottom item-appear-005"
55 @deleted="getData()"
56 @invoked="getData()"
56 - class="item-appear item-appear-bottom item-appear-005"
57 />
58 </template>
59 <template v-else>
60 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
60 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
61 </template>
62 </div>
63 </n-spin>
64 <div class="footer flex justify-end">
65 <n-pagination
66 + v-if="itemsPaginated.length > 3"
67 v-model:page="currentPage"
68 :page-size="pageSize"
69 :item-count="total"
70 :page-slot="6"
70 - v-if="itemsPaginated.length > 3"
71 />
72 </div>
73 </div>
74 </template>
75
76 <script setup lang="ts">
77 -import { ref, onBeforeMount, computed } from "vue"
78 -import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination, useDialog } from "naive-ui"
77 +import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
78 import Api from "@/api"
79 import Icon from "@/components/common/Icon.vue"
80 import { useResizeObserver } from "@vueuse/core"
81 +import { NButton, NEmpty, NPagination, NPopover, NSpin, useDialog, useMessage } from "naive-ui"
82 +import { computed, onBeforeMount, ref } from "vue"
83 import Alert from "./Item.vue"
83 -import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
84
85 const dialog = useDialog()
86 const message = useMessage()
frontend/src/components/networkConnectors/NetworkConnectorsList.vue
+6 -6
@@ -1,22 +1,22 @@
1 <template>
2 <ServicesList
3 + v-model:selected="selected"
4 type="network-connector"
5 :embedded="embedded"
5 - :hideTotals="hideTotals"
6 + :hide-totals="hideTotals"
7 :selectable="selectable"
7 - :disabledIdsList="disabledIdsList"
8 + :disabled-ids-list="disabledIdsList"
9 :loading="loading"
10 :list="list"
10 - v-model:selected="selected"
11 />
12 </template>
13
14 <script setup lang="ts">
15 -import { ref, onBeforeMount } from "vue"
16 -import { useMessage } from "naive-ui"
15 +import type { ServiceItemData } from "../services/types"
16 import Api from "@/api"
17 import ServicesList from "@/components/services/List.vue"
19 -import type { ServiceItemData } from "../services/types"
18 +import { useMessage } from "naive-ui"
19 +import { onBeforeMount, ref } from "vue"
20
21 const { embedded, hideTotals, selectable, disabledIdsList } = defineProps<{
22 embedded?: boolean
frontend/src/components/overview/AgentsCard.vue
+10 -10
@@ -2,30 +2,30 @@
2 <n-spin :show="loading">
3 <CardStatsDouble
4 title="Agents"
5 - firstLabel="Total"
5 + first-label="Total"
6 hovered
7 class="cursor-pointer"
8 - @click="gotoAgent()"
8 :value="total"
10 - secondLabel="Online"
11 - :subValue="onlineTotal"
12 - :secondStatus="onlineTotal ? 'success' : undefined"
9 + second-label="Online"
10 + :sub-value="onlineTotal"
11 + :second-status="onlineTotal ? 'success' : undefined"
12 + @click="gotoAgent()"
13 >
14 <template #icon>
15 - <CardStatsIcon :iconName="AgentsIcon" boxed :boxSize="30"></CardStatsIcon>
15 + <CardStatsIcon :icon-name="AgentsIcon" boxed :box-size="30"></CardStatsIcon>
16 </template>
17 </CardStatsDouble>
18 </n-spin>
19 </template>
20
21 <script setup lang="ts">
22 -import { computed, onBeforeMount, ref } from "vue"
23 -import { AgentStatus, type Agent } from "@/types/agents.d"
22 +import Api from "@/api"
23 import CardStatsDouble from "@/components/common/CardStatsDouble.vue"
24 import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
26 -import Api from "@/api"
27 -import { useMessage, NSpin } from "naive-ui"
25 import { useGoto } from "@/composables/useGoto"
26 +import { type Agent, AgentStatus } from "@/types/agents.d"
27 +import { NSpin, useMessage } from "naive-ui"
28 +import { computed, onBeforeMount, ref } from "vue"
29
30 const AgentsIcon = "carbon:network-3"
31 const { gotoAgent } = useGoto()
frontend/src/components/overview/CustomersCard.vue
+6 -6
@@ -9,20 +9,20 @@
9 @click="gotoCustomer()"
10 >
11 <template #icon>
12 - <CardStatsIcon :iconName="CustomersIcon" boxed :boxSize="40"></CardStatsIcon>
12 + <CardStatsIcon :icon-name="CustomersIcon" boxed :box-size="40"></CardStatsIcon>
13 </template>
14 </CardStats>
15 </n-spin>
16 </template>
17
18 <script setup lang="ts">
19 -import { computed, onBeforeMount, ref, toRefs } from "vue"
20 -import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
21 -import CardStats from "@/components/common/CardStats.vue"
22 -import Api from "@/api"
23 -import { useMessage, NSpin } from "naive-ui"
19 import type { Customer } from "@/types/customers.d"
20 +import Api from "@/api"
21 +import CardStats from "@/components/common/CardStats.vue"
22 +import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
23 import { useGoto } from "@/composables/useGoto"
24 +import { NSpin, useMessage } from "naive-ui"
25 +import { computed, onBeforeMount, ref, toRefs } from "vue"
26
27 const props = defineProps<{
28 vertical?: boolean
frontend/src/components/overview/HealthcheckCard.vue
+11 -11
@@ -2,20 +2,20 @@
2 <n-spin :show="loading">
3 <CardStatsDouble
4 title="Healthcheck"
5 - firstLabel="Total"
5 + first-label="Total"
6 :value="total"
7 hovered
8 class="cursor-pointer"
9 + second-label="Critical"
10 + :sub-value="criticalTotal"
11 + :second-status="criticalTotal ? 'warning' : undefined"
12 @click="gotoHealthcheck()"
10 - secondLabel="Critical"
11 - :subValue="criticalTotal"
12 - :secondStatus="criticalTotal ? 'warning' : undefined"
13 >
14 <template #icon>
15 <CardStatsIcon
16 - :iconName="HealthcheckIcon"
16 + :icon-name="HealthcheckIcon"
17 boxed
18 - :boxSize="30"
18 + :box-size="30"
19 :color="criticalTotal ? style['warning-color'] : undefined"
20 ></CardStatsIcon>
21 </template>
@@ -24,14 +24,14 @@
24 </template>
25
26 <script setup lang="ts">
27 -import { computed, onBeforeMount, ref } from "vue"
27 +import Api from "@/api"
28 import CardStatsDouble from "@/components/common/CardStatsDouble.vue"
29 import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
30 -import Api from "@/api"
31 -import { useMessage, NSpin } from "naive-ui"
32 -import { InfluxDBAlertLevel, type InfluxDBAlert } from "@/types/healthchecks.d"
33 -import { useThemeStore } from "@/stores/theme"
30 import { useGoto } from "@/composables/useGoto"
31 +import { useThemeStore } from "@/stores/theme"
32 +import { type InfluxDBAlert, InfluxDBAlertLevel } from "@/types/healthchecks.d"
33 +import { NSpin, useMessage } from "naive-ui"
34 +import { computed, onBeforeMount, ref } from "vue"
35
36 const HealthcheckIcon = "ph:heartbeat"
37 const { gotoHealthcheck } = useGoto()
frontend/src/components/overview/SocAlertsCard.vue
+6 -6
@@ -9,20 +9,20 @@
9 @click="gotoSocAlerts()"
10 >
11 <template #icon>
12 - <CardStatsIcon :iconName="SOCIcon" boxed :boxSize="40"></CardStatsIcon>
12 + <CardStatsIcon :icon-name="SOCIcon" boxed :box-size="40"></CardStatsIcon>
13 </template>
14 </CardStats>
15 </n-spin>
16 </template>
17
18 <script setup lang="ts">
19 -import { computed, onBeforeMount, ref, toRefs } from "vue"
20 -import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
21 -import CardStats from "@/components/common/CardStats.vue"
22 -import Api from "@/api"
23 -import { useMessage, NSpin } from "naive-ui"
19 import type { SocAlert } from "@/types/soc/alert.d"
20 +import Api from "@/api"
21 +import CardStats from "@/components/common/CardStats.vue"
22 +import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
23 import { useGoto } from "@/composables/useGoto"
24 +import { NSpin, useMessage } from "naive-ui"
25 +import { computed, onBeforeMount, ref, toRefs } from "vue"
26
27 const props = defineProps<{
28 vertical?: boolean
frontend/src/components/profile/ProfileSettings.vue
+9 -9
@@ -26,22 +26,22 @@
26 </template>
27
28 <script setup lang="ts">
29 -import { ref } from "vue"
29 +import { useSettingsStore } from "@/stores/settings"
30 +import dayjs from "@/utils/dayjs"
31 import {
31 - NSpin,
32 + type FormInst,
33 + type FormValidationError,
34 + NButton,
35 NCard,
36 NForm,
37 NFormItem,
35 - NButton,
36 - NSelect,
38 NRadio,
39 NRadioGroup,
39 - type FormValidationError,
40 - useMessage,
41 - type FormInst
40 + NSelect,
41 + NSpin,
42 + useMessage
43 } from "naive-ui"
43 -import { useSettingsStore } from "@/stores/settings"
44 -import dayjs from "@/utils/dayjs"
44 +import { ref } from "vue"
45
46 const settingsStore = useSettingsStore()
47
frontend/src/components/reportCreation/Panels.vue
+32 -26
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin v-model:show="loading" class="overflow-hidden h-full w-full" content-class="overflow-hidden h-full w-full">
3 - <div class="report-panels h-full w-full flex gap-2" v-if="org">
3 + <div v-if="org" class="report-panels h-full w-full flex gap-2">
4 <div class="panels-container grow h-full flex flex-col gap-4">
5 <div class="rows-container">
6 <n-scrollbar style="max-height: 100%" trigger="none">
@@ -16,7 +16,7 @@
16 >
17 <template #item="{ element: row }">
18 <div class="row p-3" :class="{ 'height-large': row.height === 2 }">
19 - <div class="empty-message" v-if="!row.panels.length">Drop panels here</div>
19 + <div v-if="!row.panels.length" class="empty-message">Drop panels here</div>
20 <draggable
21 v-model="row.panels"
22 item-key="id"
@@ -39,7 +39,7 @@
39 <div class="delete-box">
40 <n-tooltip trigger="hover">
41 <template #trigger>
42 - <n-button text @click="removeRow(row)" type="error">
42 + <n-button text type="error" @click="removeRow(row)">
43 <template #icon>
44 <Icon :name="CloseIcon"></Icon>
45 </template>
@@ -63,8 +63,8 @@
63 Row height:
64 </div>
65 <n-switch
66 - size="small"
66 v-model:value="row.height"
67 + size="small"
68 :unchecked-value="1"
69 :checked-value="2"
70 >
@@ -87,8 +87,8 @@
87 <template #trigger>
88 <n-button
89 text
90 - @click="removePanel(row, panel)"
90 type="error"
91 + @click="removePanel(row, panel)"
92 >
93 <template #icon>
94 <Icon :name="CloseIcon"></Icon>
@@ -115,9 +115,9 @@
115 </div>
116 <div class="toolbar pr-4 flex items-center justify-between gap-2">
117 <n-button
118 + v-if="dashboard || panelsReady"
119 class="add-task-btn flex items-center justify-center !mt-0"
120 @click="addRow()"
120 - v-if="dashboard || panelsReady"
121 >
122 <template #icon>
123 <Icon :name="AddIcon"></Icon>
@@ -125,14 +125,14 @@
125 <span>Add row</span>
126 </n-button>
127
128 - <div class="flex items-center gap-2" v-if="panelsReady">
129 - <n-button type="success" @click="print()" :loading="loading">
128 + <div v-if="panelsReady" class="flex items-center gap-2">
129 + <n-button type="success" :loading="loading" @click="print()">
130 <template #icon>
131 <Icon :name="PrintIcon"></Icon>
132 </template>
133 Print Report
134 </n-button>
135 - <n-button type="success" @click="openSettings()" :loading="loading">
135 + <n-button type="success" :loading="loading" @click="openSettings()">
136 <template #icon>
137 <Icon :name="SettingsIcon"></Icon>
138 </template>
@@ -142,7 +142,7 @@
142 </div>
143
144 <div class="panels-sidebar h-full">
145 - <n-scrollbar style="max-height: 100%" trigger="none" v-if="dashboard && panelsList.length">
145 + <n-scrollbar v-if="dashboard && panelsList.length" style="max-height: 100%" trigger="none">
146 <div class="p-3">
147 <draggable
148 class="flex flex-col gap-3"
@@ -198,19 +198,27 @@
198 </template>
199
200 <script setup lang="ts">
201 -import { ref, computed, toRefs, watch, onMounted } from "vue"
202 -import { NButton, NSpin, NScrollbar, NTooltip, NDrawer, NDrawerContent, NPopover, NSwitch, useMessage } from "naive-ui"
201 +import type { GenerateReportPayload, ReportTimeRange } from "@/api/endpoints/reporting"
202 import type { Dashboard, Org, Panel } from "@/types/reporting.d"
204 -import Icon from "@/components/common/Icon.vue"
205 -import PrintSettings, { type PrintSettingsData } from "./PrintSettings.vue"
206 -import draggable from "vuedraggable"
203 import Api from "@/api"
208 -import type { GenerateReportPayload, ReportTimeRange } from "@/api/endpoints/reporting"
209 -import { saveAs } from "file-saver"
204 +import Icon from "@/components/common/Icon.vue"
205 +import { useSettingsStore } from "@/stores/settings"
206 +import { formatDate } from "@/utils"
207 import { useStorage } from "@vueuse/core"
208 +import { saveAs } from "file-saver"
209 import _kebabCase from "lodash/kebabCase"
210 +import { NButton, NDrawer, NDrawerContent, NPopover, NScrollbar, NSpin, NSwitch, NTooltip, useMessage } from "naive-ui"
211 +import { computed, onMounted, ref, toRefs, watch } from "vue"
212 +import draggable from "vuedraggable"
213 import * as defaultSettings from "./defaultSettings"
214 +import PrintSettings, { type PrintSettingsData } from "./PrintSettings.vue"
215
216 +const props = defineProps<{
217 + timerange: ReportTimeRange | null
218 + org: Org | null
219 + dashboard: Dashboard | null
220 + panels: Panel[]
221 +}>()
222 const ROW_GAP = 20
223 const ROW_WIDTH = 800
224 const ROW_HEIGHT = 320
@@ -235,12 +243,6 @@ interface PanelData {
243 dashboardTitle: string
244 }
245
238 -const props = defineProps<{
239 - timerange: ReportTimeRange | null
240 - org: Org | null
241 - dashboard: Dashboard | null
242 - panels: Panel[]
243 -}>()
246 const { timerange, org, dashboard, panels } = toRefs(props)
247
248 const PanIcon = "carbon:draggable"
@@ -249,6 +251,7 @@ const SettingsIcon = "carbon:settings"
251 const AddIcon = "carbon:add-alt"
252 const PrintIcon = "carbon:printer"
253 const RowSettingsIcon = "carbon:fit-to-height"
254 +const dFormats = useSettingsStore().dateFormat
255 const message = useMessage()
256 const loadingPrint = ref(false)
257 const loading = computed(() => loadingPrint.value)
@@ -353,7 +356,7 @@ function print() {
356
357 loadingPrint.value = true
358
356 - const timeValue = parseInt(timerange.value.match(/\d+/)?.[0] || "1")
359 + const timeValue = Number.parseInt(timerange.value.match(/\d+/)?.[0] || "1")
360 const timeUnit = (timerange.value.match(/[a-z]/i)?.[0] || "h").toLocaleLowerCase()
361 const timerangeText = `Last ${timeValue} ${timeUnit === "d" ? "Day" : timeUnit === "h" ? "Hour" : "minute"}${
362 timeValue > 1 ? "s" : ""
@@ -389,13 +392,16 @@ function print() {
392 }
393 }
394
392 - const reportFileName = `report${org.value?.name ? "-" + _kebabCase(org.value.name) : ""}.pdf`
395 + const reportFileName = `report${org.value?.name ? `:${_kebabCase(org.value.name)}` : ""}_${formatDate(
396 + new Date(),
397 + dFormats.datetimesec
398 + )}.pdf`
399
400 Api.reporting
401 .generateReport(payload)
402 .then(res => {
403 if (res.data.success) {
398 - const dataUri = "data:application/pdf;base64," + res.data.base64_result
404 + const dataUri = `data:application/pdf;base64,${res.data.base64_result}`
405 saveAs(dataUri, reportFileName)
406 } else {
407 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/reportCreation/PrintSettings.vue
+4 -4
@@ -4,7 +4,7 @@
4 <n-avatar :size="110" :src="logo" />
5
6 <div class="flex flex-col justify-between grow">
7 - <ImageCropper v-slot="{ openCropper }" @crop="setCroppedImage" :placeholder="'Select a Logo'">
7 + <ImageCropper v-slot="{ openCropper }" placeholder="Select a Logo" @crop="setCroppedImage">
8 <n-button @click="openCropper()">
9 <template #icon>
10 <Icon :name="EditIcon"></Icon>
@@ -37,11 +37,11 @@
37 </template>
38
39 <script setup lang="ts">
40 -import { watch, onMounted } from "vue"
41 -import { NAvatar, NButton, NInput, NFormItem, NRadioGroup, NRadioButton, NSwitch } from "naive-ui"
42 -import ImageCropper, { type ImageCropperResult } from "@/components/common/ImageCropper.vue"
40 import Icon from "@/components/common/Icon.vue"
41 +import ImageCropper, { type ImageCropperResult } from "@/components/common/ImageCropper.vue"
42 import { useStorage } from "@vueuse/core"
43 +import { NAvatar, NButton, NFormItem, NInput, NRadioButton, NRadioGroup, NSwitch } from "naive-ui"
44 +import { onMounted, watch } from "vue"
45 import * as defaultSettings from "./defaultSettings"
46
47 export interface PrintSettingsData {
frontend/src/components/reportCreation/Wizard.vue
+11 -10
@@ -24,7 +24,7 @@
24 />
25 </n-form-item>
26
27 - <n-form-item label="Dashboard" v-if="canSelectDashboard" class="flex flex-grow">
27 + <n-form-item v-if="canSelectDashboard" label="Dashboard" class="flex flex-grow">
28 <n-select
29 v-model:value="selectedDashboardUID"
30 :options="dashboardsOptions"
@@ -33,8 +33,8 @@
33 />
34 </n-form-item>
35 </div>
36 - <div class="flex" v-if="!hidePanelsSelect">
37 - <n-form-item label="Panels" v-if="canSelectPanels" class="grow">
36 + <div v-if="!hidePanelsSelect" class="flex">
37 + <n-form-item v-if="canSelectPanels" label="Panels" class="grow">
38 <n-select
39 v-model:value="selectedPanelsIds"
40 :options="panelsOptions"
@@ -50,12 +50,16 @@
50 </template>
51
52 <script setup lang="ts">
53 -import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
54 -import { NSpin, NForm, NFormItem, NInputGroup, NInputNumber, NSelect, useMessage } from "naive-ui"
55 -import Api from "@/api"
56 -import type { Dashboard, Org, Panel } from "@/types/reporting.d"
53 import type { ReportTimeRange, RowPanelTimeUnit } from "@/api/endpoints/reporting"
54 +import type { Dashboard, Org, Panel } from "@/types/reporting.d"
55 +import Api from "@/api"
56 import { useStorage } from "@vueuse/core"
57 +import { NForm, NFormItem, NInputGroup, NInputNumber, NSelect, NSpin, useMessage } from "naive-ui"
58 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
59 +
60 +const props = defineProps<{
61 + hidePanelsSelect?: boolean
62 +}>()
63
64 const emit = defineEmits<{
65 (e: "selected", value: Panel[]): void
@@ -65,9 +69,6 @@ const emit = defineEmits<{
69 (e: "timerange", value: ReportTimeRange): void
70 }>()
71
68 -const props = defineProps<{
69 - hidePanelsSelect?: boolean
70 -}>()
72 const { hidePanelsSelect } = toRefs(props)
73
74 const message = useMessage()
frontend/src/components/scheduler/Item.vue
+16 -8
@@ -1,7 +1,9 @@
1 <template>
2 <div class="item flex flex-col gap-4 px-5 py-3">
3 <div class="header-box flex justify-between gap-4">
4 - <div class="name">{{ job.id }}</div>
4 + <div class="name">
5 + {{ job.id }}
6 + </div>
7 <div class="time flex items-center gap-2">
8 {{ formatDate(job.last_success, dFormats.datetimesec) }}
9
@@ -15,8 +17,12 @@
17 </div>
18 <div class="main-box flex justify-between gap-4 items-center">
19 <div class="content">
18 - <div class="title">{{ job.name }}</div>
19 - <div class="description mt-1">{{ job.description }}</div>
20 + <div class="title">
21 + {{ job.name }}
22 + </div>
23 + <div class="description mt-1">
24 + {{ job.description }}
25 + </div>
26 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
27 <Badge type="splitted" color="primary">
28 <template #label>Interval</template>
@@ -33,18 +39,20 @@
39 </div>
40 <div class="footer-box flex flex-col gap-4">
41 <JobActions :job="job" size="small" inline />
36 - <div class="time w-full text-right">{{ formatDate(job.last_success, dFormats.datetimesec) }}</div>
42 + <div class="time w-full text-right">
43 + {{ formatDate(job.last_success, dFormats.datetimesec) }}
44 + </div>
45 </div>
46 </div>
47 </template>
48
49 <script setup lang="ts">
42 -import Icon from "@/components/common/Icon.vue"
43 -import Badge from "@/components/common/Badge.vue"
44 -import { NTooltip } from "naive-ui"
50 import type { Job } from "@/types/scheduler.d"
46 -import { formatDate } from "@/utils"
51 +import Badge from "@/components/common/Badge.vue"
52 +import Icon from "@/components/common/Icon.vue"
53 import { useSettingsStore } from "@/stores/settings"
54 +import { formatDate } from "@/utils"
55 +import { NTooltip } from "naive-ui"
56 import JobActions from "./JobActions.vue"
57
58 const { job } = defineProps<{ job: Job }>()
frontend/src/components/scheduler/JobActions.vue
+12 -12
@@ -5,9 +5,9 @@
5 :size="size"
6 :type="job.enabled ? 'warning' : 'success'"
7 secondary
8 - @click="toggleState()"
8 :loading="loadingAction"
9 class="grow"
10 + @click="toggleState()"
11 >
12 <template #icon>
13 <Icon :name="job.enabled ? PauseIcon : StartIcon"></Icon>
@@ -15,22 +15,22 @@
15 {{ job.enabled ? "Pause" : "Start" }}
16 </n-button>
17
18 - <NextTooltip :job-id="job.id" v-if="job.enabled && !inline" />
18 + <NextTooltip v-if="job.enabled && !inline" :job-id="job.id" />
19 </div>
20 <div class="flex gap-3 items-center">
21 - <n-button :size="size" type="success" secondary @click="run()" :loading="loadingRun">
21 + <n-button :size="size" type="success" secondary :loading="loadingRun" @click="run()">
22 <template #icon>
23 <Icon :name="RunIcon"></Icon>
24 </template>
25 Run once
26 </n-button>
27 - <n-button :size="size" secondary @click="showForm = true" :loading="loadingUpdate">
27 + <n-button :size="size" secondary :loading="loadingUpdate" @click="showForm = true">
28 <template #icon>
29 <Icon :name="UpdatedIcon"></Icon>
30 </template>
31 </n-button>
32
33 - <NextTooltip :job-id="job.id" v-if="job.enabled && inline" />
33 + <NextTooltip v-if="job.enabled && inline" :job-id="job.id" />
34 </div>
35 </div>
36
@@ -43,20 +43,20 @@
43 :bordered="false"
44 segmented
45 >
46 - <JobForm @updated="update($event)" :job="job" />
46 + <JobForm :job="job" @updated="update($event)" />
47 </n-modal>
48 </template>
49
50 <script setup lang="ts">
51 -import { ref, toRefs } from "vue"
52 -import { NButton, NModal, useMessage } from "naive-ui"
53 -import Icon from "@/components/common/Icon.vue"
54 -import JobForm from "./JobForm.vue"
55 -import NextTooltip from "./NextTooltip.vue"
51 +import type { UpdateJobPayload } from "@/api/endpoints/scheduler"
52 import type { Job } from "@/types/scheduler.d"
53 import type { Size } from "naive-ui/es/button/src/interface"
54 import Api from "@/api"
59 -import type { UpdateJobPayload } from "@/api/endpoints/scheduler"
55 +import Icon from "@/components/common/Icon.vue"
56 +import { NButton, NModal, useMessage } from "naive-ui"
57 +import { ref, toRefs } from "vue"
58 +import JobForm from "./JobForm.vue"
59 +import NextTooltip from "./NextTooltip.vue"
60
61 const props = defineProps<{ job: Job; size?: Size; inline?: boolean }>()
62 const { job, size, inline } = toRefs(props)
frontend/src/components/scheduler/JobForm.vue
+19 -19
@@ -1,11 +1,11 @@
1 <template>
2 <n-spin :show="loading" class="job-form">
3 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-1">
5 <n-form-item label="Time interval (minutes)" path="time_interval" class="grow">
6 <n-input-number
7 - :min="1"
7 v-model:value="form.time_interval"
8 + :min="1"
9 placeholder="Input time in minutes"
10 clearable
11 class="w-full"
@@ -13,12 +13,12 @@
13 </n-form-item>
14
15 <div class="flex gap-3 justify-end items-center">
16 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
16 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
17 <n-button
18 type="primary"
19 :disabled="!isValid"
20 - @click="validate(() => submit())"
20 :loading="submitting"
21 + @click="validate(() => submit())"
22 >
23 Submit
24 </n-button>
@@ -29,33 +29,33 @@
29 </template>
30
31 <script setup lang="ts">
32 -import { computed, ref, toRefs } from "vue"
32 +import type { UpdateJobPayload } from "@/api/endpoints/scheduler"
33 +import type { Job } from "@/types/scheduler.d"
34 import Api from "@/api"
35 +import _get from "lodash/get"
36 +import _trim from "lodash/trim"
37 import {
35 - useMessage,
38 + type FormInst,
39 + type FormItemRule,
40 + type FormRules,
41 + type FormValidationError,
42 + type MessageReactive,
43 + NButton,
44 NForm,
45 NFormItem,
38 - NButton,
39 - NSpin,
46 NInputNumber,
41 - type FormValidationError,
42 - type FormInst,
43 - type FormRules,
44 - type FormItemRule,
45 - type MessageReactive
47 + NSpin,
48 + useMessage
49 } from "naive-ui"
47 -import _trim from "lodash/trim"
48 -import _get from "lodash/get"
49 -import type { UpdateJobPayload } from "@/api/endpoints/scheduler"
50 -import type { Job } from "@/types/scheduler.d"
50 +import { computed, ref, toRefs } from "vue"
51
52 const props = defineProps<{ job: Job }>()
53 -const { job } = toRefs(props)
54 -
53 const emit = defineEmits<{
54 (e: "updated", value: UpdateJobPayload): void
55 }>()
56
57 +const { job } = toRefs(props)
58 +
59 const submitting = ref(false)
60 const loading = computed(() => submitting.value)
61 const message = useMessage()
frontend/src/components/scheduler/List.vue
+4 -4
@@ -6,7 +6,7 @@
6 <JobCard v-for="job of jobs" :key="job.id" :job="job" class="mb-2" />
7 </template>
8 <template v-else>
9 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
9 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
10 </template>
11 </div>
12 </n-spin>
@@ -14,11 +14,11 @@
14 </template>
15
16 <script setup lang="ts">
17 -import { ref, onBeforeMount, computed } from "vue"
18 -import { useMessage, NSpin, NEmpty } from "naive-ui"
17 +import type { Job } from "@/types/scheduler.d"
18 import Api from "@/api"
19 +import { NEmpty, NSpin, useMessage } from "naive-ui"
20 +import { computed, onBeforeMount, ref } from "vue"
21 import JobCard from "./Item.vue"
21 -import type { Job } from "@/types/scheduler.d"
22
23 const message = useMessage()
24 const loadingJobs = ref(false)
frontend/src/components/scheduler/NextTooltip.vue
+6 -6
@@ -1,11 +1,11 @@
1 <template>
2 - <n-tooltip @update:show="getNextRun()" placement="top-end">
2 + <n-tooltip placement="top-end" @update:show="getNextRun()">
3 <template #trigger>
4 <Icon :name="NextIcon"></Icon>
5 </template>
6 <template #header>Next run time:</template>
7 <div>
8 - <n-spin :size="12" v-if="loadingNext" />
8 + <n-spin v-if="loadingNext" :size="12" />
9 <span v-if="!loadingNext">
10 {{ nextRunTime ? formatDate(nextRunTime, dFormats.datetimesec) : "-" }}
11 </span>
@@ -14,12 +14,12 @@
14 </template>
15
16 <script setup lang="ts">
17 -import { ref, toRefs } from "vue"
18 -import { NTooltip, NSpin, useMessage } from "naive-ui"
19 -import Icon from "@/components/common/Icon.vue"
20 -import { formatDate } from "@/utils"
17 import Api from "@/api"
18 +import Icon from "@/components/common/Icon.vue"
19 import { useSettingsStore } from "@/stores/settings"
20 +import { formatDate } from "@/utils"
21 +import { NSpin, NTooltip, useMessage } from "naive-ui"
22 +import { ref, toRefs } from "vue"
23
24 const props = defineProps<{ jobId: string }>()
25 const { jobId } = toRefs(props)
frontend/src/components/services/Item.vue
+15 -9
@@ -3,8 +3,8 @@
3 <div class="px-4 py-3 flex flex-col gap-2">
4 <div class="header-box flex justify-between items-center">
5 <div class="flex items-center gap-2 cursor-pointer">
6 - <div class="check-box mr-2" v-if="selectable">
7 - <n-radio size="large" v-model:checked="checked" />
6 + <div v-if="selectable" class="check-box mr-2">
7 + <n-radio v-model:checked="checked" size="large" />
8 </div>
9 <div class="id">#{{ data.id }}</div>
10 </div>
@@ -18,7 +18,9 @@
18 </div>
19 <div class="main-box flex items-center gap-3">
20 <div class="content flex flex-col gap-1 grow">
21 - <div class="title">{{ data.name }}</div>
21 + <div class="title">
22 + {{ data.name }}
23 + </div>
24 <div class="description">
25 {{ data.description }}
26 </div>
@@ -28,7 +30,9 @@
30 <div class="badges-box flex flex-wrap items-center gap-3 mt-2">
31 <code class="py-1">Auth Keys:</code>
32 <Badge v-for="authKey of data.keys" :key="authKey.auth_key_name">
31 - <template #value>{{ authKey.auth_key_name }}</template>
33 + <template #value>
34 + {{ authKey.auth_key_name }}
35 + </template>
36 </Badge>
37 </div>
38 </div>
@@ -49,12 +53,11 @@
53 </template>
54
55 <script setup lang="ts">
52 -import { defineAsyncComponent, ref, toRefs } from "vue"
53 -import { NModal, NRadio, NButton } from "naive-ui"
54 -import Icon from "@/components/common/Icon.vue"
55 -import Badge from "@/components/common/Badge.vue"
56 import type { ServiceItemData, ServiceItemType } from "./types"
57 -const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
57 +import Badge from "@/components/common/Badge.vue"
58 +import Icon from "@/components/common/Icon.vue"
59 +import { NButton, NModal, NRadio } from "naive-ui"
60 +import { defineAsyncComponent, ref, toRefs } from "vue"
61
62 const props = defineProps<{
63 data: ServiceItemData
@@ -64,6 +67,9 @@ const props = defineProps<{
67 selectable?: boolean
68 disabled?: boolean
69 }>()
70 +
71 +const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
72 +
73 const { data, embedded, checked, selectable, disabled } = toRefs(props)
74
75 const InfoIcon = "carbon:information"
frontend/src/components/services/List.vue
+6 -6
@@ -1,6 +1,6 @@
1 <template>
2 <div class="services-list">
3 - <div class="header mb-4 flex gap-2 justify-between items-center" v-if="!hideTotals">
3 + <div v-if="!hideTotals" class="header mb-4 flex gap-2 justify-between items-center">
4 <div>
5 Total:
6 <strong class="font-mono">{{ total }}</strong>
@@ -18,12 +18,12 @@
18 :selectable="isSelectable(item)"
19 :disabled="isDisabled(item)"
20 :checked="selected?.id === item.id"
21 - @click="setItem(item)"
21 class="item-appear item-appear-bottom item-appear-005 mb-2"
22 + @click="setItem(item)"
23 />
24 </template>
25 <template v-else>
26 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
26 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
27 </template>
28 </div>
29 </n-spin>
@@ -31,10 +31,10 @@
31 </template>
32
33 <script setup lang="ts">
34 -import { toRefs, computed } from "vue"
35 -import { NSpin, NEmpty } from "naive-ui"
36 -import ServiceItem from "./Item.vue"
34 import type { ServiceItemData, ServiceItemType } from "./types"
35 +import { NEmpty, NSpin } from "naive-ui"
36 +import { computed, toRefs } from "vue"
37 +import ServiceItem from "./Item.vue"
38
39 const props = defineProps<{
40 type: ServiceItemType
frontend/src/components/sigma/QueriesActions.vue
+1 -1
@@ -48,13 +48,13 @@
48 </template>
49
50 <script setup lang="ts">
51 +import Icon from "@/components/common/Icon.vue"
52 import { NButton } from "naive-ui"
53 import QueryActiveAllForm from "./actionsProviders/QueryActiveAllForm.vue"
54 import QueryDeleteAll from "./actionsProviders/QueryDeleteAll.vue"
55 import QueryDownloadAll from "./actionsProviders/QueryDownloadAll.vue"
56 import QueryUploadDB from "./actionsProviders/QueryUploadDB.vue"
57 import QueryUploadFile from "./actionsProviders/QueryUploadFile.vue"
57 -import Icon from "@/components/common/Icon.vue"
58
59 const emit = defineEmits<{
60 (e: "updated"): void
frontend/src/components/sigma/QueriesList.vue
+13 -13
@@ -1,6 +1,6 @@
1 <template>
2 <div class="sigma-queries-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -73,11 +73,11 @@
73 </div>
74 <div class="px-3 flex justify-between gap-2">
75 <div class="flex justify-start gap-2">
76 - <n-button size="small" @click="showFilters = false" quaternary>Close</n-button>
76 + <n-button size="small" quaternary @click="showFilters = false">Close</n-button>
77 </div>
78 <div class="flex justify-end gap-2">
79 - <n-button size="small" @click="resetFilters()" secondary>Reset</n-button>
80 - <n-button size="small" @click="getData()" type="primary" secondary :loading>
79 + <n-button size="small" secondary @click="resetFilters()">Reset</n-button>
80 + <n-button size="small" type="primary" secondary :loading @click="getData()">
81 Submit
82 </n-button>
83 </div>
@@ -99,40 +99,40 @@
99 v-for="query of itemsPaginated"
100 :key="query.id"
101 :query="query"
102 + class="item-appear item-appear-bottom item-appear-005"
103 @deleted="deleteQueryItem"
104 @updated="updateQueryItem"
104 - class="item-appear item-appear-bottom item-appear-005"
105 />
106 </template>
107 <template v-else>
108 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
108 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
109 </template>
110 </div>
111 </n-spin>
112
113 <div class="footer flex justify-end">
114 <n-pagination
115 + v-if="itemsPaginated.length > 3"
116 v-model:page="currentPage"
117 :page-size="pageSize"
118 :item-count="total"
119 :page-slot="6"
119 - v-if="itemsPaginated.length > 3"
120 />
121 </div>
122 </div>
123 </template>
124
125 <script setup lang="ts">
126 -import { ref, onBeforeMount, computed, watch } from "vue"
127 -import { NCard, NSpin, NPopover, NButton, NEmpty, NSelect, NPagination, NBadge, useMessage } from "naive-ui"
126 +import type { SigmaQuery } from "@/types/sigma.d"
127 import Api from "@/api"
129 -import _cloneDeep from "lodash/cloneDeep"
130 -import _orderBy from "lodash/orderBy"
128 import Icon from "@/components/common/Icon.vue"
129 import { useResizeObserver, useStorage } from "@vueuse/core"
133 -import QueryItem from "./QueryItem.vue"
130 +import _cloneDeep from "lodash/cloneDeep"
131 +import _orderBy from "lodash/orderBy"
132 +import { NBadge, NButton, NCard, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
133 +import { computed, onBeforeMount, ref, watch } from "vue"
134 import QueriesActions from "./QueriesActions.vue"
135 -import type { SigmaQuery } from "@/types/sigma.d"
135 +import QueryItem from "./QueryItem.vue"
136
137 interface QueriesFilter {
138 active: "active" | "inactive"
frontend/src/components/sigma/QueryDetails.vue
+7 -8
@@ -14,21 +14,20 @@
14 </template>
15
16 <script setup lang="ts">
17 -import { defineAsyncComponent, toRefs } from "vue"
18 -import { NTabs, NTabPane } from "naive-ui"
17 import type { SigmaQuery } from "@/types/sigma.d"
18 +import { NTabPane, NTabs } from "naive-ui"
19 +import { defineAsyncComponent, toRefs } from "vue"
20
21 -const QueryOverview = defineAsyncComponent(() => import("./QueryOverview.vue"))
22 -const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
23 -
21 +const props = defineProps<{
22 + query: SigmaQuery
23 +}>()
24 const emit = defineEmits<{
25 (e: "deleted", value: SigmaQuery): void
26 (e: "updated", value: SigmaQuery): void
27 }>()
28 +const QueryOverview = defineAsyncComponent(() => import("./QueryOverview.vue"))
29 +const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
30
29 -const props = defineProps<{
30 - query: SigmaQuery
31 -}>()
31 const { query } = toRefs(props)
32
33 function updateQuery(updatedQuery: SigmaQuery) {
frontend/src/components/sigma/QueryItem.vue
+17 -16
@@ -8,8 +8,8 @@
8 </div>
9 <div class="status flex gap-2 items-center">
10 <span>{{ query.active ? "Active" : "Inactive" }}</span>
11 - <Icon :name="EnabledIcon" :size="14" class="text-success-color" v-if="query.active"></Icon>
12 - <Icon :name="DisabledIcon" :size="14" class="text-secondary-color" v-else></Icon>
11 + <Icon v-if="query.active" :name="EnabledIcon" :size="14" class="text-success-color"></Icon>
12 + <Icon v-else :name="DisabledIcon" :size="14" class="text-secondary-color"></Icon>
13 </div>
14 </div>
15
@@ -24,11 +24,11 @@
24 <div class="footer-box px-5 py-3 flex justify-between items-center gap-4">
25 <div class="badges-box flex flex-wrap items-center gap-3">
26 <QueryTimeIntervalForm
27 - :query
27 v-slot="{ loading: loadingTimeInterval, togglePopup: toggleTimeIntervalPopup }"
28 + :query
29 @updated="updateQuery($event)"
30 >
31 - <Badge type="splitted" bright pointCursor @click.stop="toggleTimeIntervalPopup()">
31 + <Badge type="splitted" bright point-cursor @click.stop="toggleTimeIntervalPopup()">
32 <template #iconLeft>
33 <n-spin
34 :size="12"
@@ -66,8 +66,8 @@
66 </div>
67 <div class="actions-box">
68 <QueryDeleteOne
69 - :query
69 v-slot="{ loading: loadingDelete, togglePopup: toggleDeletePopup }"
70 + :query
71 @deleted="emit('deleted', query)"
72 >
73 <n-button quaternary size="tiny" :loading="loadingDelete" @click.stop="toggleDeletePopup()">
@@ -88,10 +88,10 @@
88 content-class="flex flex-col !p-0"
89 :title="`#${query.id}`"
90 closable
91 - @close="closeDetails()"
91 :bordered="false"
92 segmented
93 role="modal"
94 + @close="closeDetails()"
95 >
96 <QueryDetails :query @deleted="emitDelete(query)" @updated="updateQuery($event)" />
97 </n-card>
@@ -100,26 +100,27 @@
100 </template>
101
102 <script setup lang="ts">
103 -import { ref, toRefs } from "vue"
104 -import { NModal, NButton, NSpin, NCard } from "naive-ui"
103 +import type { SigmaQuery } from "@/types/sigma.d"
104 +import Badge from "@/components/common/Badge.vue"
105 +import Icon from "@/components/common/Icon.vue"
106 import { useSettingsStore } from "@/stores/settings"
107 import { formatDate } from "@/utils"
107 -import Icon from "@/components/common/Icon.vue"
108 -import Badge from "@/components/common/Badge.vue"
109 -import QueryTimeIntervalForm from "./actionsProviders/QueryTimeIntervalForm.vue"
108 +import { NButton, NCard, NModal, NSpin } from "naive-ui"
109 +import { ref, toRefs } from "vue"
110 import QueryDeleteOne from "./actionsProviders/QueryDeleteOne.vue"
111 +import QueryTimeIntervalForm from "./actionsProviders/QueryTimeIntervalForm.vue"
112 import QueryDetails from "./QueryDetails.vue"
112 -import type { SigmaQuery } from "@/types/sigma.d"
113 +
114 +const props = defineProps<{
115 + query: SigmaQuery
116 + embedded?: boolean
117 +}>()
118
119 const emit = defineEmits<{
120 (e: "deleted", value: SigmaQuery): void
121 (e: "updated", value: SigmaQuery): void
122 }>()
123
119 -const props = defineProps<{
120 - query: SigmaQuery
121 - embedded?: boolean
122 -}>()
124 const { query, embedded } = toRefs(props)
125
126 const TimeIcon = "carbon:time"
frontend/src/components/sigma/QueryOverview.vue
+20 -15
@@ -13,17 +13,17 @@
13 <template #value>
14 <div class="flex">
15 <QueryActiveForm
16 - :query
16 v-slot="{ loading: loadingActive, togglePopup: toggleActivePopup }"
17 + :query
18 @updated="updateQuery($event)"
19 >
20 <div
21 - @click.stop="toggleActivePopup()"
21 class="flex gap-3 items-center"
22 :class="{
23 'cursor-not-allowed': loadingActive,
24 'cursor-pointer': !loadingActive
25 }"
26 + @click.stop="toggleActivePopup()"
27 >
28 <span>{{ query.active ? "Active" : "Inactive" }}</span>
29 <n-spin
@@ -49,17 +49,17 @@
49 <template #value>
50 <div class="flex">
51 <QueryTimeIntervalForm
52 - :query
52 v-slot="{ loading: loadingTimeInterval, togglePopup: toggleTimeIntervalPopup }"
53 + :query
54 @updated="updateQuery($event)"
55 >
56 <div
57 - @click.stop="toggleTimeIntervalPopup()"
57 class="flex gap-3 items-center"
58 :class="{
59 'cursor-not-allowed': loadingTimeInterval,
60 'cursor-pointer': !loadingTimeInterval
61 }"
62 + @click.stop="toggleTimeIntervalPopup()"
63 >
64 <span>{{ query.time_interval || "n/d" }}</span>
65 <n-spin
@@ -79,7 +79,9 @@
79 <div class="px-7">
80 <KVCard>
81 <template #key>name</template>
82 - <template #value>{{ query.rule_name ?? "-" }}</template>
82 + <template #value>
83 + {{ query.rule_name ?? "-" }}
84 + </template>
85 </KVCard>
86 </div>
87
@@ -108,12 +110,14 @@
110 <div class="grow"></div>
111
112 <QueryDeleteOne
111 - :query
113 v-slot="{ loading: loadingDelete, togglePopup: toggleDeletePopup }"
114 + :query
115 @deleted="emit('deleted', query)"
116 >
117 <n-button type="error" secondary :loading="loadingDelete" @click.stop="toggleDeletePopup()">
116 - <template #icon><Icon :name="TrashIcon" /></template>
118 + <template #icon>
119 + <Icon :name="TrashIcon" />
120 + </template>
121 Delete
122 </n-button>
123 </QueryDeleteOne>
@@ -123,23 +127,24 @@
127 </template>
128
129 <script setup lang="ts">
126 -import { ref, toRefs } from "vue"
130 +import type { SigmaQuery } from "@/types/sigma.d"
131 +import Icon from "@/components/common/Icon.vue"
132 +import KVCard from "@/components/common/KVCard.vue"
133 +import { useSettingsStore } from "@/stores/settings"
134 +import { formatDate } from "@/utils"
135 import { NButton, NSpin } from "naive-ui"
136 +import { ref, toRefs } from "vue"
137 import QueryActiveForm from "./actionsProviders/QueryActiveForm.vue"
129 -import QueryTimeIntervalForm from "./actionsProviders/QueryTimeIntervalForm.vue"
138 import QueryDeleteOne from "./actionsProviders/QueryDeleteOne.vue"
131 -import KVCard from "@/components/common/KVCard.vue"
132 -import Icon from "@/components/common/Icon.vue"
133 -import { formatDate } from "@/utils"
134 -import { useSettingsStore } from "@/stores/settings"
135 -import type { SigmaQuery } from "@/types/sigma.d"
139 +import QueryTimeIntervalForm from "./actionsProviders/QueryTimeIntervalForm.vue"
140 +
141 +const props = defineProps<{ query: SigmaQuery }>()
142
143 const emit = defineEmits<{
144 (e: "deleted", value: SigmaQuery): void
145 (e: "updated", value: SigmaQuery): void
146 }>()
147
142 -const props = defineProps<{ query: SigmaQuery }>()
148 const { query } = toRefs(props)
149
150 const TrashIcon = "carbon:trash-can"
frontend/src/components/sigma/actionsProviders/QueryActiveAllForm.vue
+6 -6
@@ -1,28 +1,28 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-2 justify-center min-w-52">
8 <div class="flex gap-2 items-center">
9 - <n-button :loading @click="updateActive(true)" type="success">Activates all Sigma queries</n-button>
9 + <n-button :loading type="success" @click="updateActive(true)">Activates all Sigma queries</n-button>
10 <n-button :loading @click="updateActive(false)">Deactivates all Sigma queries</n-button>
11 </div>
12
13 <p class="text-right">* It may take several minutes</p>
14
15 <div class="flex gap-2 justify-between">
16 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
16 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
17 </div>
18 </div>
19 </n-popover>
20 </template>
21
22 <script setup lang="ts">
23 -import { ref } from "vue"
24 -import { NButton, NPopover, useMessage } from "naive-ui"
23 import Api from "@/api"
24 +import { NButton, NPopover, useMessage } from "naive-ui"
25 +import { ref } from "vue"
26
27 const emit = defineEmits<{
28 (e: "updated"): void
frontend/src/components/sigma/actionsProviders/QueryActiveForm.vue
+11 -10
@@ -1,7 +1,7 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-4 justify-center min-w-52">
@@ -11,8 +11,8 @@
11 </div>
12
13 <div class="flex gap-2 justify-between">
14 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
15 - <n-button :disabled="!dirty" :loading @click="updateActive()" type="primary" size="small">
14 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
15 + <n-button :disabled="!dirty" :loading type="primary" size="small" @click="updateActive()">
16 Save
17 </n-button>
18 </div>
@@ -21,18 +21,19 @@
21 </template>
22
23 <script setup lang="ts">
24 -import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
25 -import { NButton, NPopover, NSwitch, useMessage } from "naive-ui"
26 -import Api from "@/api"
24 import type { SigmaQuery } from "@/types/sigma.d"
25 +import Api from "@/api"
26 +import { NButton, NPopover, NSwitch, useMessage } from "naive-ui"
27 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
28 +
29 +const props = defineProps<{
30 + query: SigmaQuery
31 +}>()
32
33 const emit = defineEmits<{
34 (e: "updated", value: SigmaQuery): void
35 }>()
36
33 -const props = defineProps<{
34 - query: SigmaQuery
35 -}>()
37 const { query } = toRefs(props)
38
39 const loading = defineModel<boolean | undefined>("loading", { default: false })
frontend/src/components/sigma/actionsProviders/QueryDeleteAll.vue
+6 -6
@@ -1,7 +1,7 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-2 max-w-80">
@@ -10,17 +10,17 @@
10 <p class="text-right">* It may take several minutes</p>
11
12 <div class="flex gap-2 justify-between">
13 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
14 - <n-button :loading @click="deleteQueries()" type="primary" size="small">Yes I'm sure</n-button>
13 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
14 + <n-button :loading type="primary" size="small" @click="deleteQueries()">Yes I'm sure</n-button>
15 </div>
16 </div>
17 </n-popover>
18 </template>
19
20 <script setup lang="ts">
21 -import { ref } from "vue"
22 -import { NButton, NPopover, useMessage } from "naive-ui"
21 import Api from "@/api"
22 +import { NButton, NPopover, useMessage } from "naive-ui"
23 +import { ref } from "vue"
24
25 const emit = defineEmits<{
26 (e: "updated"): void
frontend/src/components/sigma/actionsProviders/QueryDeleteOne.vue
+15 -12
@@ -1,7 +1,7 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-4">
@@ -12,9 +12,11 @@
12 </div>
13
14 <div class="flex gap-2 justify-between">
15 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
16 - <n-button :loading @click="deleteQuery()" type="error" size="small">
17 - <template #icon><Icon :name="TrashIcon" /></template>
15 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
16 + <n-button :loading type="error" size="small" @click="deleteQuery()">
17 + <template #icon>
18 + <Icon :name="TrashIcon" />
19 + </template>
20 Delete Query
21 </n-button>
22 </div>
@@ -23,19 +25,20 @@
25 </template>
26
27 <script setup lang="ts">
26 -import { ref, toRefs } from "vue"
27 -import { NButton, NPopover, useMessage } from "naive-ui"
28 -import Icon from "@/components/common/Icon.vue"
29 -import Api from "@/api"
28 import type { SigmaQuery } from "@/types/sigma.d"
29 +import Api from "@/api"
30 +import Icon from "@/components/common/Icon.vue"
31 +import { NButton, NPopover, useMessage } from "naive-ui"
32 +import { ref, toRefs } from "vue"
33 +
34 +const props = defineProps<{
35 + query: SigmaQuery
36 +}>()
37
38 const emit = defineEmits<{
39 (e: "deleted", value: SigmaQuery): void
40 }>()
41
36 -const props = defineProps<{
37 - query: SigmaQuery
38 -}>()
42 const { query } = toRefs(props)
43
44 const loading = defineModel<boolean | undefined>("loading", { default: false })
frontend/src/components/sigma/actionsProviders/QueryDownloadAll.vue
+6 -6
@@ -1,24 +1,24 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-4 max-w-80">
8 <div>This will download ALL Sigma queries, are you sure you want to proceed?</div>
9
10 <div class="flex gap-2 justify-between">
11 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
12 - <n-button :loading @click="downloadQueries()" type="primary" size="small">Yes I'm sure</n-button>
11 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
12 + <n-button :loading type="primary" size="small" @click="downloadQueries()">Yes I'm sure</n-button>
13 </div>
14 </div>
15 </n-popover>
16 </template>
17
18 <script setup lang="ts">
19 -import { ref } from "vue"
20 -import { NButton, NPopover, useMessage } from "naive-ui"
19 import Api from "@/api"
20 +import { NButton, NPopover, useMessage } from "naive-ui"
21 +import { ref } from "vue"
22
23 const emit = defineEmits<{
24 (e: "updated"): void
frontend/src/components/sigma/actionsProviders/QueryTimeIntervalForm.vue
+12 -11
@@ -1,7 +1,7 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-4">
@@ -26,13 +26,13 @@
26 </div>
27
28 <div class="flex gap-2 justify-between">
29 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
29 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
30 <n-button
31 :disabled="!dirty || !isValid"
32 :loading
33 - @click="updateTimeInterval()"
33 type="primary"
34 size="small"
35 + @click="updateTimeInterval()"
36 >
37 Save
38 </n-button>
@@ -42,18 +42,19 @@
42 </template>
43
44 <script setup lang="ts">
45 -import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
46 -import { NSelect, NInputGroup, NButton, NInputNumber, NPopover, useMessage } from "naive-ui"
47 -import Api from "@/api"
45 import type { SigmaQuery, SigmaTimeInterval, SigmaTimeIntervalUnit } from "@/types/sigma.d"
46 +import Api from "@/api"
47 +import { NButton, NInputGroup, NInputNumber, NPopover, NSelect, useMessage } from "naive-ui"
48 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
49 +
50 +const props = defineProps<{
51 + query: SigmaQuery
52 +}>()
53
54 const emit = defineEmits<{
55 (e: "updated", value: SigmaQuery): void
56 }>()
57
54 -const props = defineProps<{
55 - query: SigmaQuery
56 -}>()
58 const { query } = toRefs(props)
59
60 const loading = defineModel<boolean | undefined>("loading", { default: false })
@@ -98,7 +99,7 @@ function setModel() {
99 timeUnit.value = (
100 query.value.time_interval.match(/[a-z]/i)?.[0] || "m"
101 ).toLocaleLowerCase() as SigmaTimeIntervalUnit
101 - timeValue.value = parseInt(query.value.time_interval.match(/\d+/)?.[0] || "1")
102 + timeValue.value = Number.parseInt(query.value.time_interval.match(/\d+/)?.[0] || "1")
103
104 model.value.unit = timeUnit.value
105 model.value.time = timeValue.value
frontend/src/components/sigma/actionsProviders/QueryUploadDB.vue
+7 -7
@@ -1,7 +1,7 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-2 max-w-80 min-w-72">
@@ -15,8 +15,8 @@
15 <p class="text-right">* It may take several minutes</p>
16
17 <div class="flex gap-2 justify-between">
18 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
19 - <n-button :disabled="!isValid" :loading @click="uploadQueries()" type="primary" size="small">
18 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
19 + <n-button :disabled="!isValid" :loading type="primary" size="small" @click="uploadQueries()">
20 Upload
21 </n-button>
22 </div>
@@ -25,10 +25,10 @@
25 </template>
26
27 <script setup lang="ts">
28 -import { computed, ref } from "vue"
29 -import { NButton, NPopover, NCheckboxGroup, NCheckbox, useMessage } from "naive-ui"
30 -import Api from "@/api"
28 import type { SigmaRuleLevels } from "@/types/sigma.d"
29 +import Api from "@/api"
30 +import { NButton, NCheckbox, NCheckboxGroup, NPopover, useMessage } from "naive-ui"
31 +import { computed, ref } from "vue"
32
33 const emit = defineEmits<{
34 (e: "updated"): void
frontend/src/components/sigma/actionsProviders/QueryUploadFile.vue
+7 -7
@@ -1,14 +1,14 @@
1 <template>
2 - <n-popover trigger="manual" to="body" content-class="px-0" v-model:show="show" @clickoutside="closePopup()">
2 + <n-popover v-model:show="show" trigger="manual" to="body" content-class="px-0" @clickoutside="closePopup()">
3 <template #trigger>
4 - <slot :loading :togglePopup />
4 + <slot :loading :toggle-popup />
5 </template>
6
7 <div class="py-1 flex flex-col gap-2 max-w-80">
8 <n-upload
9 + v-model:file-list="fileList"
10 :max="1"
11 accept="application/x-yaml, .yaml, .YAML, .yml, .YML"
11 - v-model:file-list="fileList"
12 :disabled="loading"
13 >
14 <n-upload-dragger>
@@ -21,8 +21,8 @@
21 </n-upload>
22
23 <div class="flex gap-2 justify-between">
24 - <n-button @click="closePopup()" quaternary size="small">Close</n-button>
25 - <n-button :disabled="!isValid" :loading @click="uploadQueries()" type="primary" size="small">
24 + <n-button quaternary size="small" @click="closePopup()">Close</n-button>
25 + <n-button :disabled="!isValid" :loading type="primary" size="small" @click="uploadQueries()">
26 Upload
27 </n-button>
28 </div>
@@ -31,10 +31,10 @@
31 </template>
32
33 <script setup lang="ts">
34 -import { computed, ref } from "vue"
35 -import { NButton, NPopover, NUpload, NUploadDragger, useMessage, type UploadFileInfo } from "naive-ui"
34 import Api from "@/api"
35 import Icon from "@/components/common/Icon.vue"
36 +import { NButton, NPopover, NUpload, NUploadDragger, type UploadFileInfo, useMessage } from "naive-ui"
37 +import { computed, ref } from "vue"
38
39 const emit = defineEmits<{
40 (e: "updated"): void
frontend/src/components/soc/SocAlerts/SocAlertAssets/SocAlertAssetsItem.vue
+39 -25
@@ -11,8 +11,10 @@
11 </div>
12 <div class="main-box flex justify-between gap-4">
13 <div class="content">
14 - <div class="title">{{ asset.asset_name }}</div>
15 - <div class="description mt-2" v-if="asset.asset_description">
14 + <div class="title">
15 + {{ asset.asset_name }}
16 + </div>
17 + <div v-if="asset.asset_description" class="description mt-2">
18 <template v-if="isUrlLike(asset.asset_description)">
19 <a
20 :href="asset.asset_description"
@@ -29,23 +31,29 @@
31 </code>
32 </a>
33 </template>
32 - <template v-else>{{ excerpt }}</template>
34 + <template v-else>
35 + {{ excerpt }}
36 + </template>
37 </div>
38
39 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
36 - <Badge type="splitted" color="primary" v-if="asset.date_added">
40 + <Badge v-if="asset.date_added" type="splitted" color="primary">
41 <template #iconLeft>
42 <Icon :name="ClockIcon" :size="14"></Icon>
43 </template>
44 <template #label>Added</template>
41 - <template #value>{{ formatDate(asset.date_added) }}</template>
45 + <template #value>
46 + {{ formatDate(asset.date_added) }}
47 + </template>
48 </Badge>
43 - <Badge type="splitted" color="primary" v-if="asset.date_update">
49 + <Badge v-if="asset.date_update" type="splitted" color="primary">
50 <template #iconLeft>
51 <Icon :name="ClockIcon" :size="14"></Icon>
52 </template>
53 <template #label>Updated</template>
48 - <template #value>{{ formatDate(asset.date_update) }}</template>
54 + <template #value>
55 + {{ formatDate(asset.date_update) }}
56 + </template>
57 </Badge>
58 <Badge type="active" class="cursor-pointer" @click="gotoAgent(asset.asset_tags)">
59 <template #iconRight>
@@ -69,15 +77,17 @@
77 >
78 <n-tabs type="line" animated :tabs-padding="24">
79 <n-tab-pane name="Info" tab="Info" display-directive="show">
72 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4" v-if="properties">
80 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
81 <KVCard v-for="(value, key) of properties" :key="key">
74 - <template #key>{{ key }}</template>
82 + <template #key>
83 + {{ key }}
84 + </template>
85 <template #value>
86 <template v-if="key === 'asset_tags'">
87 <code
88 + v-if="value && value !== '-'"
89 class="cursor-pointer text-primary-color"
90 @click="gotoAgent(value)"
80 - v-if="value && value !== '-'"
91 >
92 {{ value }}
93 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -92,10 +102,14 @@
102 </div>
103 </n-tab-pane>
104 <n-tab-pane name="Type" tab="Type" display-directive="show">
95 - <div class="grid gap-2 grid-auto-fit-250 p-7 pt-4" v-if="assetType">
105 + <div v-if="assetType" class="grid gap-2 grid-auto-fit-250 p-7 pt-4">
106 <KVCard v-for="(value, key) of assetType" :key="key">
97 - <template #key>{{ key }}</template>
98 - <template #value>{{ value || "-" }}</template>
107 + <template #key>
108 + {{ key }}
109 + </template>
110 + <template #value>
111 + {{ value || "-" }}
112 + </template>
113 </KVCard>
114 </div>
115 </n-tab-pane>
@@ -134,22 +148,22 @@
148 </template>
149
150 <script setup lang="ts">
151 +import type { SocAlertAsset } from "@/types/soc/asset.d"
152 +import Badge from "@/components/common/Badge.vue"
153 import Icon from "@/components/common/Icon.vue"
154 import KVCard from "@/components/common/KVCard.vue"
139 -import Badge from "@/components/common/Badge.vue"
140 -import { computed, defineAsyncComponent, ref } from "vue"
141 -import { NModal, NTabs, NTabPane } from "naive-ui"
142 -import _omit from "lodash/omit"
143 -import dayjs from "@/utils/dayjs"
144 -import { isUrlLike } from "@/utils"
145 -import type { SocAlertAsset } from "@/types/soc/asset.d"
146 -import { useSettingsStore } from "@/stores/settings"
155 import { useGoto } from "@/composables/useGoto"
148 -
149 -const ArtifactsCollect = defineAsyncComponent(() => import("@/components/artifacts/ArtifactsCollect.vue"))
156 +import { useSettingsStore } from "@/stores/settings"
157 +import { isUrlLike } from "@/utils"
158 +import dayjs from "@/utils/dayjs"
159 +import _omit from "lodash/omit"
160 +import { NModal, NTabPane, NTabs } from "naive-ui"
161 +import { computed, defineAsyncComponent, ref } from "vue"
162
163 const { asset } = defineProps<{ asset: SocAlertAsset }>()
164
165 +const ArtifactsCollect = defineAsyncComponent(() => import("@/components/artifacts/ArtifactsCollect.vue"))
166 +
167 const InfoIcon = "carbon:information"
168 const ClockIcon = "carbon:time"
169 const LinkIcon = "carbon:launch"
@@ -168,7 +182,7 @@ const excerpt = computed(() => {
182 const descriptionFull = computed(() => {
183 const text = asset.asset_description
184
171 - return text.replace(/\n/gim, "<br>") || "Empty"
185 + return text.replace(/\n/g, "<br>") || "Empty"
186 })
187
188 const properties = computed(() => {
@@ -186,7 +200,7 @@ const assetType = computed(() => {
200 return asset.asset_type || {}
201 })
202
189 -const formatDate = (date: string, useSec = false) => {
203 +function formatDate(date: string, useSec = false) {
204 const datejs = dayjs(date)
205 if (!datejs.isValid()) return date
206
frontend/src/components/soc/SocAlerts/SocAlertAssets/SocAlertAssetsList.vue
+5 -6
@@ -5,19 +5,18 @@
5 <SocAlertAssetsItem v-for="asset of assetsList" :key="asset.asset_id" :asset="asset" />
6 </div>
7 <template v-else>
8 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingAssets" />
8 + <n-empty v-if="!loadingAssets" description="No items found" class="justify-center h-48" />
9 </template>
10 </n-spin>
11 </div>
12 </template>
13
14 <script setup lang="ts">
15 -import { ref } from "vue"
16 -import SocAlertAssetsItem from "./SocAlertAssetsItem.vue"
17 -import Api from "@/api"
18 -import { useMessage, NSpin, NEmpty } from "naive-ui"
15 import type { SocAlertAsset } from "@/types/soc/asset.d"
20 -import { onBeforeMount } from "vue"
16 +import Api from "@/api"
17 +import { NEmpty, NSpin, useMessage } from "naive-ui"
18 +import { onBeforeMount, ref } from "vue"
19 +import SocAlertAssetsItem from "./SocAlertAssetsItem.vue"
20
21 const { alertId } = defineProps<{ alertId: string | number }>()
22
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItem.vue
+37 -35
@@ -1,16 +1,16 @@
1 <template>
2 <n-spin
3 + :id="`alert-${alert?.alert_id}`"
4 :show="loading"
5 :description="loadingDelete ? 'Deleting Soc Alert' : 'Loading Soc Alert'"
6 class="soc-alert-item flex flex-col gap-0 min-h-36 pb-2"
7 :class="{ bookmarked: isBookmark, highlight, embedded }"
7 - :id="'alert-' + alert?.alert_id"
8 >
9 - <div class="soc-alert-info px-5 py-3 flex flex-col gap-3" v-if="alert">
9 + <div v-if="alert" class="soc-alert-info px-5 py-3 flex flex-col gap-3">
10 <div class="header-box flex justify-between">
11 <div class="flex items-center gap-2 cursor-pointer">
12 <div v-if="showCheckbox" class="check-box mr-2">
13 - <n-checkbox size="large" v-model:checked="checked" />
13 + <n-checkbox v-model:checked="checked" size="large" />
14 </div>
15 <div class="id flex items-center gap-2 cursor-pointer" @click="showDetails = true">
16 <span>#{{ alert.alert_id }} - {{ alert.alert_uuid }}</span>
@@ -19,7 +19,7 @@
19 <SocAlertItemBookmarkToggler
20 v-if="!hideBookmarkAction && alert"
21 :alert
22 - :isBookmark
22 + :is-bookmark
23 @bookmark="emit('bookmark', $event)"
24 />
25 </div>
@@ -29,10 +29,12 @@
29 </div>
30 <div class="main-box flex justify-between gap-4">
31 <div class="content">
32 - <div class="title">{{ alert.alert_title }}</div>
32 + <div class="title">
33 + {{ alert.alert_title }}
34 + </div>
35 <div
34 - class="description mb-2"
36 v-if="alert.alert_description && alert.alert_title !== alert.alert_description"
37 + class="description mb-2"
38 >
39 {{ alert.alert_description }}
40 </div>
@@ -40,18 +42,18 @@
42 <SocAlertItemActions
43 v-if="!hideSocCaseAction"
44 class="actions-box"
43 - :caseId
44 - :alertId="alert.alert_id"
45 - @caseCreated="caseCreated($event)"
45 + :case-id
46 + :alert-id="alert.alert_id"
47 + @case-created="caseCreated($event)"
48 @deleted="deleted()"
47 - @startDeleting="loadingDelete = true"
49 + @start-deleting="loadingDelete = true"
50 />
51 </div>
52
53 <div>
54 <div
53 - class="show-badges-toggle flex items-center gap-2"
55 v-if="showBadgesToggle"
56 + class="show-badges-toggle flex items-center gap-2"
57 @click="showBadges = !showBadges"
58 >
59 {{ showBadges ? "Less info" : "More info" }}
@@ -60,7 +62,7 @@
62 </span>
63 </div>
64 <n-collapse-transition :show="!showBadgesToggle || showBadges">
63 - <SocAlertItemBadges class="badges-box" v-if="alert" :alert :users @updated="updateAlert" />
65 + <SocAlertItemBadges v-if="alert" class="badges-box" :alert :users @updated="updateAlert" />
66 </n-collapse-transition>
67 </div>
68
@@ -70,11 +72,11 @@
72 class="actions-box grow !flex-wrap !justify-start"
73 style="flex-direction: initial"
74 size="small"
73 - :caseId
74 - :alertId="alert.alert_id"
75 - @caseCreated="caseCreated($event)"
75 + :case-id
76 + :alert-id="alert.alert_id"
77 + @case-created="caseCreated($event)"
78 @deleted="deleted()"
77 - @startDeleting="loadingDelete = true"
79 + @start-deleting="loadingDelete = true"
80 />
81 <div class="time">
82 <SocAlertItemTime :alert hide-timeline />
@@ -116,30 +118,16 @@
118 </template>
119
120 <script setup lang="ts">
119 -import type { SocAlert } from "@/types/soc/alert.d"
121 import type { Alert } from "@/types/alerts.d"
122 +import type { SocAlert } from "@/types/soc/alert.d"
123 +import type { SocUser } from "@/types/soc/user.d"
124 +import Api from "@/api"
125 import Icon from "@/components/common/Icon.vue"
126 +import { NCheckbox, NCollapse, NCollapseItem, NCollapseTransition, NModal, NSpin, useMessage } from "naive-ui"
127 import { computed, defineAsyncComponent, onBeforeMount, ref, toRefs, watch } from "vue"
128 import SocAlertItemActions from "./SocAlertItemActions.vue"
124 -import SocAlertItemTime from "./SocAlertItemTime.vue"
129 import SocAlertItemBookmarkToggler from "./SocAlertItemBookmarkToggler.vue"
126 -import Api from "@/api"
127 -import { NCollapse, useMessage, NCollapseItem, NModal, NSpin, NCheckbox, NCollapseTransition } from "naive-ui"
128 -import type { SocUser } from "@/types/soc/user.d"
129 -const SocAlertItemDetails = defineAsyncComponent(() => import("./SocAlertItemDetails.vue"))
130 -const SocAlertItemBadges = defineAsyncComponent(() => import("./SocAlertItemBadges.vue"))
131 -const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
132 -const AlertItem = defineAsyncComponent(() => import("@/components/alerts/Alert.vue"))
133 -
134 -const checked = defineModel<boolean>("checked", { default: false })
135 -
136 -const emit = defineEmits<{
137 - (e: "bookmark", value: boolean): void
138 - (e: "deleted"): void
139 - (e: "checked"): void
140 - (e: "unchecked"): void
141 - (e: "check", value: boolean): void
142 -}>()
130 +import SocAlertItemTime from "./SocAlertItemTime.vue"
131
132 const props = defineProps<{
133 alertData?: SocAlert
@@ -153,6 +141,20 @@ const props = defineProps<{
141 showBadgesToggle?: boolean
142 showCheckbox?: boolean
143 }>()
144 +const emit = defineEmits<{
145 + (e: "bookmark", value: boolean): void
146 + (e: "deleted"): void
147 + (e: "checked"): void
148 + (e: "unchecked"): void
149 + (e: "check", value: boolean): void
150 +}>()
151 +const SocAlertItemDetails = defineAsyncComponent(() => import("./SocAlertItemDetails.vue"))
152 +const SocAlertItemBadges = defineAsyncComponent(() => import("./SocAlertItemBadges.vue"))
153 +const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
154 +const AlertItem = defineAsyncComponent(() => import("@/components/alerts/Alert.vue"))
155 +
156 +const checked = defineModel<boolean>("checked", { default: false })
157 +
158 const {
159 alertData,
160 alertId,
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemActions.vue
+25 -19
@@ -1,29 +1,35 @@
1 <template>
2 <div class="soc-alert-actions flex flex-col gap-2 justify-center">
3 - <n-button v-if="existCase" type="success" secondary @click="openSocCase()" :size="size">
4 - <template #icon><Icon :name="ViewIcon"></Icon></template>
3 + <n-button v-if="existCase" type="success" secondary :size="size" @click="openSocCase()">
4 + <template #icon>
5 + <Icon :name="ViewIcon"></Icon>
6 + </template>
7 View SOC Case
8 </n-button>
9 <n-button
10 + v-else-if="alertId"
11 :loading="loadingCaseCreation"
12 type="warning"
13 secondary
11 - @click="createCase()"
14 :size="size"
13 - v-else-if="alertId"
15 + @click="createCase()"
16 >
15 - <template #icon><Icon :name="DangerIcon"></Icon></template>
17 + <template #icon>
18 + <Icon :name="DangerIcon"></Icon>
19 + </template>
20 Create SOC Case
21 </n-button>
22 <n-button
23 + v-if="alertId"
24 :loading="loadingAlertDelete"
25 :size="size"
26 type="error"
27 secondary
28 @click="handleDelete()"
24 - v-if="alertId"
29 >
26 - <template #icon><Icon :name="DeleteIcon"></Icon></template>
30 + <template #icon>
31 + <Icon :name="DeleteIcon"></Icon>
32 + </template>
33 Delete
34 </n-button>
35
@@ -39,9 +45,9 @@
45 <div class="h-full w-full flex items-center justify-center">
46 <SocCaseItem
47 v-if="caseId"
42 - :caseId="caseId"
48 + :case-id="caseId"
49 embedded
44 - hideSocAlertLink
50 + hide-soc-alert-link
51 hide-soc-case-action
52 class="w-full"
53 />
@@ -51,12 +57,18 @@
57 </template>
58
59 <script setup lang="ts">
54 -import { NButton, useDialog, useMessage, NModal } from "naive-ui"
55 -import Icon from "@/components/common/Icon.vue"
60 +import type { Size } from "naive-ui/es/button/src/interface"
61 import Api from "@/api"
57 -import { computed, ref, watch } from "vue"
62 +import Icon from "@/components/common/Icon.vue"
63 import SocCaseItem from "@/components/soc/SocCases/SocCaseItem.vue"
59 -import type { Size } from "naive-ui/es/button/src/interface"
64 +import { NButton, NModal, useDialog, useMessage } from "naive-ui"
65 +import { computed, ref, watch } from "vue"
66 +
67 +const { alertId, caseId, size } = defineProps<{
68 + alertId?: string | number | null
69 + caseId?: string | number | null
70 + size?: Size
71 +}>()
72
73 const emit = defineEmits<{
74 (e: "startLoading"): void
@@ -66,12 +78,6 @@ const emit = defineEmits<{
78 (e: "startDeleting"): void
79 }>()
80
69 -const { alertId, caseId, size } = defineProps<{
70 - alertId?: string | number | null
71 - caseId?: string | number | null
72 - size?: Size
73 -}>()
74 -
81 const DeleteIcon = "ph:trash"
82 const DangerIcon = "majesticons:exclamation-line"
83 const ViewIcon = "iconoir:eye-alt"
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemBadges.vue
+22 -13
@@ -7,7 +7,9 @@
7 <Icon :name="StatusIcon" :size="14"></Icon>
8 </template>
9 <template #label>Status</template>
10 - <template #value>{{ alert.status?.status_name || "-" }}</template>
10 + <template #value>
11 + {{ alert.status?.status_name || "-" }}
12 + </template>
13 </Badge>
14 </template>
15 {{ alert.status.status_description }}
@@ -17,14 +19,18 @@
19 <Icon :name="SeverityIcon" :size="13"></Icon>
20 </template>
21 <template #label>Severity</template>
20 - <template #value>{{ alert.severity?.severity_name || "-" }}</template>
22 + <template #value>
23 + {{ alert.severity?.severity_name || "-" }}
24 + </template>
25 </Badge>
26 <Badge type="splitted" color="primary" class="hide-on-small">
27 <template #iconLeft>
28 <Icon :name="SourceIcon" :size="13"></Icon>
29 </template>
30 <template #label>Source</template>
27 - <template #value>{{ alert.alert_source || "-" }}</template>
31 + <template #value>
32 + {{ alert.alert_source || "-" }}
33 + </template>
34 </Badge>
35 <Badge type="splitted" color="primary" class="hide-on-small">
36 <template #iconLeft>
@@ -47,7 +53,7 @@
53 </template>
54 </Badge>
55
50 - <SocAssignUser :alert="alert" :users="users" v-slot="{ loading }" @updated="emit('updated', $event)">
56 + <SocAssignUser v-slot="{ loading }" :alert="alert" :users="users" @updated="emit('updated', $event)">
57 <Badge type="active" class="cursor-pointer">
58 <template #iconLeft>
59 <n-spin :size="16" :show="loading">
@@ -55,7 +61,9 @@
61 </n-spin>
62 </template>
63 <template #label>Owner</template>
58 - <template #value>{{ ownerName || "n/d" }}</template>
64 + <template #value>
65 + {{ ownerName || "n/d" }}
66 + </template>
67 </Badge>
68 </SocAssignUser>
69
@@ -77,22 +85,23 @@
85
86 <script setup lang="ts">
87 import type { SocAlert } from "@/types/soc/alert.d"
80 -import Icon from "@/components/common/Icon.vue"
88 +import type { SocUser } from "@/types/soc/user.d"
89 import Badge from "@/components/common/Badge.vue"
90 +import Icon from "@/components/common/Icon.vue"
91 +import { useGoto } from "@/composables/useGoto"
92 +import { NSpin, NTooltip } from "naive-ui"
93 import { computed, toRefs } from "vue"
94 import SocAssignUser from "./SocAssignUser.vue"
84 -import { NSpin, NTooltip } from "naive-ui"
85 -import type { SocUser } from "@/types/soc/user.d"
86 -import { useGoto } from "@/composables/useGoto"
87 -
88 -const emit = defineEmits<{
89 - (e: "updated", value: SocAlert): void
90 -}>()
95
96 const props = defineProps<{
97 alert: SocAlert
98 users?: SocUser[]
99 }>()
100 +
101 +const emit = defineEmits<{
102 + (e: "updated", value: SocAlert): void
103 +}>()
104 +
105 const { alert, users } = toRefs(props)
106
107 const LinkIcon = "carbon:launch"
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemBookmarkToggler.vue
+9 -8
@@ -2,27 +2,28 @@
2 <Icon
3 :name="loadingBookmark ? LoadingIcon : isBookmark ? StarActiveIcon : StarIcon"
4 :size="16"
5 - @click="toggleBookmark()"
5 class="toggler-bookmark"
6 :class="{ active: isBookmark }"
7 + @click="toggleBookmark()"
8 ></Icon>
9 </template>
10
11 <script setup lang="ts">
12 import type { SocAlert } from "@/types/soc/alert.d"
13 -import Icon from "@/components/common/Icon.vue"
14 -import { ref, toRefs } from "vue"
13 import Api from "@/api"
14 +import Icon from "@/components/common/Icon.vue"
15 import { useMessage } from "naive-ui"
17 -
18 -const emit = defineEmits<{
19 - (e: "bookmark", value: boolean): void
20 -}>()
16 +import { ref, toRefs } from "vue"
17
18 const props = defineProps<{
19 alert: SocAlert
20 isBookmark?: boolean
21 }>()
22 +
23 +const emit = defineEmits<{
24 + (e: "bookmark", value: boolean): void
25 +}>()
26 +
27 const { alert, isBookmark } = toRefs(props)
28
29 const StarActiveIcon = "carbon:star-filled"
@@ -41,7 +42,7 @@ function toggleBookmark() {
42 Api.soc[method](alert.value.alert_id.toString())
43 .then(res => {
44 if (res.data.success) {
44 - emit("bookmark", method === "removeAlertBookmark" ? false : true)
45 + emit("bookmark", method !== "removeAlertBookmark")
46 message.success(res.data?.message || "Stream started.")
47 } else {
48 message.warning(res.data?.message || "An error occurred. Please try again later.")
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemContext.vue
+14 -12
@@ -1,6 +1,6 @@
1 <template>
2 <div class="flex flex-col gap-3">
3 - <n-input placeholder="Search..." v-model:value="textFilter" clearable>
3 + <n-input v-model:value="textFilter" placeholder="Search..." clearable>
4 <template #prefix>
5 <Icon :name="SearchIcon" />
6 </template>
@@ -8,7 +8,9 @@
8
9 <div class="grid gap-2 grid-auto-fit-200">
10 <KVCard v-for="{ value, key } of contextFiltered" :key="key">
11 - <template #key>{{ key }}</template>
11 + <template #key>
12 + {{ key }}
13 + </template>
14 <template #value>
15 <template v-if="key === 'process_name'">
16 <template v-if="value && value !== '-' && value.toString() && processNameList.length">
@@ -23,7 +25,7 @@
25 <template v-else>-</template>
26 </template>
27 <template v-else>
26 - <ExpandableText :text="value.toString() ?? '-'" :maxLength="100" />
28 + <ExpandableText :text="value.toString() ?? '-'" :max-length="100" />
29 </template>
30 </template>
31 </KVCard>
@@ -33,21 +35,21 @@
35
36 <script setup lang="ts">
37 import type { SocAlert } from "@/types/soc/alert.d"
36 -import KVCard from "@/components/common/KVCard.vue"
37 -import { NInput } from "naive-ui"
38 import Icon from "@/components/common/Icon.vue"
39 -import { computed, defineAsyncComponent, ref } from "vue"
40 -import _split from "lodash/split"
39 +import KVCard from "@/components/common/KVCard.vue"
40 import _compact from "lodash/compact"
41 +import _split from "lodash/split"
42 import _uniq from "lodash/uniq"
43 -const ThreatIntelProcessEvaluationBadge = defineAsyncComponent(
44 - () => import("@/components/threatIntel/ThreatIntelProcessEvaluationBadge.vue")
45 -)
46 -const ExpandableText = defineAsyncComponent(() => import("@/components/common/ExpandableText.vue"))
43 +import { NInput } from "naive-ui"
44 +import { computed, defineAsyncComponent, ref } from "vue"
45
46 const { alert } = defineProps<{
47 alert: SocAlert
48 }>()
49 +const ThreatIntelProcessEvaluationBadge = defineAsyncComponent(
50 + () => import("@/components/threatIntel/ThreatIntelProcessEvaluationBadge.vue")
51 +)
52 +const ExpandableText = defineAsyncComponent(() => import("@/components/common/ExpandableText.vue"))
53
54 const SearchIcon = "carbon:search"
55
@@ -74,6 +76,6 @@ const contextNormalized = computed(() => {
76 })
77
78 const contextFiltered = computed(() =>
77 - contextNormalized.value.filter(o => o.key.toLowerCase().indexOf(textFilter.value.toLowerCase()) !== -1)
79 + contextNormalized.value.filter(o => o.key.toLowerCase().includes(textFilter.value.toLowerCase()))
80 )
81 </script>
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemDetails.vue
+19 -19
@@ -1,5 +1,5 @@
1 <template>
2 - <n-tabs type="line" animated :tabs-padding="24" v-if="alert">
2 + <n-tabs v-if="alert" type="line" animated :tabs-padding="24">
3 <n-tab-pane name="Context" tab="Context" display-directive="show:lazy">
4 <SocAlertItemContext :alert="alert" class="p-7 pt-4" />
5 </n-tab-pane>
@@ -11,7 +11,9 @@
11 <n-tab-pane name="Customer" tab="Customer" display-directive="show:lazy">
12 <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
13 <KVCard v-for="(value, key) of alert.customer" :key="key">
14 - <template #key>{{ key }}</template>
14 + <template #key>
15 + {{ key }}
16 + </template>
17 <template #value>
18 <template v-if="key === 'customer_code' && value && value !== 'Customer Not Found'">
19 <code
@@ -43,9 +45,9 @@
45 <template #key>user_login</template>
46 <template #value>
47 <SocAssignUser
48 + v-slot="{ loading }"
49 :alert="alert"
50 :users="users"
48 - v-slot="{ loading }"
51 @updated="emit('updated', $event)"
52 >
53 <div class="flex items-center gap-2 cursor-pointer text-primary-color">
@@ -79,7 +81,7 @@
81 </n-tab-pane>
82 <n-tab-pane name="Details" tab="Details" display-directive="show:lazy">
83 <div class="p-7 pt-4">
82 - <SimpleJsonViewer class="vuesjv-override" :model-value="socAlertDetail" :initialExpandedDepth="1" />
84 + <SimpleJsonViewer class="vuesjv-override" :model-value="socAlertDetail" :initial-expanded-depth="1" />
85 </div>
86 </n-tab-pane>
87 <n-tab-pane name="Assets" tab="Assets" display-directive="show:lazy">
@@ -89,30 +91,28 @@
91 </template>
92
93 <script setup lang="ts">
92 -import { computed, defineAsyncComponent } from "vue"
93 -import { NTabs, NTabPane, NSpin } from "naive-ui"
94 -import Icon from "@/components/common/Icon.vue"
94 +import type { SocAlert } from "@/types/soc/alert.d"
95 +import type { SocUser } from "@/types/soc/user.d"
96 import Badge from "@/components/common/Badge.vue"
97 +import Icon from "@/components/common/Icon.vue"
98 import KVCard from "@/components/common/KVCard.vue"
99 +import { useGoto } from "@/composables/useGoto"
100 +import { NSpin, NTabPane, NTabs } from "naive-ui"
101 +import { computed, defineAsyncComponent } from "vue"
102 import { SimpleJsonViewer } from "vue-sjv"
103 import "@/assets/scss/vuesjv-override.scss"
99 -import { useGoto } from "@/composables/useGoto"
100 -import type { SocAlert } from "@/types/soc/alert.d"
101 -import type { SocUser } from "@/types/soc/user.d"
102 -
103 -const SocAlertItemTimeline = defineAsyncComponent(() => import("./SocAlertItemTimeline.vue"))
104 -const SocAssignUser = defineAsyncComponent(() => import("./SocAssignUser.vue"))
105 -const SocAlertItemContext = defineAsyncComponent(() => import("./SocAlertItemContext.vue"))
106 -const SocAlertAssetsList = defineAsyncComponent(() => import("../SocAlertAssets/SocAlertAssetsList.vue"))
107 -
108 -const emit = defineEmits<{
109 - (e: "updated", value: SocAlert): void
110 -}>()
104
105 const { alert } = defineProps<{
106 alert: SocAlert
107 users?: SocUser[]
108 }>()
109 +const emit = defineEmits<{
110 + (e: "updated", value: SocAlert): void
111 +}>()
112 +const SocAlertItemTimeline = defineAsyncComponent(() => import("./SocAlertItemTimeline.vue"))
113 +const SocAssignUser = defineAsyncComponent(() => import("./SocAssignUser.vue"))
114 +const SocAlertItemContext = defineAsyncComponent(() => import("./SocAlertItemContext.vue"))
115 +const SocAlertAssetsList = defineAsyncComponent(() => import("../SocAlertAssets/SocAlertAssetsList.vue"))
116
117 const LinkIcon = "carbon:launch"
118 const EditIcon = "uil:edit-alt"
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemTime.vue
+4 -4
@@ -5,7 +5,7 @@
5 <span>
6 {{ formatDate(alert.alert_source_event_time) }}
7 </span>
8 - <Icon :name="TimeIcon" :size="16" v-if="!hideTimeline"></Icon>
8 + <Icon v-if="!hideTimeline" :name="TimeIcon" :size="16"></Icon>
9 </div>
10 </template>
11 <div class="flex flex-col py-2 px-1">
@@ -17,11 +17,11 @@
17 <script setup lang="ts">
18 import type { SocAlert } from "@/types/soc/alert.d"
19 import Icon from "@/components/common/Icon.vue"
20 -import { toRefs } from "vue"
21 -import SocAlertItemTimeline from "./SocAlertItemTimeline.vue"
22 -import { NPopover } from "naive-ui"
20 import { useSettingsStore } from "@/stores/settings"
21 import dayjs from "@/utils/dayjs"
22 +import { NPopover } from "naive-ui"
23 +import { toRefs } from "vue"
24 +import SocAlertItemTimeline from "./SocAlertItemTimeline.vue"
25
26 const props = defineProps<{
27 alert: SocAlert
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemTimeline.vue
+4 -4
@@ -2,8 +2,8 @@
2 <n-timeline>
3 <n-timeline-item
4 v-for="(item, $index) of history"
5 - :type="$index === 0 ? 'success' : undefined"
5 :key="item.label"
6 + :type="$index === 0 ? 'success' : undefined"
7 :title="item.label"
8 :time="item.timeString"
9 :line-type="$index === history.length - 2 ? 'dashed' : undefined"
@@ -12,12 +12,12 @@
12 </template>
13
14 <script setup lang="ts">
15 -import { useSettingsStore } from "@/stores/settings"
15 import type { SocAlert } from "@/types/soc/alert.d"
16 +import { useSettingsStore } from "@/stores/settings"
17 import dayjs from "@/utils/dayjs"
18 -import { onBeforeMount, ref } from "vue"
18 import _toNumber from "lodash/toSafeInteger"
19 import { NTimeline, NTimelineItem } from "naive-ui"
20 +import { onBeforeMount, ref } from "vue"
21
22 const { alert } = defineProps<{ alert: SocAlert }>()
23
@@ -45,7 +45,7 @@ onBeforeMount(() => {
45 const item = alert.modification_history[key]
46 history.value.push({
47 timeString: formatDate(_toNumber(key) * 1000, false),
48 - label: item.action + ` [${item.user}]`
48 + label: `${item.action} [${item.user}]`
49 })
50 }
51 }
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAssignUser.vue
+6 -6
@@ -13,22 +13,22 @@
13 </template>
14
15 <script setup lang="ts">
16 -import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
17 -import { useMessage, NPopselect } from "naive-ui"
18 -import Api from "@/api"
16 import type { SocAlert } from "@/types/soc/alert.d"
17 import type { SocUser } from "@/types/soc/user.d"
18 +import Api from "@/api"
19 +import { NPopselect, useMessage } from "naive-ui"
20 +import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
21
22 const props = defineProps<{
23 alert: SocAlert
24 users?: SocUser[]
25 }>()
26 -const { alert, users } = toRefs(props)
27 -
26 const emit = defineEmits<{
27 (e: "updated", value: SocAlert): void
28 }>()
29
30 +const { alert, users } = toRefs(props)
31 +
32 const loadingUsers = ref(false)
33 const message = useMessage()
34
@@ -88,7 +88,7 @@ function assignUser() {
88 }
89
90 function parseUsers(users: SocUser[]) {
91 - usersOptions.value = users.map(o => ({ label: "#" + o.user_id + " • " + o.user_login, value: o.user_id }))
91 + usersOptions.value = users.map(o => ({ label: `#${o.user_id} • ${o.user_login}`, value: o.user_id }))
92
93 usersOptions.value.push({
94 label: "- Set default owner -",
frontend/src/components/soc/SocAlerts/SocAlertsBookmarks.vue
+8 -8
@@ -15,7 +15,7 @@
15 <SocAlertItem
16 v-for="alert of bookmarksList"
17 :key="alert.alert_id"
18 - :alertData="alert"
18 + :alert-data="alert"
19 class="item-appear item-appear-bottom item-appear-005 mb-2"
20 :is-bookmark="true"
21 :users="usersList"
@@ -24,7 +24,7 @@
24 />
25 </template>
26 <template v-else>
27 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingBookmarks" />
27 + <n-empty v-if="!loadingBookmarks" description="No items found" class="justify-center h-48" />
28 </template>
29 </div>
30 </n-spin>
@@ -32,19 +32,17 @@
32 </template>
33
34 <script setup lang="ts">
35 -import { ref, onBeforeMount, toRefs, onMounted, onBeforeUnmount } from "vue"
36 -import { useMessage, NSpin, NEmpty } from "naive-ui"
37 -import Api from "@/api"
38 -import SocAlertItem from "./SocAlertItem/SocAlertItem.vue"
35 import type { SocAlert } from "@/types/soc/alert.d"
36 import type { SocUser } from "@/types/soc/user.d"
37 +import Api from "@/api"
38 import axios from "axios"
39 +import { NEmpty, NSpin, useMessage } from "naive-ui"
40 +import { onBeforeMount, onBeforeUnmount, onMounted, ref, toRefs } from "vue"
41 +import SocAlertItem from "./SocAlertItem/SocAlertItem.vue"
42
43 const props = defineProps<{
44 usersList?: SocUser[]
45 }>()
46 -const { usersList } = toRefs(props)
47 -
46 const emit = defineEmits<{
47 (e: "bookmark"): void
48 (e: "deleted", value?: string): void
@@ -57,6 +55,8 @@ const emit = defineEmits<{
55 ): void
56 }>()
57
58 +const { usersList } = toRefs(props)
59 +
60 let reloadTimeout: NodeJS.Timeout | null = null
61 const message = useMessage()
62 const loadingBookmarks = ref(false)
frontend/src/components/soc/SocAlerts/SocAlertsFullList.vue
+14 -14
@@ -1,16 +1,16 @@
1 <template>
2 - <div class="soc-alerts-list" ref="list">
2 + <div ref="list" class="soc-alerts-list">
3 <n-split
4 + v-if="!compactMode"
5 direction="horizontal"
6 :default-size="splitDefault"
7 :resize-trigger-size="26"
8 :min="splitMin"
9 :max="splitMax"
9 - v-if="!compactMode"
10 >
11 <template #1>
12 <SocAlertsBookmarks
13 - :usersList="usersList"
13 + :users-list="usersList"
14 @bookmark="reloadAlerts()"
15 @deleted="itemDeleted($event)"
16 @loaded="bookmarksList = $event"
@@ -20,8 +20,8 @@
20 <template #2>
21 <SocAlertsList
22 :highlight="highlight"
23 - :bookmarksList="bookmarksList"
24 - :usersList="usersList"
23 + :bookmarks-list="bookmarksList"
24 + :users-list="usersList"
25 @bookmark="reloadBookmarks()"
26 @deleted="reloadBookmarks()"
27 @mounted="socAlertsCTX = $event"
@@ -39,8 +39,8 @@
39 <template v-else>
40 <SocAlertsList
41 :highlight="highlight"
42 - :bookmarksList="bookmarksList"
43 - :usersList="usersList"
42 + :bookmarks-list="bookmarksList"
43 + :users-list="usersList"
44 @bookmark="reloadBookmarks()"
45 @deleted="reloadBookmarks()"
46 @mounted="socAlertsCTX = $event"
@@ -64,7 +64,7 @@
64 >
65 <n-drawer-content title="Alerts list" closable :native-scrollbar="false">
66 <SocAlertsBookmarks
67 - :usersList="usersList"
67 + :users-list="usersList"
68 @bookmark="reloadAlerts()"
69 @deleted="itemDeleted($event)"
70 @loaded="bookmarksList = $event"
@@ -79,15 +79,15 @@
79 </template>
80
81 <script setup lang="ts">
82 -import { ref, onBeforeMount, toRefs } from "vue"
83 -import { useMessage, NSplit, NBackTop, NButton, NDrawer, NDrawerContent } from "naive-ui"
84 -import Api from "@/api"
85 -import SocAlertsBookmarks from "./SocAlertsBookmarks.vue"
86 -import SocAlertsList from "./SocAlertsList.vue"
82 import type { SocAlert } from "@/types/soc/alert.d"
88 -import Icon from "@/components/common/Icon.vue"
83 import type { SocUser } from "@/types/soc/user.d"
84 +import Api from "@/api"
85 +import Icon from "@/components/common/Icon.vue"
86 import { useResizeObserver } from "@vueuse/core"
87 +import { NBackTop, NButton, NDrawer, NDrawerContent, NSplit, useMessage } from "naive-ui"
88 +import { onBeforeMount, ref, toRefs } from "vue"
89 +import SocAlertsBookmarks from "./SocAlertsBookmarks.vue"
90 +import SocAlertsList from "./SocAlertsList.vue"
91
92 const props = defineProps<{ highlight: string | null | undefined }>()
93 const { highlight } = toRefs(props)
frontend/src/components/soc/SocAlerts/SocAlertsList.vue
+22 -22
@@ -1,20 +1,20 @@
1 <template>
2 <div class="soc-alerts-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <slot name="header"></slot>
5 <div class="grow">
6 <n-input v-model:value="alertTitle" size="small" placeholder="Search by title..." clearable />
7 </div>
8 <div class="delete-box">
9 - <n-popover :width="200" placement="bottom" style="max-height: 240px" scrollable v-if="checkedCount">
9 + <n-popover v-if="checkedCount" :width="200" placement="bottom" style="max-height: 240px" scrollable>
10 <template #trigger>
11 <n-button
12 size="small"
13 type="error"
14 ghost
15 - @click="handleDelete()"
15 :loading="loadingPurge"
16 :disabled="loadingAlerts"
17 + @click="handleDelete()"
18 >
19 <div class="flex items-center gap-2">
20 <Icon :name="TrashIcon" :size="16"></Icon>
@@ -35,7 +35,7 @@
35 </template>
36 <div class="checked-list flex flex-col gap-2">
37 <div v-for="alertId of checkedList" :key="alertId" class="w-full">
38 - <n-button size="small" @click="clearChecked(alertId)" class="!w-full !justify-start">
38 + <n-button size="small" class="!w-full !justify-start" @click="clearChecked(alertId)">
39 <template #icon>
40 <Icon :name="CloseIcon" :size="18"></Icon>
41 </template>
@@ -44,7 +44,7 @@
44 </div>
45 </div>
46 </n-popover>
47 - <n-button size="small" type="error" ghost @click="handlePurge()" :loading="loadingPurge" v-else>
47 + <n-button v-else size="small" type="error" ghost :loading="loadingPurge" @click="handlePurge()">
48 <div class="flex items-center gap-2">
49 <Icon :name="TrashIcon" :size="16"></Icon>
50 <span class="hidden xs:block">Purge</span>
@@ -53,11 +53,11 @@
53 </div>
54 <PaginationIndeterminate
55 v-model:page="page"
56 - v-model:pageSize="pageSize"
56 + v-model:page-size="pageSize"
57 v-model:sort="sort"
58 - :pageSizes="pageSizes"
59 - :showPageSizes="!compactMode"
60 - :showSort="!smallDeviceMode"
58 + :page-sizes="pageSizes"
59 + :show-page-sizes="!compactMode"
60 + :show-sort="!smallDeviceMode"
61 />
62 </div>
63
@@ -67,7 +67,8 @@
67 <SocAlertItem
68 v-for="alert of alertsList"
69 :key="alert.id"
70 - :alertData="alert.data"
70 + v-model:checked="alert.checked"
71 + :alert-data="alert.data"
72 class="item-appear item-appear-bottom item-appear-005 mb-2"
73 :is-bookmark="isBookmarked(alert.data)"
74 :users="usersList"
@@ -75,13 +76,12 @@
76 show-badges-toggle
77 show-checkbox
78 @check="toggleCheckedList(alert.id, $event)"
78 - v-model:checked="alert.checked"
79 @bookmark="bookmark()"
80 @deleted="itemDeleted(alert.id)"
81 />
82 </template>
83 <template v-else>
84 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingAlerts" />
84 + <n-empty v-if="!loadingAlerts" description="No items found" class="justify-center h-48" />
85 </template>
86 </div>
87 </n-spin>
@@ -89,17 +89,17 @@
89 </template>
90
91 <script setup lang="ts">
92 -import { ref, onBeforeMount, watch, toRefs, nextTick, onBeforeUnmount, onMounted, computed } from "vue"
93 -import { useMessage, NSpin, NEmpty, NInput, useDialog, NButton, NPopover } from "naive-ui"
94 -import Api from "@/api"
95 -import SocAlertItem from "./SocAlertItem/SocAlertItem.vue"
92 +import type { AlertsFilter } from "@/api/endpoints/soc"
93 import type { SocAlert } from "@/types/soc/alert.d"
94 import type { SocUser } from "@/types/soc/user.d"
98 -import type { AlertsFilter } from "@/api/endpoints/soc"
99 -import { useResizeObserver, watchDebounced } from "@vueuse/core"
100 -import PaginationIndeterminate from "@/components/common/PaginationIndeterminate.vue"
95 +import Api from "@/api"
96 import Icon from "@/components/common/Icon.vue"
97 +import PaginationIndeterminate from "@/components/common/PaginationIndeterminate.vue"
98 +import { useResizeObserver, watchDebounced } from "@vueuse/core"
99 import axios from "axios"
100 +import { NButton, NEmpty, NInput, NPopover, NSpin, useDialog, useMessage } from "naive-ui"
101 +import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, toRefs, watch } from "vue"
102 +import SocAlertItem from "./SocAlertItem/SocAlertItem.vue"
103 // MOCK
104 // import { alerts as alertsMock } from "./mock"
105
@@ -108,8 +108,6 @@ const props = defineProps<{
108 bookmarksList?: SocAlert[]
109 usersList?: SocUser[]
110 }>()
111 -const { highlight, bookmarksList, usersList } = toRefs(props)
112 -
111 const emit = defineEmits<{
112 (e: "bookmark"): void
113 (e: "deleted", value?: string): void
@@ -122,6 +120,8 @@ const emit = defineEmits<{
120 ): void
121 }>()
122
123 +const { highlight, bookmarksList, usersList } = toRefs(props)
124 +
125 const TrashIcon = "carbon:trash-can"
126 const CloseIcon = "carbon:close"
127
@@ -376,7 +376,7 @@ useResizeObserver(header, entries => {
376
377 onBeforeMount(() => {
378 // MOCK
379 - //alertsList.value = alertsMock as unknown as SocAlert[]
379 + // alertsList.value = alertsMock as unknown as SocAlert[]
380 getAlerts()
381 })
382
frontend/src/components/soc/SocAlerts/mock.ts
+100 -100
@@ -156,12 +156,12 @@ export const alerts = [
156 alert_note: null,
157 alert_source: "SOCFORTRESS RULE",
158 modification_history: {
159 - "1705168579.77425": {
159 + 1705168579.77425: {
160 user: "administrator",
161 user_id: 1,
162 action: "Alert created"
163 },
164 - "1705168579.823584": {
164 + 1705168579.823584: {
165 user: "administrator",
166 user_id: 1,
167 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -344,12 +344,12 @@ export const alerts = [
344 alert_note: null,
345 alert_source: "SOCFORTRESS RULE",
346 modification_history: {
347 - "1705169119.780053": {
347 + 1705169119.780053: {
348 user: "administrator",
349 user_id: 1,
350 action: "Alert created"
351 },
352 - "1705169119.827961": {
352 + 1705169119.827961: {
353 user: "administrator",
354 user_id: 1,
355 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -532,12 +532,12 @@ export const alerts = [
532 alert_note: null,
533 alert_source: "SOCFORTRESS RULE",
534 modification_history: {
535 - "1705167979.768851": {
535 + 1705167979.768851: {
536 user: "administrator",
537 user_id: 1,
538 action: "Alert created"
539 },
540 - "1705167979.819793": {
540 + 1705167979.819793: {
541 user: "administrator",
542 user_id: 1,
543 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -720,12 +720,12 @@ export const alerts = [
720 alert_note: null,
721 alert_source: "SOCFORTRESS RULE",
722 modification_history: {
723 - "1705168399.775005": {
723 + 1705168399.775005: {
724 user: "administrator",
725 user_id: 1,
726 action: "Alert created"
727 },
728 - "1705168399.82801": {
728 + 1705168399.82801: {
729 user: "administrator",
730 user_id: 1,
731 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -908,12 +908,12 @@ export const alerts = [
908 alert_note: null,
909 alert_source: "SOCFORTRESS RULE",
910 modification_history: {
911 - "1705168819.76398": {
911 + 1705168819.76398: {
912 user: "administrator",
913 user_id: 1,
914 action: "Alert created"
915 },
916 - "1705168819.814266": {
916 + 1705168819.814266: {
917 user: "administrator",
918 user_id: 1,
919 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -1096,12 +1096,12 @@ export const alerts = [
1096 alert_note: null,
1097 alert_source: "SOCFORTRESS RULE",
1098 modification_history: {
1099 - "1705169059.778518": {
1099 + 1705169059.778518: {
1100 user: "administrator",
1101 user_id: 1,
1102 action: "Alert created"
1103 },
1104 - "1705169059.836505": {
1104 + 1705169059.836505: {
1105 user: "administrator",
1106 user_id: 1,
1107 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -1284,12 +1284,12 @@ export const alerts = [
1284 alert_note: null,
1285 alert_source: "SOCFORTRESS RULE",
1286 modification_history: {
1287 - "1705167619.769534": {
1287 + 1705167619.769534: {
1288 user: "administrator",
1289 user_id: 1,
1290 action: "Alert created"
1291 },
1292 - "1705167619.818835": {
1292 + 1705167619.818835: {
1293 user: "administrator",
1294 user_id: 1,
1295 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -1472,12 +1472,12 @@ export const alerts = [
1472 alert_note: null,
1473 alert_source: "SOCFORTRESS RULE",
1474 modification_history: {
1475 - "1705167859.765731": {
1475 + 1705167859.765731: {
1476 user: "administrator",
1477 user_id: 1,
1478 action: "Alert created"
1479 },
1480 - "1705167859.811671": {
1480 + 1705167859.811671: {
1481 user: "administrator",
1482 user_id: 1,
1483 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -1660,12 +1660,12 @@ export const alerts = [
1660 alert_note: null,
1661 alert_source: "SOCFORTRESS RULE",
1662 modification_history: {
1663 - "1705168219.77573": {
1663 + 1705168219.77573: {
1664 user: "administrator",
1665 user_id: 1,
1666 action: "Alert created"
1667 },
1668 - "1705168219.828675": {
1668 + 1705168219.828675: {
1669 user: "administrator",
1670 user_id: 1,
1671 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -1848,12 +1848,12 @@ export const alerts = [
1848 alert_note: null,
1849 alert_source: "SOCFORTRESS RULE",
1850 modification_history: {
1851 - "1705168459.770737": {
1851 + 1705168459.770737: {
1852 user: "administrator",
1853 user_id: 1,
1854 action: "Alert created"
1855 },
1856 - "1705168459.833107": {
1856 + 1705168459.833107: {
1857 user: "administrator",
1858 user_id: 1,
1859 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -2036,12 +2036,12 @@ export const alerts = [
2036 alert_note: null,
2037 alert_source: "SOCFORTRESS RULE",
2038 modification_history: {
2039 - "1705168699.769288": {
2039 + 1705168699.769288: {
2040 user: "administrator",
2041 user_id: 1,
2042 action: "Alert created"
2043 },
2044 - "1705168699.82393": {
2044 + 1705168699.82393: {
2045 user: "administrator",
2046 user_id: 1,
2047 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -2224,12 +2224,12 @@ export const alerts = [
2224 alert_note: null,
2225 alert_source: "SOCFORTRESS RULE",
2226 modification_history: {
2227 - "1705168879.764017": {
2227 + 1705168879.764017: {
2228 user: "administrator",
2229 user_id: 1,
2230 action: "Alert created"
2231 },
2232 - "1705168879.817701": {
2232 + 1705168879.817701: {
2233 user: "administrator",
2234 user_id: 1,
2235 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -2412,12 +2412,12 @@ export const alerts = [
2412 alert_note: null,
2413 alert_source: "SOCFORTRESS RULE",
2414 modification_history: {
2415 - "1705168999.77058": {
2415 + 1705168999.77058: {
2416 user: "administrator",
2417 user_id: 1,
2418 action: "Alert created"
2419 },
2420 - "1705168999.817484": {
2420 + 1705168999.817484: {
2421 user: "administrator",
2422 user_id: 1,
2423 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -2600,12 +2600,12 @@ export const alerts = [
2600 alert_note: null,
2601 alert_source: "SOCFORTRESS RULE",
2602 modification_history: {
2603 - "1705167439.771146": {
2603 + 1705167439.771146: {
2604 user: "administrator",
2605 user_id: 1,
2606 action: "Alert created"
2607 },
2608 - "1705167439.81702": {
2608 + 1705167439.81702: {
2609 user: "administrator",
2610 user_id: 1,
2611 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -2788,12 +2788,12 @@ export const alerts = [
2788 alert_note: null,
2789 alert_source: "SOCFORTRESS RULE",
2790 modification_history: {
2791 - "1705167499.763375": {
2791 + 1705167499.763375: {
2792 user: "administrator",
2793 user_id: 1,
2794 action: "Alert created"
2795 },
2796 - "1705167499.822182": {
2796 + 1705167499.822182: {
2797 user: "administrator",
2798 user_id: 1,
2799 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -2976,12 +2976,12 @@ export const alerts = [
2976 alert_note: null,
2977 alert_source: "SOCFORTRESS RULE",
2978 modification_history: {
2979 - "1705167679.773684": {
2979 + 1705167679.773684: {
2980 user: "administrator",
2981 user_id: 1,
2982 action: "Alert created"
2983 },
2984 - "1705167679.826435": {
2984 + 1705167679.826435: {
2985 user: "administrator",
2986 user_id: 1,
2987 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -3164,12 +3164,12 @@ export const alerts = [
3164 alert_note: null,
3165 alert_source: "SOCFORTRESS RULE",
3166 modification_history: {
3167 - "1705167739.775084": {
3167 + 1705167739.775084: {
3168 user: "administrator",
3169 user_id: 1,
3170 action: "Alert created"
3171 },
3172 - "1705167739.822561": {
3172 + 1705167739.822561: {
3173 user: "administrator",
3174 user_id: 1,
3175 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -3352,12 +3352,12 @@ export const alerts = [
3352 alert_note: null,
3353 alert_source: "SOCFORTRESS RULE",
3354 modification_history: {
3355 - "1705167919.768401": {
3355 + 1705167919.768401: {
3356 user: "administrator",
3357 user_id: 1,
3358 action: "Alert created"
3359 },
3360 - "1705167919.813058": {
3360 + 1705167919.813058: {
3361 user: "administrator",
3362 user_id: 1,
3363 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -3540,12 +3540,12 @@ export const alerts = [
3540 alert_note: null,
3541 alert_source: "SOCFORTRESS RULE",
3542 modification_history: {
3543 - "1705168039.764918": {
3543 + 1705168039.764918: {
3544 user: "administrator",
3545 user_id: 1,
3546 action: "Alert created"
3547 },
3548 - "1705168039.809592": {
3548 + 1705168039.809592: {
3549 user: "administrator",
3550 user_id: 1,
3551 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -3728,12 +3728,12 @@ export const alerts = [
3728 alert_note: null,
3729 alert_source: "SOCFORTRESS RULE",
3730 modification_history: {
3731 - "1705168159.760292": {
3731 + 1705168159.760292: {
3732 user: "administrator",
3733 user_id: 1,
3734 action: "Alert created"
3735 },
3736 - "1705168159.810327": {
3736 + 1705168159.810327: {
3737 user: "administrator",
3738 user_id: 1,
3739 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -3916,12 +3916,12 @@ export const alerts = [
3916 alert_note: null,
3917 alert_source: "SOCFORTRESS RULE",
3918 modification_history: {
3919 - "1705168279.771562": {
3919 + 1705168279.771562: {
3920 user: "administrator",
3921 user_id: 1,
3922 action: "Alert created"
3923 },
3924 - "1705168279.821397": {
3924 + 1705168279.821397: {
3925 user: "administrator",
3926 user_id: 1,
3927 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -4104,12 +4104,12 @@ export const alerts = [
4104 alert_note: null,
4105 alert_source: "SOCFORTRESS RULE",
4106 modification_history: {
4107 - "1705168339.77171": {
4107 + 1705168339.77171: {
4108 user: "administrator",
4109 user_id: 1,
4110 action: "Alert created"
4111 },
4112 - "1705168339.818058": {
4112 + 1705168339.818058: {
4113 user: "administrator",
4114 user_id: 1,
4115 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -4292,12 +4292,12 @@ export const alerts = [
4292 alert_note: null,
4293 alert_source: "SOCFORTRESS RULE",
4294 modification_history: {
4295 - "1705009819.765604": {
4295 + 1705009819.765604: {
4296 user: "administrator",
4297 user_id: 1,
4298 action: "Alert created"
4299 },
4300 - "1705009819.813519": {
4300 + 1705009819.813519: {
4301 user: "administrator",
4302 user_id: 1,
4303 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -4480,12 +4480,12 @@ export const alerts = [
4480 alert_note: null,
4481 alert_source: "SOCFORTRESS RULE",
4482 modification_history: {
4483 - "1705168639.769476": {
4483 + 1705168639.769476: {
4484 user: "administrator",
4485 user_id: 1,
4486 action: "Alert created"
4487 },
4488 - "1705168639.823149": {
4488 + 1705168639.823149: {
4489 user: "administrator",
4490 user_id: 1,
4491 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -4668,12 +4668,12 @@ export const alerts = [
4668 alert_note: null,
4669 alert_source: "SOCFORTRESS RULE",
4670 modification_history: {
4671 - "1705097539.778607": {
4671 + 1705097539.778607: {
4672 user: "administrator",
4673 user_id: 1,
4674 action: "Alert created"
4675 },
4676 - "1705097539.82934": {
4676 + 1705097539.82934: {
4677 user: "administrator",
4678 user_id: 1,
4679 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -4856,12 +4856,12 @@ export const alerts = [
4856 alert_note: null,
4857 alert_source: "SOCFORTRESS RULE",
4858 modification_history: {
4859 - "1705168759.779238": {
4859 + 1705168759.779238: {
4860 user: "administrator",
4861 user_id: 1,
4862 action: "Alert created"
4863 },
4864 - "1705168759.835745": {
4864 + 1705168759.835745: {
4865 user: "administrator",
4866 user_id: 1,
4867 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -5044,12 +5044,12 @@ export const alerts = [
5044 alert_note: null,
5045 alert_source: "SOCFORTRESS RULE",
5046 modification_history: {
5047 - "1705166419.774267": {
5047 + 1705166419.774267: {
5048 user: "administrator",
5049 user_id: 1,
5050 action: "Alert created"
5051 },
5052 - "1705166419.832241": {
5052 + 1705166419.832241: {
5053 user: "administrator",
5054 user_id: 1,
5055 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -5232,12 +5232,12 @@ export const alerts = [
5232 alert_note: null,
5233 alert_source: "SOCFORTRESS RULE",
5234 modification_history: {
5235 - "1705168939.767156": {
5235 + 1705168939.767156: {
5236 user: "administrator",
5237 user_id: 1,
5238 action: "Alert created"
5239 },
5240 - "1705168939.818993": {
5240 + 1705168939.818993: {
5241 user: "administrator",
5242 user_id: 1,
5243 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -5420,12 +5420,12 @@ export const alerts = [
5420 alert_note: null,
5421 alert_source: "SOCFORTRESS RULE",
5422 modification_history: {
5423 - "1705166479.762014": {
5423 + 1705166479.762014: {
5424 user: "administrator",
5425 user_id: 1,
5426 action: "Alert created"
5427 },
5428 - "1705166479.80864": {
5428 + 1705166479.80864: {
5429 user: "administrator",
5430 user_id: 1,
5431 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -5608,12 +5608,12 @@ export const alerts = [
5608 alert_note: null,
5609 alert_source: "SOCFORTRESS RULE",
5610 modification_history: {
5611 - "1705166659.774677": {
5611 + 1705166659.774677: {
5612 user: "administrator",
5613 user_id: 1,
5614 action: "Alert created"
5615 },
5616 - "1705166659.825521": {
5616 + 1705166659.825521: {
5617 user: "administrator",
5618 user_id: 1,
5619 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -5796,12 +5796,12 @@ export const alerts = [
5796 alert_note: null,
5797 alert_source: "SOCFORTRESS RULE",
5798 modification_history: {
5799 - "1705166359.781171": {
5799 + 1705166359.781171: {
5800 user: "administrator",
5801 user_id: 1,
5802 action: "Alert created"
5803 },
5804 - "1705166359.834287": {
5804 + 1705166359.834287: {
5805 user: "administrator",
5806 user_id: 1,
5807 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -5984,12 +5984,12 @@ export const alerts = [
5984 alert_note: null,
5985 alert_source: "SOCFORTRESS RULE",
5986 modification_history: {
5987 - "1705167379.768553": {
5987 + 1705167379.768553: {
5988 user: "administrator",
5989 user_id: 1,
5990 action: "Alert created"
5991 },
5992 - "1705167379.821981": {
5992 + 1705167379.821981: {
5993 user: "administrator",
5994 user_id: 1,
5995 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -6172,12 +6172,12 @@ export const alerts = [
6172 alert_note: null,
6173 alert_source: "SOCFORTRESS RULE",
6174 modification_history: {
6175 - "1705166959.770099": {
6175 + 1705166959.770099: {
6176 user: "administrator",
6177 user_id: 1,
6178 action: "Alert created"
6179 },
6180 - "1705166959.818128": {
6180 + 1705166959.818128: {
6181 user: "administrator",
6182 user_id: 1,
6183 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -6360,12 +6360,12 @@ export const alerts = [
6360 alert_note: null,
6361 alert_source: "SOCFORTRESS RULE",
6362 modification_history: {
6363 - "1705167559.773929": {
6363 + 1705167559.773929: {
6364 user: "administrator",
6365 user_id: 1,
6366 action: "Alert created"
6367 },
6368 - "1705167559.824655": {
6368 + 1705167559.824655: {
6369 user: "administrator",
6370 user_id: 1,
6371 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -6548,12 +6548,12 @@ export const alerts = [
6548 alert_note: null,
6549 alert_source: "SOCFORTRESS RULE",
6550 modification_history: {
6551 - "1705166719.766816": {
6551 + 1705166719.766816: {
6552 user: "administrator",
6553 user_id: 1,
6554 action: "Alert created"
6555 },
6556 - "1705166719.81852": {
6556 + 1705166719.81852: {
6557 user: "administrator",
6558 user_id: 1,
6559 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -6736,12 +6736,12 @@ export const alerts = [
6736 alert_note: null,
6737 alert_source: "SOCFORTRESS RULE",
6738 modification_history: {
6739 - "1705167079.767064": {
6739 + 1705167079.767064: {
6740 user: "administrator",
6741 user_id: 1,
6742 action: "Alert created"
6743 },
6744 - "1705167079.811045": {
6744 + 1705167079.811045: {
6745 user: "administrator",
6746 user_id: 1,
6747 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -6924,12 +6924,12 @@ export const alerts = [
6924 alert_note: null,
6925 alert_source: "SOCFORTRESS RULE",
6926 modification_history: {
6927 - "1705166599.766987": {
6927 + 1705166599.766987: {
6928 user: "administrator",
6929 user_id: 1,
6930 action: "Alert created"
6931 },
6932 - "1705166599.827963": {
6932 + 1705166599.827963: {
6933 user: "administrator",
6934 user_id: 1,
6935 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -7112,12 +7112,12 @@ export const alerts = [
7112 alert_note: null,
7113 alert_source: "SOCFORTRESS RULE",
7114 modification_history: {
7115 - "1705167799.771157": {
7115 + 1705167799.771157: {
7116 user: "administrator",
7117 user_id: 1,
7118 action: "Alert created"
7119 },
7120 - "1705167799.823734": {
7120 + 1705167799.823734: {
7121 user: "administrator",
7122 user_id: 1,
7123 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -7300,12 +7300,12 @@ export const alerts = [
7300 alert_note: null,
7301 alert_source: "SOCFORTRESS RULE",
7302 modification_history: {
7303 - "1705166899.778191": {
7303 + 1705166899.778191: {
7304 user: "administrator",
7305 user_id: 1,
7306 action: "Alert created"
7307 },
7308 - "1705166899.832702": {
7308 + 1705166899.832702: {
7309 user: "administrator",
7310 user_id: 1,
7311 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -7488,12 +7488,12 @@ export const alerts = [
7488 alert_note: null,
7489 alert_source: "SOCFORTRESS RULE",
7490 modification_history: {
7491 - "1705166779.769931": {
7491 + 1705166779.769931: {
7492 user: "administrator",
7493 user_id: 1,
7494 action: "Alert created"
7495 },
7496 - "1705166779.819621": {
7496 + 1705166779.819621: {
7497 user: "administrator",
7498 user_id: 1,
7499 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -7676,12 +7676,12 @@ export const alerts = [
7676 alert_note: null,
7677 alert_source: "SOCFORTRESS RULE",
7678 modification_history: {
7679 - "1705167139.766918": {
7679 + 1705167139.766918: {
7680 user: "administrator",
7681 user_id: 1,
7682 action: "Alert created"
7683 },
7684 - "1705167139.81798": {
7684 + 1705167139.81798: {
7685 user: "administrator",
7686 user_id: 1,
7687 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -7864,12 +7864,12 @@ export const alerts = [
7864 alert_note: null,
7865 alert_source: "SOCFORTRESS RULE",
7866 modification_history: {
7867 - "1705168099.775378": {
7867 + 1705168099.775378: {
7868 user: "administrator",
7869 user_id: 1,
7870 action: "Alert created"
7871 },
7872 - "1705168099.832125": {
7872 + 1705168099.832125: {
7873 user: "administrator",
7874 user_id: 1,
7875 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -8052,12 +8052,12 @@ export const alerts = [
8052 alert_note: null,
8053 alert_source: "SOCFORTRESS RULE",
8054 modification_history: {
8055 - "1705167259.768425": {
8055 + 1705167259.768425: {
8056 user: "administrator",
8057 user_id: 1,
8058 action: "Alert created"
8059 },
8060 - "1705167259.814414": {
8060 + 1705167259.814414: {
8061 user: "administrator",
8062 user_id: 1,
8063 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -8240,12 +8240,12 @@ export const alerts = [
8240 alert_note: null,
8241 alert_source: "SOCFORTRESS RULE",
8242 modification_history: {
8243 - "1705166839.772684": {
8243 + 1705166839.772684: {
8244 user: "administrator",
8245 user_id: 1,
8246 action: "Alert created"
8247 },
8248 - "1705166839.825065": {
8248 + 1705166839.825065: {
8249 user: "administrator",
8250 user_id: 1,
8251 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -8428,12 +8428,12 @@ export const alerts = [
8428 alert_note: null,
8429 alert_source: "SOCFORTRESS RULE",
8430 modification_history: {
8431 - "1705167019.77548": {
8431 + 1705167019.77548: {
8432 user: "administrator",
8433 user_id: 1,
8434 action: "Alert created"
8435 },
8436 - "1705167019.827344": {
8436 + 1705167019.827344: {
8437 user: "administrator",
8438 user_id: 1,
8439 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -8616,12 +8616,12 @@ export const alerts = [
8616 alert_note: null,
8617 alert_source: "SOCFORTRESS RULE",
8618 modification_history: {
8619 - "1705167199.758733": {
8619 + 1705167199.758733: {
8620 user: "administrator",
8621 user_id: 1,
8622 action: "Alert created"
8623 },
8624 - "1705167199.807584": {
8624 + 1705167199.807584: {
8625 user: "administrator",
8626 user_id: 1,
8627 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -8804,12 +8804,12 @@ export const alerts = [
8804 alert_note: null,
8805 alert_source: "SOCFORTRESS RULE",
8806 modification_history: {
8807 - "1705167319.764755": {
8807 + 1705167319.764755: {
8808 user: "administrator",
8809 user_id: 1,
8810 action: "Alert created"
8811 },
8812 - "1705167319.815539": {
8812 + 1705167319.815539: {
8813 user: "administrator",
8814 user_id: 1,
8815 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -8992,12 +8992,12 @@ export const alerts = [
8992 alert_note: null,
8993 alert_source: "SOCFORTRESS RULE",
8994 modification_history: {
8995 - "1705168519.779179": {
8995 + 1705168519.779179: {
8996 user: "administrator",
8997 user_id: 1,
8998 action: "Alert created"
8999 },
9000 - "1705168519.830021": {
9000 + 1705168519.830021: {
9001 user: "administrator",
9002 user_id: 1,
9003 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -9180,12 +9180,12 @@ export const alerts = [
9180 alert_note: null,
9181 alert_source: "SOCFORTRESS RULE",
9182 modification_history: {
9183 - "1705166539.773169": {
9183 + 1705166539.773169: {
9184 user: "administrator",
9185 user_id: 1,
9186 action: "Alert created"
9187 },
9188 - "1705166539.824737": {
9188 + 1705166539.824737: {
9189 user: "administrator",
9190 user_id: 1,
9191 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
@@ -9368,12 +9368,12 @@ export const alerts = [
9368 alert_note: null,
9369 alert_source: "SOCFORTRESS RULE",
9370 modification_history: {
9371 - "1705169179.770221": {
9371 + 1705169179.770221: {
9372 user: "administrator",
9373 user_id: 1,
9374 action: "Alert created"
9375 },
9376 - "1705169179.823647": {
9376 + 1705169179.823647: {
9377 user: "administrator",
9378 user_id: 1,
9379 action: "updated alert: \"assets\" from \"[]\" to \"[{'asset_name': 'ANSYDWDC01', 'asset_ip': '139.180.134.102', 'asset_description': 'Microsoft Windows Server 2016 Standard', 'asset_type_id': 9, 'asset_tags': 'agent_id:102'}]\""
frontend/src/components/soc/SocCases/SocCaseAssetLink.vue
+21 -13
@@ -10,20 +10,28 @@
10 </div>
11 <div class="main-box flex justify-between gap-4">
12 <div class="content">
13 - <div class="description mt-2" v-if="link.asset_description">{{ link.asset_description }}</div>
13 + <div v-if="link.asset_description" class="description mt-2">
14 + {{ link.asset_description }}
15 + </div>
16
17 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
18 <Badge type="splitted" color="primary">
19 <template #label>Case open date</template>
18 - <template #value>{{ formatDate(link.case_open_date) }}</template>
20 + <template #value>
21 + {{ formatDate(link.case_open_date) }}
22 + </template>
23 </Badge>
24 <Badge type="splitted" color="primary">
25 <template #label>Asset id</template>
22 - <template #value>{{ link.asset_id }}</template>
26 + <template #value>
27 + {{ link.asset_id }}
28 + </template>
29 </Badge>
30 <Badge type="splitted" color="primary">
31 <template #label>Compromise status</template>
26 - <template #value>{{ link.asset_compromise_status_id || "-" }}</template>
32 + <template #value>
33 + {{ link.asset_compromise_status_id || "-" }}
34 + </template>
35 </Badge>
36 </div>
37 </div>
@@ -42,17 +50,17 @@
50 <div style="min-height: 50px">
51 <n-spin :show="loadingCase">
52 <SocCaseItem
45 - :case-data="socCase"
53 v-if="socCase"
54 + :case-data="socCase"
55 :embedded="true"
48 - @deleted="getSocCase(link.case_id)"
56 class="py-2 -mt-4"
57 + @deleted="getSocCase(link.case_id)"
58 />
59 <template v-else>
60 <n-empty
61 + v-if="!loadingCase"
62 description="No Case found"
63 class="justify-center h-28 -mt-4"
55 - v-if="!loadingCase"
64 />
65 </template>
66 </n-spin>
@@ -63,15 +71,15 @@
71 </template>
72
73 <script setup lang="ts">
66 -import Icon from "@/components/common/Icon.vue"
67 -import Badge from "@/components/common/Badge.vue"
68 -import { ref } from "vue"
74 +import type { SocCaseAssetLink } from "@/types/soc/asset.d"
75 +import type { SocCase } from "@/types/soc/case.d"
76 import Api from "@/api"
70 -import { useMessage, NSpin, NCollapse, NEmpty, NCollapseItem } from "naive-ui"
77 +import Badge from "@/components/common/Badge.vue"
78 +import Icon from "@/components/common/Icon.vue"
79 import { useSettingsStore } from "@/stores/settings"
80 import dayjs from "@/utils/dayjs"
73 -import type { SocCaseAssetLink } from "@/types/soc/asset.d"
74 -import type { SocCase } from "@/types/soc/case.d"
81 +import { NCollapse, NCollapseItem, NEmpty, NSpin, useMessage } from "naive-ui"
82 +import { ref } from "vue"
83 import SocCaseItem from "./SocCaseItem.vue"
84
85 const { link } = defineProps<{ link: SocCaseAssetLink }>()
frontend/src/components/soc/SocCases/SocCaseAssetsItem.vue
+32 -18
@@ -12,7 +12,7 @@
12 <div class="main-box flex justify-between gap-4">
13 <div class="content">
14 <div class="title" v-html="asset.asset_name"></div>
15 - <div class="description mt-2" v-if="asset.asset_description">
15 + <div v-if="asset.asset_description" class="description mt-2">
16 <template v-if="isUrlLike(asset.asset_description)">
17 <a
18 :href="asset.asset_description"
@@ -29,21 +29,31 @@
29 </code>
30 </a>
31 </template>
32 - <template v-else>{{ excerpt }}</template>
32 + <template v-else>
33 + {{ excerpt }}
34 + </template>
35 </div>
36
37 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
38 <Badge type="splitted" color="primary">
39 <template #label>Status</template>
38 - <template #value>{{ asset.analysis_status }}</template>
40 + <template #value>
41 + {{ asset.analysis_status }}
42 + </template>
43 </Badge>
44 <Badge type="splitted" color="primary">
45 <template #label>Type</template>
42 - <template #value>{{ asset.asset_type }}</template>
46 + <template #value>
47 + {{ asset.asset_type }}
48 + </template>
49 </Badge>
44 - <Badge type="splitted" color="primary" v-for="tag of tags" :key="tag.key">
45 - <template #label>{{ tag.key }}</template>
46 - <template #value v-if="tag.value !== undefined">{{ tag.value || "-" }}</template>
50 + <Badge v-for="tag of tags" :key="tag.key" type="splitted" color="primary">
51 + <template #label>
52 + {{ tag.key }}
53 + </template>
54 + <template v-if="tag.value !== undefined" #value>
55 + {{ tag.value || "-" }}
56 + </template>
57 </Badge>
58 </div>
59 </div>
@@ -61,10 +71,14 @@
71 >
72 <n-tabs type="line" animated :tabs-padding="24">
73 <n-tab-pane name="Info" tab="Info" display-directive="show">
64 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4" v-if="properties">
74 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
75 <KVCard v-for="(value, key) of properties" :key="key">
66 - <template #key>{{ key }}</template>
67 - <template #value>{{ value || "-" }}</template>
76 + <template #key>
77 + {{ key }}
78 + </template>
79 + <template #value>
80 + {{ value || "-" }}
81 + </template>
82 </KVCard>
83 </div>
84 </n-tab-pane>
@@ -90,7 +104,7 @@
104 <div v-if="asset.link?.length" class="px-4 flex flex-col gap-2">
105 <SocCaseAssetLink
106 v-for="link of asset.link"
93 - :key="link.case_id + '-' + link.asset_id"
107 + :key="`${link.case_id}-${link.asset_id}`"
108 :link="link"
109 />
110 </div>
@@ -104,21 +118,21 @@
118 </template>
119
120 <script setup lang="ts">
107 -import { computed, defineAsyncComponent, ref } from "vue"
108 -import { NModal, NTabs, NTabPane, NEmpty } from "naive-ui"
121 +import type { SocCaseAsset } from "@/types/soc/asset.d"
122 +import Badge from "@/components/common/Badge.vue"
123 import Icon from "@/components/common/Icon.vue"
124 import KVCard from "@/components/common/KVCard.vue"
111 -import Badge from "@/components/common/Badge.vue"
125 import { isUrlLike } from "@/utils"
126 import _omit from "lodash/omit"
127 import _split from "lodash/split"
128 import _upperFirst from "lodash/upperFirst"
116 -import type { SocCaseAsset } from "@/types/soc/asset.d"
117 -
118 -const SocCaseAssetLink = defineAsyncComponent(() => import("./SocCaseAssetLink.vue"))
129 +import { NEmpty, NModal, NTabPane, NTabs } from "naive-ui"
130 +import { computed, defineAsyncComponent, ref } from "vue"
131
132 const { asset } = defineProps<{ asset: SocCaseAsset }>()
133
134 +const SocCaseAssetLink = defineAsyncComponent(() => import("./SocCaseAssetLink.vue"))
135 +
136 const InfoIcon = "carbon:information"
137 const LinkIcon = "carbon:launch"
138
@@ -134,7 +148,7 @@ const excerpt = computed(() => {
148 const descriptionFull = computed(() => {
149 const text = asset.asset_description
150
137 - return text.replace(/\n/gim, "<br>") || "Empty"
151 + return text.replace(/\n/g, "<br>") || "Empty"
152 })
153
154 const tags = computed<{ key: string; value?: string }[]>(() => {
frontend/src/components/soc/SocCases/SocCaseAssetsList.vue
+7 -8
@@ -2,11 +2,11 @@
2 <div class="soc-assets-list">
3 <n-spin :show="loadingAssets" style="min-height: 50px">
4 <div class="flex flex-col gap-2 px-7 py-4 pb-0">
5 - <div class="box" v-if="assetsState">
5 + <div v-if="assetsState" class="box">
6 State:
7 <code>{{ assetsState.object_state }}</code>
8 </div>
9 - <div class="box" v-if="assetsState">
9 + <div v-if="assetsState" class="box">
10 Last update:
11 <code>{{ formatDateTime(assetsState.object_last_update) }}</code>
12 </div>
@@ -15,21 +15,20 @@
15 <SocCaseAssetsItem v-for="asset of assetsList" :key="asset.asset_id" :asset="asset" />
16 </div>
17 <template v-else>
18 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingAssets" />
18 + <n-empty v-if="!loadingAssets" description="No items found" class="justify-center h-48" />
19 </template>
20 </n-spin>
21 </div>
22 </template>
23
24 <script setup lang="ts">
25 -import { ref } from "vue"
26 -import SocCaseAssetsItem from "./SocCaseAssetsItem.vue"
25 +import type { SocCaseAsset, SocCaseAssetsState } from "@/types/soc/asset.d"
26 import Api from "@/api"
28 -import { useMessage, NSpin, NEmpty } from "naive-ui"
27 import { useSettingsStore } from "@/stores/settings"
28 import dayjs from "@/utils/dayjs"
31 -import type { SocCaseAsset, SocCaseAssetsState } from "@/types/soc/asset.d"
32 -import { onBeforeMount } from "vue"
29 +import { NEmpty, NSpin, useMessage } from "naive-ui"
30 +import { onBeforeMount, ref } from "vue"
31 +import SocCaseAssetsItem from "./SocCaseAssetsItem.vue"
32
33 const { caseId } = defineProps<{ caseId: string | number }>()
34
frontend/src/components/soc/SocCases/SocCaseItem.vue
+80 -65
@@ -4,7 +4,7 @@
4 :description="loadingDelete ? 'Deleting Soc Case' : 'Loading Soc Case'"
5 >
6 <div class="soc-case-item" :class="{ embedded }">
7 - <div class="flex flex-col gap-2 px-5 py-3" v-if="baseInfo">
7 + <div v-if="baseInfo" class="flex flex-col gap-2 px-5 py-3">
8 <div class="header-box flex justify-between">
9 <div class="flex items-center gap-2">
10 <div class="id flex items-center gap-2 cursor-pointer" @click="showDetails = true">
@@ -12,7 +12,7 @@
12 <Icon :name="InfoIcon" :size="16"></Icon>
13 </div>
14 </div>
15 - <div class="time" v-if="caseOpenDate">
15 + <div v-if="caseOpenDate" class="time">
16 <n-popover overlap placement="top-end">
17 <template #trigger>
18 <div class="flex items-center gap-2 cursor-help">
@@ -26,7 +26,7 @@
26 <n-timeline>
27 <n-timeline-item
28 type="success"
29 - :title="`Open ${openedBy ? '[' + openedBy + ']' : ''}`"
29 + :title="`Open ${openedBy ? `[${openedBy}]` : ''}`"
30 :time="formatDate(caseOpenDate)"
31 />
32 <n-timeline-item
@@ -42,7 +42,9 @@
42 <div class="main-box flex items-center justify-between gap-4">
43 <div class="content">
44 <div class="title" v-html="baseInfo.case_name"></div>
45 - <div class="description mt-2" v-if="baseInfo.case_description">{{ excerpt }}</div>
45 + <div v-if="baseInfo.case_description" class="description mt-2">
46 + {{ excerpt }}
47 + </div>
48
49 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
50 <Badge
@@ -53,27 +55,33 @@
55 <Icon :name="StatusIcon" :size="14"></Icon>
56 </template>
57 <template #label>State</template>
56 - <template #value>{{ baseInfo.state_name }}</template>
58 + <template #value>
59 + {{ baseInfo.state_name }}
60 + </template>
61 </Badge>
62 <Badge type="splitted" color="primary">
63 <template #iconLeft>
64 <Icon :name="OwnerIcon" :size="16"></Icon>
65 </template>
66 <template #label>Owner</template>
63 - <template #value>{{ baseInfo.owner }}</template>
67 + <template #value>
68 + {{ baseInfo.owner }}
69 + </template>
70 </Badge>
71 <Badge type="splitted" color="primary">
72 <template #iconLeft>
73 <Icon :name="CustomerIcon" :size="13"></Icon>
74 </template>
75 <template #label>Client</template>
70 - <template #value>{{ clientName || "-" }}</template>
76 + <template #value>
77 + {{ clientName || "-" }}
78 + </template>
79 </Badge>
80 <Badge
81 v-if="baseInfo.case_soc_id && !hideSocAlertLink"
82 type="active"
75 - @click="openSocAlert()"
83 class="cursor-pointer"
84 + @click="openSocAlert()"
85 >
86 <template #iconRight>
87 <Icon :name="LinkIcon" :size="14"></Icon>
@@ -85,25 +93,27 @@
93 <SocCaseItemActions
94 v-if="!hideSocCaseAction"
95 class="actions-box"
88 - :caseData="baseInfo"
96 + :case-data="baseInfo"
97 @closed="setClosed()"
98 @reopened="setReopened()"
99 @deleted="deleteCase()"
92 - @startDeleting="loadingDelete = true"
100 + @start-deleting="loadingDelete = true"
101 />
102 </div>
103 <div class="footer-box flex justify-between items-center gap-3">
104 <SocCaseItemActions
105 v-if="!hideSocCaseAction"
106 class="actions-box !flex-row"
99 - :caseData="baseInfo"
100 - :size="'small'"
107 + :case-data="baseInfo"
108 + size="small"
109 @closed="setClosed()"
110 @reopened="setReopened()"
111 @deleted="deleteCase()"
104 - @startDeleting="loadingDelete = true"
112 + @start-deleting="loadingDelete = true"
113 />
106 - <div class="time" v-if="caseOpenDate">{{ formatDate(caseOpenDate) }}</div>
114 + <div v-if="caseOpenDate" class="time">
115 + {{ formatDate(caseOpenDate) }}
116 + </div>
117 </div>
118 </div>
119
@@ -119,10 +129,10 @@
129 <div class="h-full w-full flex items-center justify-center">
130 <SocAlertItem
131 v-if="baseInfo?.case_soc_id"
122 - :alertId="baseInfo.case_soc_id"
132 + :alert-id="baseInfo.case_soc_id"
133 embedded
124 - hideBookmarkAction
125 - hideSocCaseAction
134 + hide-bookmark-action
135 + hide-soc-case-action
136 class="w-full"
137 />
138 </div>
@@ -133,28 +143,28 @@
143 preset="card"
144 content-class="!p-0"
145 :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
136 - :title="'SOC Case: ' + baseInfo?.case_uuid"
146 + :title="`SOC Case: ${baseInfo?.case_uuid}`"
147 :bordered="false"
148 segmented
149 >
150 <n-tabs type="line" animated :tabs-padding="24">
151 <n-tab-pane name="Info" tab="Info" display-directive="show">
152 <n-spin :show="loadingDetails">
143 - <div class="px-7 py-4" v-if="extendedInfo">
144 - <div class="flex gap-2 mb-2" v-if="tags.length">
153 + <div v-if="extendedInfo" class="px-7 py-4">
154 + <div v-if="tags.length" class="flex gap-2 mb-2">
155 <code v-for="tag of tags" :key="tag">{{ tag }}</code>
156 </div>
157 <div>{{ extendedInfo.case_name }}</div>
158 </div>
149 - <div class="flex flex-col gap-2 px-7 py-4" v-if="extendedInfo">
150 - <div class="box" v-if="baseInfo && !hideSocAlertLink">
159 + <div v-if="extendedInfo" class="flex flex-col gap-2 px-7 py-4">
160 + <div v-if="baseInfo && !hideSocAlertLink" class="box">
161 soc id:
162 <code class="cursor-pointer text-primary-color" @click="openSocAlert()">
163 #{{ baseInfo.case_soc_id }}
164 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
165 </code>
166 </div>
157 - <div class="box" v-if="extendedInfo?.protagonists && extendedInfo?.protagonists.length">
167 + <div v-if="extendedInfo?.protagonists && extendedInfo?.protagonists.length" class="box">
168 protagonists:
169 <code
170 v-for="protagonist of extendedInfo.protagonists"
@@ -165,9 +175,11 @@
175 </code>
176 </div>
177 </div>
168 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4" v-if="properties">
178 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
179 <KVCard v-for="(value, key) of properties" :key="key">
170 - <template #key>{{ key }}</template>
180 + <template #key>
181 + {{ key }}
182 + </template>
183 <template #value>
184 <template
185 v-if="key === 'customer_code' && value && value !== 'Customer Not Found'"
@@ -189,7 +201,7 @@
201 </n-spin>
202 </n-tab-pane>
203 <n-tab-pane name="Description" tab="Description" display-directive="show">
192 - <div class="p-7 pt-4" v-if="baseInfo">
204 + <div v-if="baseInfo" class="p-7 pt-4">
205 <n-input
206 :value="baseInfo.case_description"
207 type="textarea"
@@ -205,7 +217,7 @@
217 <n-tab-pane name="History" tab="History" display-directive="show:lazy">
218 <n-spin :show="loadingDetails">
219 <div class="p-7 pt-4">
208 - <SocCaseTimeline :caseData="extendedInfo" v-if="extendedInfo" />
220 + <SocCaseTimeline v-if="extendedInfo" :case-data="extendedInfo" />
221 </div>
222 </n-spin>
223 </n-tab-pane>
@@ -214,7 +226,7 @@
226 </n-tab-pane>
227 <n-tab-pane name="Notes" tab="Notes" display-directive="show:lazy">
228 <div class="px-4">
217 - <n-collapse display-directive="show" v-model:expanded-names="noteFormVisible">
229 + <n-collapse v-model:expanded-names="noteFormVisible" display-directive="show">
230 <template #arrow>
231 <div class="mx-4 flex">
232 <Icon :name="AddIcon"></Icon>
@@ -236,7 +248,7 @@
248 </n-collapse>
249 </div>
250 <n-divider class="!my-2" />
239 - <SocCaseNotesList v-if="baseInfo" :case-id="baseInfo.case_id" v-model:requested="updateNotes" />
251 + <SocCaseNotesList v-if="baseInfo" v-model:requested="updateNotes" :case-id="baseInfo.case_id" />
252 </n-tab-pane>
253 </n-tabs>
254 </n-modal>
@@ -245,38 +257,31 @@
257 </template>
258
259 <script setup lang="ts">
248 -import { computed, defineAsyncComponent, onBeforeMount, ref, watch } from "vue"
249 -import {
250 - useMessage,
251 - NPopover,
252 - NSpin,
253 - NTimeline,
254 - NTimelineItem,
255 - NModal,
256 - NTabs,
257 - NTabPane,
258 - NDivider,
259 - NInput,
260 - NCollapse,
261 - NCollapseItem
262 -} from "naive-ui"
260 +import Api from "@/api"
261 +import Badge from "@/components/common/Badge.vue"
262 import Icon from "@/components/common/Icon.vue"
263 import KVCard from "@/components/common/KVCard.vue"
265 -import Badge from "@/components/common/Badge.vue"
266 -import Api from "@/api"
264 +import { useGoto } from "@/composables/useGoto"
265 import { useSettingsStore } from "@/stores/settings"
266 +import { type SocCase, type SocCaseExt, StateName } from "@/types/soc/case.d"
267 import dayjs from "@/utils/dayjs"
268 import _omit from "lodash/omit"
269 import _split from "lodash/split"
271 -import { useGoto } from "@/composables/useGoto"
272 -import { type SocCase, StateName, type SocCaseExt } from "@/types/soc/case.d"
273 -
274 -const SocCaseTimeline = defineAsyncComponent(() => import("./SocCaseTimeline.vue"))
275 -const SocCaseAssetsList = defineAsyncComponent(() => import("./SocCaseAssetsList.vue"))
276 -const SocCaseNoteForm = defineAsyncComponent(() => import("./SocCaseNoteForm.vue"))
277 -const SocCaseNotesList = defineAsyncComponent(() => import("./SocCaseNotesList.vue"))
278 -const SocCaseItemActions = defineAsyncComponent(() => import("./SocCaseItemActions.vue"))
279 -const SocAlertItem = defineAsyncComponent(() => import("../SocAlerts/SocAlertItem/SocAlertItem.vue"))
270 +import {
271 + NCollapse,
272 + NCollapseItem,
273 + NDivider,
274 + NInput,
275 + NModal,
276 + NPopover,
277 + NSpin,
278 + NTabPane,
279 + NTabs,
280 + NTimeline,
281 + NTimelineItem,
282 + useMessage
283 +} from "naive-ui"
284 +import { computed, defineAsyncComponent, onBeforeMount, ref, watch } from "vue"
285
286 const { caseData, caseId, embedded, hideSocCaseAction, hideSocAlertLink } = defineProps<{
287 caseData?: SocCase
@@ -285,10 +290,15 @@ const { caseData, caseId, embedded, hideSocCaseAction, hideSocAlertLink } = defi
290 hideSocAlertLink?: boolean
291 hideSocCaseAction?: boolean
292 }>()
288 -
293 const emit = defineEmits<{
294 (e: "deleted"): void
295 }>()
296 +const SocCaseTimeline = defineAsyncComponent(() => import("./SocCaseTimeline.vue"))
297 +const SocCaseAssetsList = defineAsyncComponent(() => import("./SocCaseAssetsList.vue"))
298 +const SocCaseNoteForm = defineAsyncComponent(() => import("./SocCaseNoteForm.vue"))
299 +const SocCaseNotesList = defineAsyncComponent(() => import("./SocCaseNotesList.vue"))
300 +const SocCaseItemActions = defineAsyncComponent(() => import("./SocCaseItemActions.vue"))
301 +const SocAlertItem = defineAsyncComponent(() => import("../SocAlerts/SocAlertItem/SocAlertItem.vue"))
302
303 const TimeIcon = "carbon:time"
304 const InfoIcon = "carbon:information"
@@ -307,46 +317,51 @@ const message = useMessage()
317 const noteFormVisible = ref([])
318 const updateNotes = ref(false)
319
310 -const baseInfo = computed<SocCase | SocCaseExt | null>(() => caseData || extendedInfo.value)
320 const extendedInfo = ref<SocCaseExt | null>(null)
321 +const baseInfo = computed<(SocCase & Partial<SocCaseExt>) | null>(() => {
322 + if (caseData) {
323 + return { ...caseData, ...(extendedInfo.value || {}) }
324 + }
325 + return null
326 +})
327
328 const dFormats = useSettingsStore().dateFormat
329
330 const caseOpenDate = computed<string | null>(() => {
316 - if (baseInfo.value && "case_open_date" in baseInfo.value) {
331 + if (baseInfo.value?.case_open_date) {
332 return baseInfo.value.case_open_date as string
333 }
319 - if (baseInfo.value && "open_date" in baseInfo.value) {
334 + if (baseInfo.value?.open_date) {
335 return baseInfo.value.open_date as string
336 }
322 - if (extendedInfo.value && "open_date" in extendedInfo.value) {
337 + if (extendedInfo.value?.open_date) {
338 return extendedInfo.value.open_date as string
339 }
340 return null
341 })
342
343 const caseCloseDate = computed<string | null>(() => {
329 - if (baseInfo.value && "case_close_date" in baseInfo.value) {
344 + if (baseInfo.value?.case_close_date) {
345 return baseInfo.value.case_close_date as string
346 }
332 - if (baseInfo.value && "close_date" in baseInfo.value) {
347 + if (baseInfo.value?.close_date) {
348 return baseInfo.value.close_date as string
349 }
335 - if (extendedInfo.value && "close_date" in extendedInfo.value) {
350 + if (extendedInfo.value?.close_date) {
351 return extendedInfo.value.close_date as string
352 }
353 return null
354 })
355
356 const clientName = computed<string | null>(() => {
342 - if (baseInfo.value && "client_name" in baseInfo.value) {
357 + if (baseInfo.value?.client_name) {
358 return baseInfo.value.client_name as string
359 }
360 return null
361 })
362
363 const openedBy = computed<string | null>(() => {
349 - if (baseInfo.value && "opened_by" in baseInfo.value) {
364 + if (baseInfo.value?.opened_by) {
365 return baseInfo.value.opened_by as string
366 }
367 return null
frontend/src/components/soc/SocCases/SocCaseItemActions.vue
+19 -13
@@ -8,27 +8,38 @@
8 secondary
9 @click="reopenCase()"
10 >
11 - <template #icon><Icon :name="OpenIcon"></Icon></template>
11 + <template #icon>
12 + <Icon :name="OpenIcon"></Icon>
13 + </template>
14 Reopen
15 </n-button>
16 <n-button v-else :loading="loadingCaseClose" type="success" secondary :size="size" @click="closeCase()">
15 - <template #icon><Icon :name="CloseIcon"></Icon></template>
17 + <template #icon>
18 + <Icon :name="CloseIcon"></Icon>
19 + </template>
20 Close
21 </n-button>
22 <n-button :loading="loadingCaseDelete" :size="size" type="error" secondary @click="handleDelete()">
19 - <template #icon><Icon :name="DeleteIcon"></Icon></template>
23 + <template #icon>
24 + <Icon :name="DeleteIcon"></Icon>
25 + </template>
26 Delete
27 </n-button>
28 </div>
29 </template>
30
31 <script setup lang="ts">
26 -import { NButton, useDialog, useMessage } from "naive-ui"
27 -import Icon from "@/components/common/Icon.vue"
28 -import Api from "@/api"
29 -import { computed, watch, ref } from "vue"
30 -import { StateName, type SocCase, type SocCaseExt } from "@/types/soc/case.d"
32 import type { Size } from "naive-ui/es/button/src/interface"
33 +import Api from "@/api"
34 +import Icon from "@/components/common/Icon.vue"
35 +import { type SocCase, type SocCaseExt, StateName } from "@/types/soc/case.d"
36 +import { NButton, useDialog, useMessage } from "naive-ui"
37 +import { computed, ref, watch } from "vue"
38 +
39 +const { caseData, size } = defineProps<{
40 + caseData: SocCase | SocCaseExt | null
41 + size?: Size
42 +}>()
43
44 const emit = defineEmits<{
45 (e: "closed"): void
@@ -39,11 +50,6 @@ const emit = defineEmits<{
50 (e: "stopLoading"): void
51 }>()
52
42 -const { caseData, size } = defineProps<{
43 - caseData: SocCase | SocCaseExt | null
44 - size?: Size
45 -}>()
46 -
53 const DeleteIcon = "ph:trash"
54 const CloseIcon = "ph:circle-wavy-check"
55 const OpenIcon = "ph:circle-wavy-warning"
frontend/src/components/soc/SocCases/SocCaseNote.vue
+19 -11
@@ -27,7 +27,9 @@
27 <div class="main-box flex justify-between gap-4">
28 <div class="content">
29 <div class="title" v-html="note.note_title"></div>
30 - <div class="description mt-2" v-if="note.note_details.note_content">{{ excerpt }}</div>
30 + <div v-if="note.note_details.note_content" class="description mt-2">
31 + {{ excerpt }}
32 + </div>
33
34 <!--
35 <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
@@ -48,7 +50,9 @@
50 </div>
51 </div>
52 <div class="footer-box flex justify-end items-center gap-3">
51 - <div class="time">{{ formatDateTime(note.note_details.note_creationdate) }}</div>
53 + <div class="time">
54 + {{ formatDateTime(note.note_details.note_creationdate) }}
55 + </div>
56 </div>
57 </div>
58
@@ -63,10 +67,14 @@
67 >
68 <n-tabs type="line" animated :tabs-padding="24">
69 <n-tab-pane name="Info" tab="Info" display-directive="show">
66 - <div class="grid gap-2 grid-auto-fit-200 p-7 pt-4" v-if="properties">
70 + <div v-if="properties" class="grid gap-2 grid-auto-fit-200 p-7 pt-4">
71 <KVCard v-for="(value, key) of properties" :key="key">
68 - <template #key>{{ key }}</template>
69 - <template #value>{{ value || "-" }}</template>
72 + <template #key>
73 + {{ key }}
74 + </template>
75 + <template #value>
76 + {{ value || "-" }}
77 + </template>
78 </KVCard>
79 </div>
80 </n-tab-pane>
@@ -96,19 +104,19 @@
104 </template>
105
106 <script setup lang="ts">
99 -import { computed, defineAsyncComponent, ref } from "vue"
100 -import { NModal, NTabs, NTabPane, NInput, NPopover } from "naive-ui"
107 +import type { SocNote } from "@/types/soc/note.d"
108 import Icon from "@/components/common/Icon.vue"
109 import KVCard from "@/components/common/KVCard.vue"
103 -import _omit from "lodash/omit"
110 import { useSettingsStore } from "@/stores/settings"
111 import dayjs from "@/utils/dayjs"
106 -import type { SocNote } from "@/types/soc/note.d"
107 -
108 -const SocCaseNoteTimeline = defineAsyncComponent(() => import("./SocCaseNoteTimeline.vue"))
112 +import _omit from "lodash/omit"
113 +import { NInput, NModal, NPopover, NTabPane, NTabs } from "naive-ui"
114 +import { computed, defineAsyncComponent, ref } from "vue"
115
116 const { note } = defineProps<{ note: SocNote }>()
117
118 +const SocCaseNoteTimeline = defineAsyncComponent(() => import("./SocCaseNoteTimeline.vue"))
119 +
120 const InfoIcon = "carbon:information"
121 const TimeIcon = "carbon:time"
122
frontend/src/components/soc/SocCases/SocCaseNoteForm.vue
+5 -5
@@ -13,8 +13,8 @@
13 }"
14 />
15 <div class="flex gap-2 justify-end">
16 - <n-button :disabled="loading" @click="clear(true)" secondary class="!w-32">Close</n-button>
17 - <n-button :disabled="loading || !title" @click="addNote()" secondary type="primary" class="!w-32">
16 + <n-button :disabled="loading" secondary class="!w-32" @click="clear(true)">Close</n-button>
17 + <n-button :disabled="loading || !title" secondary type="primary" class="!w-32" @click="addNote()">
18 Submit
19 </n-button>
20 </div>
@@ -23,10 +23,10 @@
23 </template>
24
25 <script setup lang="ts">
26 -import { ref } from "vue"
27 -import Api from "@/api"
28 -import { useMessage, NSpin, NInput, NButton } from "naive-ui"
26 import type { SocNewNote } from "@/types/soc/note.d"
27 +import Api from "@/api"
28 +import { NButton, NInput, NSpin, useMessage } from "naive-ui"
29 +import { ref } from "vue"
30
31 const { caseId } = defineProps<{ caseId: string | number }>()
32
frontend/src/components/soc/SocCases/SocCaseNoteTimeline.vue
+3 -3
@@ -2,8 +2,8 @@
2 <n-timeline>
3 <n-timeline-item
4 v-for="(item, $index) of history"
5 - :type="$index === 0 ? 'success' : undefined"
5 :key="item.label"
6 + :type="$index === 0 ? 'success' : undefined"
7 :title="item.label"
8 :time="item.timeString"
9 :line-type="$index === history.length - 2 ? 'dashed' : undefined"
@@ -12,11 +12,11 @@
12 </template>
13
14 <script setup lang="ts">
15 +import type { SocNote } from "@/types/soc/note.d"
16 import { useSettingsStore } from "@/stores/settings"
17 import dayjs from "@/utils/dayjs"
17 -import { onBeforeMount, ref } from "vue"
18 -import type { SocNote } from "@/types/soc/note.d"
18 import { NTimeline, NTimelineItem } from "naive-ui"
19 +import { onBeforeMount, ref } from "vue"
20
21 const { note } = defineProps<{ note: SocNote }>()
22
frontend/src/components/soc/SocCases/SocCaseNotesList.vue
+7 -7
@@ -8,25 +8,25 @@
8 <SocCaseNote v-for="note of notesList" :key="note.note_id" :note="note" />
9 </div>
10 <template v-else>
11 - <n-empty description="No items found" class="justify-center h-48" v-if="!loadingNotes" />
11 + <n-empty v-if="!loadingNotes" description="No items found" class="justify-center h-48" />
12 </template>
13 </n-spin>
14 </div>
15 </template>
16
17 <script setup lang="ts">
18 -import { ref, watch, onBeforeMount } from "vue"
19 -import SocCaseNote from "./SocCaseNote.vue"
20 -import Api from "@/api"
21 -import { useMessage, NSpin, NInput, NEmpty } from "naive-ui"
18 import type { SocNote } from "@/types/soc/note.d"
19 +import Api from "@/api"
20 import { refDebounced } from "@vueuse/core"
24 -import { toRefs } from "vue"
21 import axios from "axios"
22 +import { NEmpty, NInput, NSpin, useMessage } from "naive-ui"
23 +import { onBeforeMount, ref, toRefs, watch } from "vue"
24 +import SocCaseNote from "./SocCaseNote.vue"
25 +
26 +const props = defineProps<{ caseId: string | number }>()
27
28 const requested = defineModel<boolean | undefined>("requested", { default: false })
29
29 -const props = defineProps<{ caseId: string | number }>()
30 const { caseId } = toRefs(props)
31
32 const loadingNotes = ref(false)
frontend/src/components/soc/SocCases/SocCaseTimeline.vue
+4 -4
@@ -2,8 +2,8 @@
2 <n-timeline>
3 <n-timeline-item
4 v-for="(item, $index) of history"
5 - :type="$index === 0 ? 'success' : undefined"
5 :key="item.label"
6 + :type="$index === 0 ? 'success' : undefined"
7 :title="item.label"
8 :time="item.timeString"
9 :line-type="$index === history.length - 2 ? 'dashed' : undefined"
@@ -12,12 +12,12 @@
12 </template>
13
14 <script setup lang="ts">
15 -import { useSettingsStore } from "@/stores/settings"
15 import type { SocCaseExt } from "@/types/soc/case.d"
16 +import { useSettingsStore } from "@/stores/settings"
17 import dayjs from "@/utils/dayjs"
18 -import { onBeforeMount, ref } from "vue"
18 import _toNumber from "lodash/toSafeInteger"
19 import { NTimeline, NTimelineItem } from "naive-ui"
20 +import { onBeforeMount, ref } from "vue"
21
22 const { caseData } = defineProps<{ caseData: SocCaseExt }>()
23
@@ -40,7 +40,7 @@ onBeforeMount(() => {
40 const item = caseData.modification_history[key]
41 history.value.push({
42 timeString: formatDate(_toNumber(key) * 1000, false),
43 - label: item.action + ` [${item.user}]`
43 + label: `${item.action} [${item.user}]`
44 })
45 }
46 }
frontend/src/components/soc/SocCases/SocCasesList.vue
+25 -25
@@ -1,6 +1,6 @@
1 <template>
2 <div class="soc-cases-list">
3 - <div class="header flex items-center justify-end gap-2" ref="header">
3 + <div ref="header" class="header flex items-center justify-end gap-2">
4 <div class="info grow flex gap-2">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
@@ -21,12 +21,12 @@
21 </n-popover>
22
23 <n-button
24 + v-if="casesList.length"
25 size="small"
26 type="error"
27 ghost
27 - @click="handlePurge()"
28 :loading="loadingPurge"
29 - v-if="casesList.length"
29 + @click="handlePurge()"
30 >
31 <div class="flex items-center gap-2">
32 <Icon :name="TrashIcon" :size="16"></Icon>
@@ -77,8 +77,8 @@
77 </n-input-group>
78 </div>
79 <div class="px-3 flex justify-end gap-2">
80 - <n-button size="small" @click="showFilters = false" secondary>Close</n-button>
81 - <n-button size="small" @click="getData()" type="primary" secondary>Submit</n-button>
80 + <n-button size="small" secondary @click="showFilters = false">Close</n-button>
81 + <n-button size="small" type="primary" secondary @click="getData()">Submit</n-button>
82 </div>
83 </div>
84 </n-popover>
@@ -89,52 +89,52 @@
89 <SocCaseItem
90 v-for="caseData of itemsPaginated"
91 :key="caseData.case_id"
92 - :caseData="caseData"
93 - @deleted="getData()"
92 + :case-data="caseData"
93 class="item-appear item-appear-bottom item-appear-005"
94 + @deleted="getData()"
95 />
96 </template>
97 <template v-else>
98 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
98 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
99 </template>
100 </div>
101 </n-spin>
102 <div class="footer flex justify-end">
103 <n-pagination
104 + v-if="itemsPaginated.length > 3"
105 v-model:page="currentPage"
106 :page-size="pageSize"
107 :item-count="total"
108 :page-slot="6"
108 - v-if="itemsPaginated.length > 3"
109 />
110 </div>
111 </div>
112 </template>
113
114 <script setup lang="ts">
115 -import { ref, onBeforeMount, computed, watch } from "vue"
115 +import type { CasesFilter } from "@/api/endpoints/soc"
116 +import type { DateFormatted, SocCase } from "@/types/soc/case.d"
117 +import Api from "@/api"
118 +import Icon from "@/components/common/Icon.vue"
119 +import dayjs from "@/utils/dayjs"
120 +import { useResizeObserver } from "@vueuse/core"
121 +import _cloneDeep from "lodash/cloneDeep"
122 +import _orderBy from "lodash/orderBy"
123 import {
117 - useMessage,
118 - NSpin,
119 - NPopover,
124 + NBadge,
125 NButton,
126 NEmpty,
122 - NSelect,
123 - NPagination,
127 NInputGroup,
125 - NBadge,
128 NInputNumber,
127 - useDialog
129 + NPagination,
130 + NPopover,
131 + NSelect,
132 + NSpin,
133 + useDialog,
134 + useMessage
135 } from "naive-ui"
129 -import Api from "@/api"
130 -import _cloneDeep from "lodash/cloneDeep"
131 -import _orderBy from "lodash/orderBy"
132 -import Icon from "@/components/common/Icon.vue"
133 -import { useResizeObserver } from "@vueuse/core"
134 -import type { CasesFilter } from "@/api/endpoints/soc"
135 -import type { DateFormatted, SocCase } from "@/types/soc/case.d"
136 +import { computed, onBeforeMount, ref, watch } from "vue"
137 import SocCaseItem from "./SocCaseItem.vue"
137 -import dayjs from "@/utils/dayjs"
138
139 const dialog = useDialog()
140 const message = useMessage()
frontend/src/components/soc/SocUsers/SocUserAlerts.vue
+7 -7
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loadingAlerts" :size="14">
3 - <div class="flex alert-list items-center gap-3" v-if="!loadingAlerts">
3 + <div v-if="!loadingAlerts" class="flex alert-list items-center gap-3">
4 <span :class="{ 'text-secondary-color': !alertsList.length, 'font-bold': alertsList.length }">
5 {{ alertsList.length || "No Alters" }}
6 </span>
@@ -27,10 +27,10 @@
27 <div class="h-full w-full flex items-center justify-center">
28 <SocAlertItem
29 v-if="selectedAlertId"
30 - :alertId="selectedAlertId"
30 + :alert-id="selectedAlertId"
31 embedded
32 - hideSocCaseAction
33 - hideBookmarkAction
32 + hide-soc-case-action
33 + hide-bookmark-action
34 class="w-full"
35 />
36 </div>
@@ -39,11 +39,11 @@
39
40 <script setup lang="ts">
41 import type { SocAlert } from "@/types/soc/alert.d"
42 -import { onBeforeMount, onBeforeUnmount, ref } from "vue"
42 import Api from "@/api"
44 -import { useMessage, NTooltip, NSpin, NModal } from "naive-ui"
45 -import SocAlertItem from "../SocAlerts/SocAlertItem/SocAlertItem.vue"
43 import axios from "axios"
44 +import { NModal, NSpin, NTooltip, useMessage } from "naive-ui"
45 +import { onBeforeMount, onBeforeUnmount, ref } from "vue"
46 +import SocAlertItem from "../SocAlerts/SocAlertItem/SocAlertItem.vue"
47
48 const { userId } = defineProps<{
49 userId: string | number
frontend/src/components/soc/SocUsers/SocUsersList.vue
+5 -5
@@ -55,13 +55,13 @@
55 </template>
56
57 <script setup lang="ts">
58 -import { ref, onBeforeMount, toRefs } from "vue"
59 -import { useMessage, NTable, NTooltip, NScrollbar, NSpin } from "naive-ui"
60 -import Icon from "@/components/common/Icon.vue"
61 -import SocUserAlerts from "./SocUserAlerts.vue"
62 -import Api from "@/api"
58 import type { SocAlert } from "@/types/soc/alert.d"
59 import type { SocUser } from "@/types/soc/user.d"
60 +import Api from "@/api"
61 +import Icon from "@/components/common/Icon.vue"
62 +import { NScrollbar, NSpin, NTable, NTooltip, useMessage } from "naive-ui"
63 +import { onBeforeMount, ref, toRefs } from "vue"
64 +import SocUserAlerts from "./SocUserAlerts.vue"
65
66 const props = defineProps<{ highlight: string | null | undefined }>()
67 const { highlight } = toRefs(props)
frontend/src/components/stackProvisioning/StackProvisioningButton.vue
+6 -4
@@ -1,6 +1,8 @@
1 <template>
2 <n-button :size="size" :type="type" @click="showForm = true">
3 - <template #icon><Icon :name="PackIcon"></Icon></template>
3 + <template #icon>
4 + <Icon :name="PackIcon"></Icon>
5 + </template>
6 Stack Provisioning
7 </n-button>
8
@@ -18,11 +20,11 @@
20 </template>
21
22 <script setup lang="ts">
21 -import { ref } from "vue"
22 -import { NButton, NModal } from "naive-ui"
23 +import type { Size, Type } from "naive-ui/es/button/src/interface"
24 import Icon from "@/components/common/Icon.vue"
25 +import { NButton, NModal } from "naive-ui"
26 +import { ref } from "vue"
27 import StackProvisioningList from "./StackProvisioningList.vue"
25 -import type { Size, Type } from "naive-ui/es/button/src/interface"
28
29 const { type, size } = defineProps<{
30 size?: Size
frontend/src/components/stackProvisioning/StackProvisioningItem.vue
+17 -9
@@ -1,13 +1,19 @@
1 <template>
2 <div class="item flex flex-col gap-2 px-5 py-3">
3 <div class="header-box flex justify-between gap-4">
4 - <div class="name">{{ contentPack.name }}</div>
4 + <div class="name">
5 + {{ contentPack.name }}
6 + </div>
7 </div>
8 <div class="main-box flex justify-between gap-4">
7 - <div class="description">{{ contentPack.description }}</div>
9 + <div class="description">
10 + {{ contentPack.description }}
11 + </div>
12 <div class="actions-box">
13 <n-button :loading="loadingProvision" type="success" secondary @click="provision(contentPack.name)">
10 - <template #icon><Icon :name="DeployIcon"></Icon></template>
14 + <template #icon>
15 + <Icon :name="DeployIcon"></Icon>
16 + </template>
17 Deploy
18 </n-button>
19 </div>
@@ -21,7 +27,9 @@
27 size="small"
28 @click="provision(contentPack.name)"
29 >
24 - <template #icon><Icon :name="DeployIcon"></Icon></template>
30 + <template #icon>
31 + <Icon :name="DeployIcon"></Icon>
32 + </template>
33 Deploy
34 </n-button>
35 </div>
@@ -30,18 +38,18 @@
38 </template>
39
40 <script setup lang="ts">
33 -import { ref } from "vue"
41 +import type { AvailableContentPack } from "@/types/stackProvisioning.d"
42 +import Api from "@/api"
43 import Icon from "@/components/common/Icon.vue"
44 import { NButton, useMessage } from "naive-ui"
36 -import Api from "@/api"
37 -import type { AvailableContentPack } from "@/types/stackProvisioning.d"
45 +import { ref } from "vue"
46 +
47 +const { contentPack } = defineProps<{ contentPack: AvailableContentPack }>()
48
49 const emit = defineEmits<{
50 (e: "provisioned"): void
51 }>()
52
43 -const { contentPack } = defineProps<{ contentPack: AvailableContentPack }>()
44 -
53 const DeployIcon = "mdi:package-variant-closed-check"
54 const loadingProvision = ref(false)
55 const message = useMessage()
frontend/src/components/stackProvisioning/StackProvisioningList.vue
+4 -4
@@ -6,7 +6,7 @@
6 <StackProvisioningItem v-for="item of list" :key="item.name" :content-pack="item" />
7 </template>
8 <template v-else>
9 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
9 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
10 </template>
11 </div>
12 </n-spin>
@@ -14,11 +14,11 @@
14 </template>
15
16 <script setup lang="ts">
17 -import { ref, onBeforeMount, computed } from "vue"
18 -import { useMessage, NSpin, NEmpty } from "naive-ui"
17 +import type { AvailableContentPack } from "@/types/stackProvisioning.d"
18 import Api from "@/api"
19 +import { NEmpty, NSpin, useMessage } from "naive-ui"
20 +import { computed, onBeforeMount, ref } from "vue"
21 import StackProvisioningItem from "./StackProvisioningItem.vue"
21 -import type { AvailableContentPack } from "@/types/stackProvisioning.d"
22
23 const message = useMessage()
24 const loadingList = ref(false)
frontend/src/components/threatIntel/ThreatIntelButton.vue
+6 -4
@@ -1,6 +1,8 @@
1 <template>
2 <n-button :size="size" :type="type" @click="showThreatIntelDrawer = true">
3 - <template #icon><Icon :name="ThreatIcon"></Icon></template>
3 + <template #icon>
4 + <Icon :name="ThreatIcon"></Icon>
5 + </template>
6 Threat Intel
7 </n-button>
8
@@ -18,11 +20,11 @@
20 </template>
21
22 <script setup lang="ts">
21 -import { ref, watch } from "vue"
23 +import type { Size, Type } from "naive-ui/es/button/src/interface"
24 +import Icon from "@/components/common/Icon.vue"
25 import { NButton, NDrawer, NDrawerContent } from "naive-ui"
26 +import { ref, watch } from "vue"
27 import ThreatIntelForm from "./ThreatIntelForm.vue"
24 -import Icon from "@/components/common/Icon.vue"
25 -import type { Size, Type } from "naive-ui/es/button/src/interface"
28
29 const { type, size } = defineProps<{
30 size?: Size
frontend/src/components/threatIntel/ThreatIntelEpssScore.vue
+10 -8
@@ -3,15 +3,17 @@
3 <div class="mb-4">
4 <KVCard>
5 <template #key>cve</template>
6 - <template #value>{{ cve }}</template>
6 + <template #value>
7 + {{ cve }}
8 + </template>
9 </KVCard>
10 </div>
9 - <div class="flex flex-col gap-3" v-if="epssList.length">
11 + <div v-if="epssList.length" class="flex flex-col gap-3">
12 <n-card
11 - content-class="bg-secondary-color"
12 - class="overflow-hidden item-appear item-appear-bottom item-appear-005"
13 v-for="item of epssList"
14 :key="item.___id"
15 + content-class="bg-secondary-color"
16 + class="overflow-hidden item-appear item-appear-bottom item-appear-005"
17 >
18 <div class="flex justify-between gap-8 xs:!flex-row flex-col">
19 <n-statistic class="grow" label="Date">
@@ -37,20 +39,20 @@
39 </p>
40 </div>
41 <template v-else>
40 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
42 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
43 </template>
44 </n-spin>
45 </template>
46
47 <script setup lang="ts">
46 -import { ref, onBeforeMount } from "vue"
47 -import { useMessage, NSpin, NEmpty, NCard, NStatistic } from "naive-ui"
48 +import type { EpssScore } from "@/types/threatIntel.d"
49 import Api from "@/api"
50 import KVCard from "@/components/common/KVCard.vue"
51 import { useSettingsStore } from "@/stores/settings"
52 import { formatDate } from "@/utils"
53 +import { NCard, NEmpty, NSpin, NStatistic, useMessage } from "naive-ui"
54 import { nanoid } from "nanoid"
53 -import type { EpssScore } from "@/types/threatIntel.d"
55 +import { onBeforeMount, ref } from "vue"
56
57 interface EpssScoreExt extends EpssScore {
58 ___id?: string
frontend/src/components/threatIntel/ThreatIntelForm.vue
+22 -12
@@ -8,30 +8,40 @@
8 <div class="flex justify-end">
9 <n-button type="primary" :disabled="!isValid" @click="create()">Submit</n-button>
10 </div>
11 - <div class="response" :class="{ error }" v-if="error || !!response">
12 - <div class="message" v-if="error">
11 + <div v-if="error || !!response" class="response" :class="{ error }">
12 + <div v-if="error" class="message">
13 {{ error }}
14 </div>
15 <div v-else class="list">
16 <div class="item">
17 <div class="key">type</div>
18 - <div class="value">{{ response?.type || "-" }}</div>
18 + <div class="value">
19 + {{ response?.type || "-" }}
20 + </div>
21 </div>
22 <div class="item">
23 <div class="key">value</div>
22 - <div class="value">{{ response?.value || "-" }}</div>
24 + <div class="value">
25 + {{ response?.value || "-" }}
26 + </div>
27 </div>
28 <div class="item">
29 <div class="key">ioc_source</div>
26 - <div class="value">{{ response?.ioc_source || "-" }}</div>
30 + <div class="value">
31 + {{ response?.ioc_source || "-" }}
32 + </div>
33 </div>
34 <div class="item">
35 <div class="key">comment</div>
30 - <div class="value">{{ response?.comment || "-" }}</div>
36 + <div class="value">
37 + {{ response?.comment || "-" }}
38 + </div>
39 </div>
40 <div class="item">
41 <div class="key">score</div>
34 - <div class="value">{{ response?.score || "-" }}</div>
42 + <div class="value">
43 + {{ response?.score || "-" }}
44 + </div>
45 </div>
46 <div class="item">
47 <div class="key">timestamp</div>
@@ -42,7 +52,7 @@
52 <div class="item">
53 <div class="key">report_url</div>
54 <div class="value">
45 - <a :href="response.report_url" target="_blank" v-if="response?.report_url">
55 + <a v-if="response?.report_url" :href="response.report_url" target="_blank">
56 {{ response.report_url }}
57 </a>
58 <span v-else>-</span>
@@ -51,7 +61,7 @@
61 <div class="item">
62 <div class="key">virustotal_url</div>
63 <div class="value">
54 - <a :href="response.virustotal_url" target="_blank" v-if="response?.virustotal_url">
64 + <a v-if="response?.virustotal_url" :href="response.virustotal_url" target="_blank">
65 {{ response.virustotal_url }}
66 </a>
67 <span v-else>-</span>
@@ -64,13 +74,13 @@
74 </template>
75
76 <script setup lang="ts">
67 -import { ref, computed, onMounted } from "vue"
68 -import { useMessage, NSpin, NButton, NInput } from "naive-ui"
77 +import type { ThreatIntelResponse } from "@/types/threatIntel.d"
78 import Api from "@/api"
79 import { useSettingsStore } from "@/stores/settings"
80 import { formatDate } from "@/utils"
81 import _trim from "lodash/trim"
73 -import type { ThreatIntelResponse } from "@/types/threatIntel.d"
82 +import { NButton, NInput, NSpin, useMessage } from "naive-ui"
83 +import { computed, onMounted, ref } from "vue"
84
85 const emit = defineEmits<{
86 (
frontend/src/components/threatIntel/ThreatIntelProcessEvaluationBadge.vue
+2 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <ThreatIntelProcessEvaluationProvider :processName v-slot="{ openEvaluation }">
2 + <ThreatIntelProcessEvaluationProvider v-slot="{ openEvaluation }" :process-name>
3 <code class="cursor-pointer text-primary-color" @click="openEvaluation()">
4 {{ processName }}
5 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -8,8 +8,8 @@
8 </template>
9
10 <script setup lang="ts">
11 -import ThreatIntelProcessEvaluationProvider from "./ThreatIntelProcessEvaluationProvider.vue"
11 import Icon from "@/components/common/Icon.vue"
12 +import ThreatIntelProcessEvaluationProvider from "./ThreatIntelProcessEvaluationProvider.vue"
13
14 const { processName } = defineProps<{
15 processName: string
frontend/src/components/threatIntel/ThreatIntelProcessEvaluationProvider.vue
+21 -15
@@ -1,5 +1,5 @@
1 <template>
2 - <slot :openEvaluation />
2 + <slot :open-evaluation />
3
4 <n-modal
5 v-model:show="showDetails"
@@ -11,7 +11,7 @@
11 segmented
12 >
13 <n-spin :show="loading" class="min-h-48">
14 - <n-tabs type="line" animated :tabs-padding="24" v-if="evaluation">
14 + <n-tabs v-if="evaluation" type="line" animated :tabs-padding="24">
15 <n-tab-pane
16 name="Overview"
17 tab="Overview"
@@ -23,7 +23,7 @@
23 <div class="flex justify-between gap-8 flex-wrap">
24 <n-statistic label="Rank" :value="evaluation.rank" tabular-nums />
25 <n-statistic label="EPS" :value="eps" tabular-nums />
26 - <n-statistic label="Host Prevalence" :value="evaluation.host_prev + '%'" tabular-nums />
26 + <n-statistic label="Host Prevalence" :value="`${evaluation.host_prev}%`" tabular-nums />
27 </div>
28 </n-card>
29 </div>
@@ -51,47 +51,53 @@
51 <ListPercentage
52 class="p-7 pt-4"
53 :list="evaluation.hashes"
54 - labelKey="hash"
55 - percentageKey="percentage"
54 + label-key="hash"
55 + percentage-key="percentage"
56 />
57 </n-tab-pane>
58 <n-tab-pane name="Network" tab="Network" display-directive="show:lazy">
59 - <ListPercentage class="p-7 pt-4" :list="evaluation.network" labelKey="port" percentageKey="usage" />
59 + <ListPercentage
60 + class="p-7 pt-4"
61 + :list="evaluation.network"
62 + label-key="port"
63 + percentage-key="usage"
64 + />
65 </n-tab-pane>
66 <n-tab-pane name="Parents" tab="Parents" display-directive="show:lazy">
67 <ListPercentage
68 class="p-7 pt-4"
69 :list="evaluation.parents"
65 - labelKey="name"
66 - percentageKey="percentage"
70 + label-key="name"
71 + percentage-key="percentage"
72 />
73 </n-tab-pane>
74 <n-tab-pane name="Paths" tab="Paths" display-directive="show:lazy">
75 <ListPercentage
76 class="p-7 pt-4"
77 :list="evaluation.paths"
73 - labelKey="directory"
74 - percentageKey="percentage"
78 + label-key="directory"
79 + percentage-key="percentage"
80 />
81 </n-tab-pane>
82 </n-tabs>
78 - <n-empty description="Evaluation not found" class="justify-center h-48" v-if="!loading && !evaluation" />
83 + <n-empty v-if="!loading && !evaluation" description="Evaluation not found" class="justify-center h-48" />
84 </n-spin>
85 </n-modal>
86 </template>
87
88 <script setup lang="ts">
84 -import { computed, defineAsyncComponent, ref } from "vue"
85 -import { useMessage, NModal, NSpin, NTabs, NTabPane, NStatistic, NInput, NCard, NEmpty } from "naive-ui"
89 +import type { EvaluationData } from "@/types/threatIntel.d"
90 import Api from "@/api"
87 -const ListPercentage = defineAsyncComponent(() => import("@/components/common/ListPercentage.vue"))
91 import _toSafeInteger from "lodash/toSafeInteger"
89 -import type { EvaluationData } from "@/types/threatIntel.d"
92 +import { NCard, NEmpty, NInput, NModal, NSpin, NStatistic, NTabPane, NTabs, useMessage } from "naive-ui"
93 +import { computed, defineAsyncComponent, ref } from "vue"
94
95 const { processName } = defineProps<{
96 processName: string
97 }>()
98
99 +const ListPercentage = defineAsyncComponent(() => import("@/components/common/ListPercentage.vue"))
100 +
101 const evaluation = ref<EvaluationData | null>(null)
102 const showDetails = ref<boolean>(false)
103 const loading = ref<boolean>(false)
frontend/src/components/users/ChangePassword.vue
+15 -15
@@ -24,10 +24,10 @@
24 <n-input
25 v-model:value="model.password"
26 type="password"
27 - @keydown.enter="submit"
27 size="large"
28 show-password-on="click"
29 placeholder="At least 8 characters"
30 + @keydown.enter="submit"
31 />
32 </n-form-item>
33 <n-form-item path="confirmPassword" label="Confirm Password" first>
@@ -35,10 +35,10 @@
35 v-model:value="model.confirmPassword"
36 type="password"
37 :disabled="!model.password"
38 - @keydown.enter="submit"
38 size="large"
39 show-password-on="click"
40 placeholder="At least 8 characters"
41 + @keydown.enter="submit"
42 />
43 </n-form-item>
44
@@ -53,26 +53,26 @@
53 </template>
54
55 <script setup lang="ts">
56 -import { ref, watch, computed } from "vue"
56 +import type { Size, Type } from "naive-ui/es/button/src/interface"
57 +import Api from "@/api"
58 +import Icon from "@/components/common/Icon.vue"
59 +import { useAuthStore } from "@/stores/auth"
60 import {
58 - useMessage,
61 + type FormInst,
62 + type FormItemRule,
63 + type FormRules,
64 + type FormValidationError,
65 + NButton,
66 NDrawer,
67 NDrawerContent,
68 NForm,
69 NFormItem,
70 NInput,
64 - NButton,
71 NSpin,
66 - type FormInst,
67 - type FormValidationError,
68 - type FormRules,
69 - type FormItemRule
72 + useMessage
73 } from "naive-ui"
71 -import Api from "@/api"
72 -import { useAuthStore } from "@/stores/auth"
73 -import passwordValidator from "password-validator"
74 -import Icon from "@/components/common/Icon.vue"
75 -import type { Size, Type } from "naive-ui/es/button/src/interface"
74 +import PasswordValidator from "password-validator"
75 +import { computed, ref, watch } from "vue"
76
77 const { type, size, username } = defineProps<{
78 username: string
@@ -95,7 +95,7 @@ const model = ref({
95 })
96 const formRef = ref<FormInst | null>(null)
97 const storedUserName = useAuthStore().userName
98 -const passwordSchema = new passwordValidator()
98 +const passwordSchema = new PasswordValidator()
99
100 passwordSchema
101 .is()
frontend/src/components/users/UsersList.vue
+6 -6
@@ -25,7 +25,7 @@
25 {{ user.email }}
26 </td>
27 <td style="max-width: 300px">
28 - <div class="flex justify-end" v-if="isAdmin">
28 + <div v-if="isAdmin" class="flex justify-end">
29 <n-dropdown
30 trigger="hover"
31 :options="options"
@@ -50,13 +50,13 @@
50 </template>
51
52 <script setup lang="ts">
53 -import { ref, onBeforeMount, toRefs, h } from "vue"
54 -import { useMessage, NTable, NScrollbar, NSpin, NDropdown, NButton } from "naive-ui"
55 -import Api from "@/api"
53 import type { AuthUser } from "@/types/auth.d"
57 -import ChangePassword from "./ChangePassword.vue"
58 -import { useAuthStore } from "@/stores/auth"
54 +import Api from "@/api"
55 import Icon from "@/components/common/Icon.vue"
56 +import { useAuthStore } from "@/stores/auth"
57 +import { NButton, NDropdown, NScrollbar, NSpin, NTable, useMessage } from "naive-ui"
58 +import { h, onBeforeMount, ref, toRefs } from "vue"
59 +import ChangePassword from "./ChangePassword.vue"
60
61 const props = defineProps<{ highlight: string | null | undefined }>()
62 const { highlight } = toRefs(props)
frontend/src/components/webVulnerabilityAssessment/ReportsItem.vue
+9 -8
@@ -6,13 +6,13 @@
6 </div>
7 <div class="footer-box flex justify-end">
8 <n-popconfirm
9 - @positive-click="deleteReport()"
9 v-model:show="showConfirm"
10 trigger="manual"
11 + @positive-click="deleteReport()"
12 @clickoutside="showConfirm = false"
13 >
14 <template #trigger>
15 - <div @click.stop="showConfirm = true" class="delete-btn">delete</div>
15 + <div class="delete-btn" @click.stop="showConfirm = true">delete</div>
16 </template>
17 Are you sure you want to delete the report?
18 </n-popconfirm>
@@ -27,14 +27,14 @@
27 segmented
28 >
29 <n-spin :show="loading" class="min-h-48">
30 - <n-page-header title="index" @back="pageBack" v-if="reportNavigation.length > 1" class="mb-3" />
30 + <n-page-header v-if="reportNavigation.length > 1" title="index" class="mb-3" @back="pageBack" />
31 <Suspense>
32 <Markdown :source="reportMarkdown || ''" @click="checkEvent($event)" />
33 </Suspense>
34 <n-empty
35 + v-if="!loading && !reportMarkdown"
36 description="Page not found"
37 class="justify-center h-48"
37 - v-if="!loading && !reportMarkdown"
38 />
39 </n-spin>
40 </n-modal>
@@ -45,15 +45,16 @@
45 <script setup lang="ts">
46 import type { NucleiReport } from "@/types/webVulnerabilityAssessment.d"
47 import Api from "@/api"
48 +import { NEmpty, NModal, NPageHeader, NPopconfirm, NSpin, useMessage } from "naive-ui"
49 import { computed, defineAsyncComponent, ref, watch } from "vue"
49 -import { useMessage, NSpin, NModal, NEmpty, NPageHeader, NPopconfirm } from "naive-ui"
50 -const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
50 +
51 +const { report } = defineProps<{ report: NucleiReport }>()
52
53 const emit = defineEmits<{
54 (e: "deleted"): void
55 }>()
56
56 -const { report } = defineProps<{ report: NucleiReport }>()
57 +const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
58
59 const message = useMessage()
60 const canceling = ref(false)
@@ -106,7 +107,7 @@ function checkEvent(event: PointerEvent) {
107 const href = el?.attributes.getNamedItem("href")
108 const path = href?.value || ""
109
109 - if (path && path.lastIndexOf(".md") !== -1) {
110 + if (path && path.includes(".md")) {
111 event.preventDefault()
112 event.stopImmediatePropagation()
113 openReport(path)
frontend/src/components/webVulnerabilityAssessment/ReportsList.vue
+5 -4
@@ -42,7 +42,7 @@
42 </div>
43 </template>
44 <template v-else>
45 - <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
45 + <n-empty v-if="!loading" description="No items found" class="justify-center h-48" />
46 </template>
47 </n-spin>
48
@@ -59,14 +59,15 @@
59 </n-modal>
60 </div>
61 </template>
62 +
63 <script setup lang="ts">
63 -import { computed, onBeforeMount, ref, watch } from "vue"
64 -import { NSpin, NEmpty, NPopover, NButton, NModal, useMessage } from "naive-ui"
64 import type { NucleiReport } from "@/types/webVulnerabilityAssessment.d"
65 +import Api from "@/api"
66 import Icon from "@/components/common/Icon.vue"
67 +import { NButton, NEmpty, NModal, NPopover, NSpin, useMessage } from "naive-ui"
68 +import { computed, onBeforeMount, ref, watch } from "vue"
69 import ReportsItem from "./ReportsItem.vue"
70 import ScanHostForm from "./ScanHostForm.vue"
69 -import Api from "@/api"
71
72 const InfoIcon = "carbon:information"
73 const NewReportIcon = "carbon:content-delivery-network"
frontend/src/components/webVulnerabilityAssessment/ScanHostForm.vue
+13 -13
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="creation-report-form">
3 - <n-form :label-width="80" :model="form" :rules="rules" ref="formRef">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
4 <div class="flex flex-col gap-2">
5 <div class="flex gap-4 items-start">
6 <n-form-item label="Host" path="host" class="grow">
@@ -9,12 +9,12 @@
9 </div>
10
11 <div class="flex justify-between gap-4">
12 - <n-button @click="reset()" :disabled="loading">Reset</n-button>
12 + <n-button :disabled="loading" @click="reset()">Reset</n-button>
13 <n-button
14 type="primary"
15 :disabled="!isValid"
16 - @click="validate(() => submit())"
16 :loading="submitting"
17 + @click="validate(() => submit())"
18 >
19 Submit
20 </n-button>
@@ -25,23 +25,23 @@
25 </template>
26
27 <script setup lang="ts">
28 -import { computed, onMounted, ref } from "vue"
28 +import type { ScanHostPayload } from "@/types/webVulnerabilityAssessment.d"
29 import Api from "@/api"
30 import {
31 - useMessage,
31 + type FormInst,
32 + type FormItemRule,
33 + type FormRules,
34 + type FormValidationError,
35 + type MessageReactive,
36 + NButton,
37 NForm,
38 NFormItem,
39 NInput,
35 - NButton,
40 NSpin,
37 - type FormValidationError,
38 - type FormInst,
39 - type FormRules,
40 - type MessageReactive,
41 - type FormItemRule
41 + useMessage
42 } from "naive-ui"
43 -import type { ScanHostPayload } from "@/types/webVulnerabilityAssessment.d"
43 import isURL from "validator/es/lib/isURL"
44 +import { computed, onMounted, ref } from "vue"
45
46 type FormPayload = ScanHostPayload
47
@@ -61,7 +61,7 @@ const message = useMessage()
61 const form = ref<FormPayload>(getClearForm())
62 const formRef = ref<FormInst | null>(null)
63
64 -const validateUrl = (rule: FormItemRule, value: string) => {
64 +function validateUrl(rule: FormItemRule, value: string) {
65 if (!value) {
66 return new Error("Please input a valid Host")
67 }
frontend/src/components/webVulnerabilityAssessment/WebVulnerabilityAssessmentButton.vue
+5 -3
@@ -1,14 +1,16 @@
1 <template>
2 <n-button :size="size" :type="type" @click="gotoPage()">
3 - <template #icon><Icon :name="VulnerabilityIcon" :size="16"></Icon></template>
3 + <template #icon>
4 + <Icon :name="VulnerabilityIcon" :size="16"></Icon>
5 + </template>
6 Web Vulnerability Assessment
7 </n-button>
8 </template>
9
10 <script setup lang="ts">
9 -import { NButton } from "naive-ui"
10 -import Icon from "@/components/common/Icon.vue"
11 import type { Size, Type } from "naive-ui/es/button/src/interface"
12 +import Icon from "@/components/common/Icon.vue"
13 +import { NButton } from "naive-ui"
14 import { useRouter } from "vue-router"
15
16 const { type, size } = defineProps<{
frontend/src/composables/useFullscreenSwitch.ts
+1
@@ -1,4 +1,5 @@
1 import { useFullscreen } from "@vueuse/core"
2 +
3 const { toggle, isFullscreen } = useFullscreen()
4
5 export function useFullscreenSwitch() {
frontend/src/composables/useGlobalActions.ts
+2 -2
@@ -1,7 +1,7 @@
1 -import { type MessageOptions } from "naive-ui"
1 +import type { MessageOptions } from "naive-ui"
2 import type { MessageApiInjection, MessageReactive } from "naive-ui/es/message/src/MessageProvider"
3 -import type { NotificationApiInjection, NotificationReactive } from "naive-ui/es/notification/src/NotificationProvider"
3 import type { NotificationOptions } from "naive-ui/es/notification/src/NotificationEnvironment"
4 +import type { NotificationApiInjection, NotificationReactive } from "naive-ui/es/notification/src/NotificationProvider"
5 import type { Writable } from "type-fest"
6
7 export type NotificationObject = Writable<NotificationOptions>
frontend/src/composables/useHealthchecksNotify.ts
+4 -4
@@ -1,9 +1,9 @@
1 -import { computed, watch } from "vue"
1 import { usHealthcheckStore } from "@/stores/healthcheck"
3 -import { useNotifications, type Notification } from "./useNotifications"
2 import { IndexHealth } from "@/types/indices.d"
3 import _capitalize from "lodash/capitalize"
4 +import { computed, watch } from "vue"
5 import { useGoto } from "./useGoto"
6 +import { type Notification, useNotifications } from "./useNotifications"
7
8 export function useHealthchecksNotify() {
9 return {
@@ -19,7 +19,7 @@ export function useHealthchecksNotify() {
19 usHealthcheckStore().start()
20
21 watch(uncommittedJournalEntries, (val, old) => {
22 - if (val != old) {
22 + if (val !== old) {
23 const obj: Notification = {
24 id: "uncommittedJournalEntries",
25 category: "alert",
@@ -45,7 +45,7 @@ export function useHealthchecksNotify() {
45 })
46
47 watch(clusterStatus, (val, old) => {
48 - if (val != old) {
48 + if (val !== old) {
49 const obj: Notification = {
50 id: "clusterHealth",
51 category: "alert",
frontend/src/composables/useNotifications.ts
+5 -5
@@ -1,10 +1,10 @@
1 -import { computed, h } from "vue"
2 -import dayjs from "@/utils/dayjs"
3 -import { NButton } from "naive-ui"
4 -import _uniqBy from "lodash/uniqBy"
5 -import { useGlobalActions, type NotificationObject } from "./useGlobalActions"
1 import { useSettingsStore } from "@/stores/settings"
2 +import dayjs from "@/utils/dayjs"
3 import { useStorage } from "@vueuse/core"
4 +import _uniqBy from "lodash/uniqBy"
5 +import { NButton } from "naive-ui"
6 +import { computed, h } from "vue"
7 +import { type NotificationObject, useGlobalActions } from "./useGlobalActions"
8
9 export type NotificationCategory = "alert"
10 export type NotificationType = "success" | "info" | "warning" | "error" | "default" | undefined
frontend/src/composables/useSearchDialog.ts
+1 -1
@@ -7,7 +7,7 @@ export function useSearchDialog() {
7 listener.value = cb
8 },
9 open: (): void => {
10 - listener.value && listener.value()
10 + listener.value?.()
11 }
12 }
13 }
frontend/src/composables/useStoreI18n.ts
+1 -1
@@ -1,5 +1,5 @@
1 -import { useI18n } from "vue-i18n"
1 import { useLocalesStore } from "@/stores/i18n"
2 +import { useI18n } from "vue-i18n"
3
4 export function useStoreI18n() {
5 const { t } = useI18n()
frontend/src/directives/v-shiki.ts
+1 -1
@@ -1,6 +1,6 @@
1 +import { codeThemes, getHighlighter } from "@/utils/highlighter"
2 import flourite from "flourite"
3 import { decode } from "html-entities"
3 -import { codeThemes, getHighlighter } from "@/utils/highlighter"
4
5 const vShiki = {
6 created: async (
frontend/src/emitter.ts
+3 -3
@@ -1,8 +1,8 @@
1 -import mitt, { type Emitter as Mitt, type EventType } from "mitt"
1 +import mitt, { type EventType, type Emitter as Mitt } from "mitt"
2
3 -type Events = {
3 +interface Events {
4 "action:add-customer": void
5 }
6
7 -export const emitter = mitt<Events>()
7 +export const emitter = mitt<Omit<Events, "">>()
8 export type Emitter<T extends Record<EventType, unknown>> = Mitt<T>
frontend/src/global.d.ts
+1 -1
@@ -9,7 +9,7 @@ declare global {
9 readonly ready: Promise<undefined>
10 readonly finished: Promise<undefined>
11 readonly updateCallbackDone: Promise<undefined>
12 - skipTransition(): void
12 + skipTransition: () => void
13 }
14
15 interface CSSStyleDeclaration {
frontend/src/lang/index.ts
+2 -2
@@ -1,6 +1,6 @@
1 -export * as it from "./it"
1 +export * as de from "./de"
2 export * as en from "./en"
3 export * as es from "./es"
4 export * as fr from "./fr"
5 -export * as de from "./de"
5 +export * as it from "./it"
6 export * as jp from "./jp"
frontend/src/layouts/Blank/MainContainer.vue
+2 -2
@@ -9,10 +9,10 @@
9 </template>
10
11 <script lang="ts" setup>
12 -import { computed, ref, onMounted } from "vue"
12 +import { useThemeStore } from "@/stores/theme"
13 import { NScrollbar } from "naive-ui"
14 +import { computed, onMounted, ref } from "vue"
15 import { useRoute, useRouter } from "vue-router"
15 -import { useThemeStore } from "@/stores/theme"
16
17 const themeStore = useThemeStore()
18 const router = useRouter()
frontend/src/layouts/HorizontalNav/HeaderBar.vue
+3 -3
@@ -8,11 +8,11 @@
8 </template>
9
10 <script lang="ts" setup>
11 -import { computed } from "vue"
12 -import { NScrollbar } from "naive-ui"
13 -import Navbar from "@/layouts/common/Navbar/index.vue"
11 import Logo from "@/layouts/common/Logo.vue"
12 +import Navbar from "@/layouts/common/Navbar/index.vue"
13 import { useThemeStore } from "@/stores/theme"
14 +import { NScrollbar } from "naive-ui"
15 +import { computed } from "vue"
16
17 const themeStore = useThemeStore()
18 const isDark = computed<boolean>(() => themeStore.isThemeDark)
frontend/src/layouts/HorizontalNav/MainContainer.vue
+5 -5
@@ -5,18 +5,18 @@
5 <div class="view" :class="[{ boxed }, `route-${routeName}`]">
6 <slot></slot>
7 </div>
8 - <MainFooter :boxed="boxed" v-if="footerShown" />
8 + <MainFooter v-if="footerShown" :boxed="boxed" />
9 </n-scrollbar>
10 </div>
11 </template>
12
13 <script lang="ts" setup>
14 -import { computed, ref, onMounted } from "vue"
15 -import { NScrollbar } from "naive-ui"
16 -import { useRoute, useRouter } from "vue-router"
17 -import Toolbar from "@/layouts/common/Toolbar/index.vue"
14 import MainFooter from "@/layouts/common/MainFooter.vue"
15 +import Toolbar from "@/layouts/common/Toolbar/index.vue"
16 import { useThemeStore } from "@/stores/theme"
17 +import { NScrollbar } from "naive-ui"
18 +import { computed, onMounted, ref } from "vue"
19 +import { useRoute, useRouter } from "vue-router"
20
21 const themeStore = useThemeStore()
22 const router = useRouter()
frontend/src/layouts/HorizontalNav/Sidebar.vue
+6 -6
@@ -4,7 +4,7 @@
4 class="sidebar flex flex-col"
5 :class="{ collapsed: sidebarCollapsed, opened: !sidebarCollapsed }"
6 >
7 - <div class="sidebar-wrap grow flex flex-col" ref="sidebar">
7 + <div ref="sidebar" class="sidebar-wrap grow flex flex-col">
8 <SidebarHeader :logo-mini="sidebarClosed" />
9 <n-scrollbar>
10 <Navbar :collapsed="sidebarClosed"></Navbar>
@@ -15,14 +15,14 @@
15 </template>
16
17 <script lang="ts" setup>
18 -import { computed, onMounted, ref, watch } from "vue"
19 -import { NScrollbar } from "naive-ui"
18 +import Navbar from "@/layouts/common/Navbar/index.vue"
19 +import { useThemeStore } from "@/stores/theme"
20 import { isMobile } from "@/utils"
21 import { onClickOutside, useElementHover } from "@vueuse/core"
22 -import Navbar from "@/layouts/common/Navbar/index.vue"
23 -import SidebarHeader from "./SidebarHeader.vue"
22 +import { NScrollbar } from "naive-ui"
23 +import { computed, onMounted, ref, watch } from "vue"
24 import SidebarFooter from "./SidebarFooter.vue"
25 -import { useThemeStore } from "@/stores/theme"
25 +import SidebarHeader from "./SidebarHeader.vue"
26
27 const themeStore = useThemeStore()
28 const sidebar = ref(null)
frontend/src/layouts/HorizontalNav/SidebarFooter.vue
+2 -2
@@ -5,10 +5,10 @@
5 </template>
6
7 <script lang="ts" setup>
8 -import { computed, h, ref, toRefs } from "vue"
9 -import { NMenu } from "naive-ui"
8 import { useThemeStore } from "@/stores/theme"
9 import { renderIcon } from "@/utils"
10 +import { NMenu } from "naive-ui"
11 +import { computed, h, ref, toRefs } from "vue"
12
13 const props = withDefaults(
14 defineProps<{
frontend/src/layouts/HorizontalNav/SidebarHeader.vue
+11 -11
@@ -2,21 +2,21 @@
2 <div class="sidebar-header flex items-center justify-between">
3 <div class="logo-box grow" :class="{ mini: logoMini }">
4 <Transition name="fade" mode="out-in">
5 - <Logo :mini="false" :dark="true" class="anim-wrap" v-if="isDark && !logoMini" />
6 - <Logo :mini="false" :dark="false" class="anim-wrap" v-else-if="isLight && !logoMini" />
7 - <Logo :mini="true" :dark="true" class="anim-wrap" v-else-if="isDark && logoMini" />
8 - <Logo :mini="true" :dark="false" class="anim-wrap" v-else-if="isLight && logoMini" />
5 + <Logo v-if="isDark && !logoMini" :mini="false" :dark="true" class="anim-wrap" />
6 + <Logo v-else-if="isLight && !logoMini" :mini="false" :dark="false" class="anim-wrap" />
7 + <Logo v-else-if="isDark && logoMini" :mini="true" :dark="true" class="anim-wrap" />
8 + <Logo v-else-if="isLight && logoMini" :mini="true" :dark="false" class="anim-wrap" />
9 </Transition>
10 </div>
11 <Transition name="fade" mode="out-in">
12 - <div class="sidebar-pin flex items-center" v-if="showPin">
12 + <div v-if="showPin" class="sidebar-pin flex items-center">
13 <Icon :size="20" @click="sidebarCollapsed = !sidebarCollapsed">
14 <span class="i-large">
15 - <Iconify :icon="CircleRegular" v-if="sidebarCollapsed" />
16 - <Iconify :icon="DotCircleRegular" v-if="!sidebarCollapsed" />
15 + <Iconify v-if="sidebarCollapsed" :icon="CircleRegular" />
16 + <Iconify v-if="!sidebarCollapsed" :icon="DotCircleRegular" />
17 </span>
18 <span class="i-small">
19 - <Iconify :icon="CloseOutline" v-if="!sidebarCollapsed" />
19 + <Iconify v-if="!sidebarCollapsed" :icon="CloseOutline" />
20 </span>
21 </Icon>
22 </div>
@@ -25,11 +25,11 @@
25 </template>
26
27 <script lang="ts" setup>
28 -import { computed, toRefs } from "vue"
29 -import { useThemeStore } from "@/stores/theme"
28 import Icon from "@/components/common/Icon.vue"
31 -import { Icon as Iconify } from "@iconify/vue"
29 import Logo from "@/layouts/common/Logo.vue"
30 +import { useThemeStore } from "@/stores/theme"
31 +import { Icon as Iconify } from "@iconify/vue"
32 +import { computed, toRefs } from "vue"
33
34 const props = defineProps<{
35 logoMini?: boolean
frontend/src/layouts/HorizontalNav/index.vue
+1 -1
@@ -10,8 +10,8 @@
10
11 <script lang="ts" setup>
12 import HeaderBar from "./HeaderBar.vue"
13 -import Sidebar from "./Sidebar.vue"
13 import MainContainer from "./MainContainer.vue"
14 +import Sidebar from "./Sidebar.vue"
15 import "./main.scss"
16 </script>
17
frontend/src/layouts/common/GlobalListener.vue
+1 -1
@@ -3,8 +3,8 @@
3 </template>
4
5 <script setup>
6 -import { useMessage, useNotification } from "naive-ui"
6 import { useGlobalActions } from "@/composables/useGlobalActions"
7 +import { useMessage, useNotification } from "naive-ui"
8
9 const message = useMessage()
10 const notification = useNotification()
frontend/src/layouts/common/Logo.vue
+4 -4
@@ -1,14 +1,14 @@
1 <template>
2 - <div class="logo" v-if="isDark && !mini">
2 + <div v-if="isDark && !mini" class="logo">
3 <img src="@/assets/images/socfortress_logo.svg?url" />
4 </div>
5 - <div class="logo" v-else-if="isLight && !mini">
5 + <div v-else-if="isLight && !mini" class="logo">
6 <img src="@/assets/images/socfortress_logo.svg?url" />
7 </div>
8 - <div class="logo" v-else-if="isDark && mini">
8 + <div v-else-if="isDark && mini" class="logo">
9 <img src="@/assets/images/socfortress_logo.svg?url" />
10 </div>
11 - <div class="logo" v-else-if="isLight && mini">
11 + <div v-else-if="isLight && mini" class="logo">
12 <img src="@/assets/images/socfortress_logo.svg?url" />
13 </div>
14 </template>
frontend/src/layouts/common/MainFooter.vue
+2 -2
@@ -23,10 +23,10 @@
23 </template>
24
25 <script lang="ts" setup>
26 -import { computed, ref, toRefs } from "vue"
27 -import Icon from "@/components/common/Icon.vue"
26 import BrainIcon from "@/assets/icons/brain-icon.svg"
27 +import Icon from "@/components/common/Icon.vue"
28 import { useThemeStore } from "@/stores/theme"
29 +import { computed, ref, toRefs } from "vue"
30
31 const props = defineProps<{
32 boxed: boolean
frontend/src/layouts/common/Navbar/index.vue
+6 -6
@@ -2,6 +2,7 @@
2 <nav class="nav" :class="[{ collapsed }, mode]">
3 <n-menu
4 ref="menu"
5 + v-model:value="selectedKey"
6 :options="menuOptions"
7 :collapsed="collapsed"
8 :mode="mode"
@@ -13,7 +14,6 @@
14 class: 'main-nav'
15 })
16 }"
16 - v-model:value="selectedKey"
17 :expanded-keys="expandedKeys"
18 @update:expanded-keys="handleUpdateExpandedKeys"
19 />
@@ -21,13 +21,13 @@
21 </template>
22
23 <script lang="ts" setup>
24 -import { type MenuInst, NMenu } from "naive-ui"
25 -import getItems from "./items"
24 +import type { MenuMixedOption } from "naive-ui/es/menu/src/interface"
25 import { useThemeStore } from "@/stores/theme"
27 -import { type MenuMixedOption } from "naive-ui/es/menu/src/interface"
28 -import { computed, onBeforeMount, ref, toRefs } from "vue"
29 -import { useRouter, useRoute, type RouteRecordNormalized } from "vue-router"
26 import _uniq from "lodash/uniq"
27 +import { type MenuInst, NMenu } from "naive-ui"
28 +import { computed, onBeforeMount, ref, toRefs } from "vue"
29 +import { type RouteRecordNormalized, useRoute, useRouter } from "vue-router"
30 +import getItems from "./items"
31
32 const props = withDefaults(
33 defineProps<{
frontend/src/layouts/common/Navbar/items.tsx
+3 -3
@@ -1,9 +1,9 @@
1 +import type { MenuMixedOption } from "naive-ui/es/menu/src/interface"
2 +import IncidentManagementIcon from "@/assets/icons/alert-settings-icon.svg"
3 import { renderIcon } from "@/utils"
4 import { h } from "vue"
3 -import { RouterLink } from "vue-router"
4 -import { type MenuMixedOption } from "naive-ui/es/menu/src/interface"
5
6 -import IncidentManagementIcon from "@/assets/icons/alert-settings-icon.svg"
6 +import { RouterLink } from "vue-router"
7
8 const OverviewIcon = "carbon:dashboard"
9 const IndiciesIcon = "ph:list-magnifying-glass"
frontend/src/layouts/common/Provider.vue
+11 -11
@@ -22,22 +22,22 @@
22 </template>
23
24 <script lang="ts" setup>
25 +import type { ThemeName } from "@/types/theme.d"
26 +import type { RtlItem } from "naive-ui/es/config-provider/src/internal-interface"
27 +import GlobalListener from "@/layouts/common/GlobalListener.vue"
28 +import { useThemeStore } from "@/stores/theme"
29 +import { useWindowSize } from "@vueuse/core"
30 import {
26 - NGlobalStyle,
31 + type GlobalThemeOverrides,
32 NConfigProvider,
33 NDialogProvider,
29 - NMessageProvider,
30 - NNotificationProvider,
34 + NGlobalStyle,
35 NLoadingBarProvider,
32 - type GlobalThemeOverrides
36 + NMessageProvider,
37 + NNotificationProvider
38 } from "naive-ui"
34 -import { useThemeStore } from "@/stores/theme"
35 -import { useWindowSize } from "@vueuse/core"
39 import { computed, watch } from "vue"
37 -import GlobalListener from "@/layouts/common/GlobalListener.vue"
38 -import type { RtlItem } from "naive-ui/es/config-provider/src/internal-interface"
40 import { rtlStyles } from "./rtlProvider"
40 -import type { ThemeName } from "@/types/theme.d"
41
42 const { width } = useWindowSize()
43 const themeStore = useThemeStore()
@@ -84,10 +84,10 @@ function setGlobalVars() {
84 body.classList.remove("direction-rtl")
85 body.classList.add("direction-ltr")
86 }
87 - //html.dir = isRTL.value ? "rtl" : "ltr"
87 + // html.dir = isRTL.value ? "rtl" : "ltr"
88 const { style: htmlStyle } = html
89 for (const key in style.value) {
90 - htmlStyle.setProperty("--" + key, style.value[key])
90 + htmlStyle.setProperty(`--${key}`, style.value[key])
91 }
92 }
93 }
frontend/src/layouts/common/SplashScreen.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <transition name="loading-fade">
3 - <div class="splash-screen-loading-box" v-if="show">
3 + <div v-if="show" class="splash-screen-loading-box">
4 <n-spin :size="80" />
5 </div>
6 </transition>
frontend/src/layouts/common/Toolbar/Avatar.vue
+3 -3
@@ -5,11 +5,11 @@
5 </template>
6
7 <script lang="ts" setup>
8 -import { NAvatar, NDropdown } from "naive-ui"
8 +import { useAuthStore } from "@/stores/auth"
9 import { renderIcon } from "@/utils"
10 +import { NAvatar, NDropdown } from "naive-ui"
11 +import { h, ref } from "vue"
12 import { useRouter } from "vue-router"
11 -import { ref, h } from "vue"
12 -import { useAuthStore } from "@/stores/auth"
13
14 const UserIcon = "ion:person-outline"
15 const LicenseIcon = "carbon:license"
frontend/src/layouts/common/Toolbar/Breadcrumb.vue
+5 -6
@@ -17,14 +17,14 @@
17 </template>
18
19 <script lang="ts" setup>
20 -import { NBreadcrumb, NBreadcrumbItem } from "naive-ui"
21 -import _upperCase from "lodash/upperCase"
20 +import type { RouteLocationNormalizedLoaded } from "vue-router"
21 +import Icon from "@/components/common/Icon.vue"
22 import _capitalize from "lodash/capitalize"
23 import _split from "lodash/split"
24 -import { type RouteLocationNormalizedLoaded } from "vue-router"
24 +import _upperCase from "lodash/upperCase"
25 +import { NBreadcrumb, NBreadcrumbItem } from "naive-ui"
26 import { onBeforeMount, ref } from "vue"
26 -import { useRouter, useRoute } from "vue-router"
27 -import Icon from "@/components/common/Icon.vue"
27 +import { useRoute, useRouter } from "vue-router"
28
29 interface Page {
30 name: string
@@ -44,7 +44,6 @@ function goto(page: Partial<Page>) {
44 }
45 if (page.path && page.path !== route.path) {
46 router.push({ path: page.path })
47 - return
47 }
48 }
49
frontend/src/layouts/common/Toolbar/FullscreenSwitch.vue
+3 -3
@@ -1,7 +1,7 @@
1 <template>
2 - <button class="fullscreen-switch" @click="toggleFullscreen" alt="fullscreen-switch" aria-label="fullscreen-switch">
3 - <Icon :size="20" :name="CloseIcon" v-if="isFullscreen"></Icon>
4 - <Icon :size="20" :name="OpenIcon" v-else></Icon>
2 + <button class="fullscreen-switch" alt="fullscreen-switch" aria-label="fullscreen-switch" @click="toggleFullscreen">
3 + <Icon v-if="isFullscreen" :size="20" :name="CloseIcon"></Icon>
4 + <Icon v-else :size="20" :name="OpenIcon"></Icon>
5 </button>
6 </template>
7
frontend/src/layouts/common/Toolbar/LocaleSwitch.vue
+1 -1
@@ -5,9 +5,9 @@
5 </template>
6
7 <script lang="ts" setup>
8 -import { NPopselect, type SelectOption } from "naive-ui"
8 import Icon from "@/components/common/Icon.vue"
9 import { useStoreI18n } from "@/composables/useStoreI18n"
10 +import { NPopselect, type SelectOption } from "naive-ui"
11 import { computed, h, type VNodeChild } from "vue"
12
13 const MultiLanguageIcon = "ion:language-outline"
frontend/src/layouts/common/Toolbar/Notifications.vue
+5 -5
@@ -11,7 +11,7 @@
11 <template #default>
12 <NotificationsList :max-items="MAX_ITEMS" class="max-h-50vh">
13 <template #last>
14 - <div class="flex justify-center p-4" v-if="list.length > MAX_ITEMS">
14 + <div v-if="list.length > MAX_ITEMS" class="flex justify-center p-4">
15 <n-button text @click="showDrawer = true">View all</n-button>
16 </div>
17 </template>
@@ -33,14 +33,14 @@
33 </template>
34
35 <script lang="ts" setup>
36 -import { NButton, NText, NPopover, NBadge, NDrawer, NDrawerContent } from "naive-ui"
37 -import { computed, ref, onBeforeMount } from "vue"
38 -import { useThemeStore } from "@/stores/theme"
36 import Icon from "@/components/common/Icon.vue"
37 import NotificationsList from "@/components/common/Notifications/List.vue"
38 import NotificationsToolbar from "@/components/common/Notifications/Toolbar.vue"
42 -import { useNotifications } from "@/composables/useNotifications"
39 import { useHealthchecksNotify } from "@/composables/useHealthchecksNotify"
40 +import { useNotifications } from "@/composables/useNotifications"
41 +import { useThemeStore } from "@/stores/theme"
42 +import { NBadge, NButton, NDrawer, NDrawerContent, NPopover, NText } from "naive-ui"
43 +import { computed, onBeforeMount, ref } from "vue"
44
45 const BellIcon = "ph:bell"
46
frontend/src/layouts/common/Toolbar/PinnedPages.vue
+30 -51
@@ -1,19 +1,27 @@
1 <template>
2 - <div class="flex pinned-pages items-center gap-5">
3 - <TransitionGroup name="anim" tag="div" class="latest-list flex items-center gap-4 overflow-hidden">
2 + <div class="pinned-pages flex items-center gap-5 relative overflow-hidden py-2 justify-end w-full">
3 + <TransitionGroup name="anim" tag="div" class="flex items-center gap-4 overflow-hidden latest-list">
4 <n-tag
5 + v-for="page of latestSanitized"
6 + :key="page.name"
7 round
8 :bordered="false"
9 :closable="false"
8 - v-for="page of latestSanitized"
9 - :key="page.name"
10 + class="bg-transparent flex-shrink overflow-hidden p-0 transition-all duration-300"
11 @close="removeLatestPage(page.name)"
12 >
12 - <span class="page-name" @click="gotoPage(page.name)" :title="page.title">
13 + <span
14 + :title="page.title"
15 + class="cursor-pointer ml-0.5 overflow-hidden text-ellipsis hover:underline hover:decoration-2 hover:decoration-[var(--primary-color)]"
16 + @click="gotoPage(page.name)"
17 + >
18 {{ page.title }}
19 </span>
20 <template #icon>
16 - <div class="icon-box" @click="pinPage(page)">
21 + <div
22 + class="cursor-pointer transition-colors duration-300 mr-0.5 hover:text-[var(--primary-color)]"
23 + @click="pinPage(page)"
24 + >
25 <Icon :size="14" :name="PinnedIcon"></Icon>
26 </div>
27 </template>
@@ -33,12 +41,12 @@
41 </template>
42 <div class="flex flex-col">
43 <n-button
36 - size="small"
37 - quaternary
38 - @click="gotoPage(page.name)"
44 v-for="page of pinned"
45 :key="page.name"
46 + size="small"
47 + quaternary
48 class="!justify-start"
49 + @click="gotoPage(page.name)"
50 >
51 <template #icon>
52 <Icon
@@ -58,15 +66,15 @@
66 </template>
67
68 <script lang="ts" setup>
61 -import { useRouter, type RouteLocationNormalized, type RouteRecordName } from "vue-router"
69 +import Icon from "@/components/common/Icon.vue"
70 +import { useThemeStore } from "@/stores/theme"
71 import { type RemovableRef, useStorage } from "@vueuse/core"
63 -import { computed, type ComputedRef } from "vue"
64 -import { NTag, NPopover, NButton, NBadge } from "naive-ui"
65 -import _takeRight from "lodash/takeRight"
72 import _split from "lodash/split"
73 +import _takeRight from "lodash/takeRight"
74 import _uniqBy from "lodash/uniqBy"
68 -import Icon from "@/components/common/Icon.vue"
69 -import { useThemeStore } from "@/stores/theme"
75 +import { NBadge, NButton, NPopover, NTag } from "naive-ui"
76 +import { computed, type ComputedRef } from "vue"
77 +import { type RouteLocationNormalized, type RouteRecordName, useRouter } from "vue-router"
78
79 interface Page {
80 name: RouteRecordName | string
@@ -78,31 +86,30 @@ const PinnedIcon = "tabler:pinned"
86 const CloseIcon = "carbon:close"
87 const router = useRouter()
88 const themeStore = useThemeStore()
81 -
89 const style = computed(() => themeStore.style)
90 +const latest: RemovableRef<Page[]> = useStorage<Page[]>("latest-pages", [], sessionStorage)
91 +const pinned: RemovableRef<Page[]> = useStorage<Page[]>("pinned-pages", [], localStorage)
92
84 -const removeLatestPage = (pageName: RouteRecordName | string) => {
93 +function removeLatestPage(pageName: RouteRecordName | string) {
94 latest.value = latest.value.filter(page => page.name !== pageName)
95 return true
96 }
88 -const removePinnedPage = (pageName: RouteRecordName | string) => {
97 +function removePinnedPage(pageName: RouteRecordName | string) {
98 pinned.value = pinned.value.filter(page => page.name !== pageName)
99 return true
100 }
92 -const gotoPage = (pageName: RouteRecordName | string) => {
101 +function gotoPage(pageName: RouteRecordName | string) {
102 router.push({ name: pageName })
103 return true
104 }
105
97 -const pinPage = (page: Page) => {
106 +function pinPage(page: Page) {
107 const isPresent = pinned.value.findIndex(p => p.name === page.name) !== -1
108 if (!isPresent) {
109 pinned.value = [page, ...pinned.value]
110 }
111 return true
112 }
104 -const latest: RemovableRef<Page[]> = useStorage<Page[]>("latest-pages", [], sessionStorage)
105 -const pinned: RemovableRef<Page[]> = useStorage<Page[]>("pinned-pages", [], localStorage)
113
114 const latestSanitized: ComputedRef<Page[]> = computed(() => {
115 return _takeRight(
@@ -111,7 +118,7 @@ const latestSanitized: ComputedRef<Page[]> = computed(() => {
118 ) as Page[]
119 })
120
114 -const checkRoute = (route: RouteLocationNormalized) => {
121 +function checkRoute(route: RouteLocationNormalized) {
122 const title = route.meta?.title || _split(route.name?.toString(), "-").at(-1)
123
124 if (route.name && title && !route.meta?.skipPin) {
@@ -131,12 +138,7 @@ router.afterEach(route => {
138
139 <style lang="scss" scoped>
140 .pinned-pages {
134 - position: relative;
135 - overflow: hidden;
136 - padding: 8px 0;
141 container-type: inline-size;
138 - justify-content: flex-end;
139 - width: 100%;
142
143 :deep() {
144 .n-tag {
@@ -180,29 +182,6 @@ router.afterEach(route => {
182 }
183 }
184
183 - .page-name {
184 - cursor: pointer;
185 - margin-left: 2px;
186 - overflow: hidden;
187 - text-overflow: ellipsis;
188 -
189 - &:hover {
190 - text-decoration: underline;
191 - text-decoration-thickness: 2px;
192 - text-decoration-color: var(--primary-color);
193 - }
194 - }
195 -
196 - .icon-box {
197 - cursor: pointer;
198 - transition: color 0.3s;
199 - margin-right: 2px;
200 -
201 - &:hover {
202 - color: var(--primary-color);
203 - }
204 - }
205 -
185 .anim-move,
186 .anim-enter-active,
187 .anim-leave-active {
frontend/src/layouts/common/Toolbar/Search.vue
+3 -3
@@ -10,11 +10,11 @@
10 </template>
11
12 <script lang="ts" setup>
13 -import { onMounted, ref } from "vue"
14 -import { NText } from "naive-ui"
15 -import { getOS } from "@/utils"
13 import Icon from "@/components/common/Icon.vue"
14 import { useSearchDialog } from "@/composables/useSearchDialog"
15 +import { getOS } from "@/utils"
16 +import { NText } from "naive-ui"
17 +import { onMounted, ref } from "vue"
18
19 const SearchIcon = "ion:search-outline"
20 const commandIcon = ref("⌘")
frontend/src/layouts/common/Toolbar/ThemeSwitch.vue
+4 -4
@@ -1,5 +1,5 @@
1 <template>
2 - <button class="theme-switch" @click="toggleTheme" alt="theme-switch" aria-label="theme-switch">
2 + <button class="theme-switch" alt="theme-switch" aria-label="theme-switch" @click="toggleTheme">
3 <Transition name="rotate">
4 <Icon v-if="isThemeDark" :size="20">
5 <Iconify :icon="Sunny" class="hover"></Iconify>
@@ -14,10 +14,10 @@
14 </template>
15
16 <script lang="ts" setup>
17 -import { useThemeStore } from "@/stores/theme"
18 -import { computed, nextTick } from "vue"
17 import Icon from "@/components/common/Icon.vue"
18 +import { useThemeStore } from "@/stores/theme"
19 import { Icon as Iconify } from "@iconify/vue"
20 +import { computed, nextTick } from "vue"
21
22 const Sunny = "ion:sunny"
23 const Moon = "ion:moon"
@@ -49,7 +49,7 @@ function toggleTheme(event?: MouseEvent) {
49
50 transition.ready.then(() => {
51 const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`]
52 - //const clipPath = [`inset(50%)`, `inset(0)`]
52 + // const clipPath = [`inset(50%)`, `inset(0)`]
53
54 document.documentElement.animate(
55 {
frontend/src/layouts/common/Toolbar/index.vue
+9 -9
@@ -21,20 +21,20 @@
21 </template>
22
23 <script lang="ts" setup>
24 +import Icon from "@/components/common/Icon.vue"
25 +import { useMainStore } from "@/stores/main"
26 +import { useThemeStore } from "@/stores/theme"
27 +import { useLoadingBar } from "naive-ui"
28 import { onMounted, toRefs } from "vue"
29 +import { useRouter } from "vue-router"
30 import Logo from "../Logo.vue"
26 -import Breadcrumb from "./Breadcrumb.vue"
31 import Avatar from "./Avatar.vue"
28 -import Search from "./Search.vue"
32 +import Breadcrumb from "./Breadcrumb.vue"
33 +import FullscreenSwitch from "./FullscreenSwitch.vue"
34 +import Notifications from "./Notifications.vue"
35 import PinnedPages from "./PinnedPages.vue"
36 +import Search from "./Search.vue"
37 import ThemeSwitch from "./ThemeSwitch.vue"
31 -import Notifications from "./Notifications.vue"
32 -import FullscreenSwitch from "./FullscreenSwitch.vue"
33 -import Icon from "@/components/common/Icon.vue"
34 -import { useLoadingBar } from "naive-ui"
35 -import { useRouter } from "vue-router"
36 -import { useMainStore } from "@/stores/main"
37 -import { useThemeStore } from "@/stores/theme"
38
39 const props = defineProps<{
40 boxed: boolean
frontend/src/layouts/common/rtlProvider.ts
+9 -9
@@ -1,25 +1,28 @@
1 +import type { RtlItem } from "naive-ui/es/config-provider/src/internal-interface"
2 import {
3 unstableAlertRtl,
4 unstableAvatarGroupRtl,
5 + unstableButtonGroupRtl,
6 // unstableBadgeRtl,
7 unstableButtonRtl,
6 - unstableNotificationRtl,
7 - unstableMessageRtl,
8 - unstableDataTableRtl,
9 - unstableButtonGroupRtl,
8 unstableCardRtl,
9 unstableCheckboxRtl,
10 unstableCollapseRtl,
11 unstableCollapseTransitionRtl,
12 + unstableDataTableRtl,
13 unstableDialogRtl,
14 unstableDrawerRtl,
15 unstableDynamicInputRtl,
16 unstableFlexRtl,
18 - unstableInputRtl,
17 unstableInputNumberRtl,
18 + unstableInputRtl,
19 unstableListRtl,
20 + unstableMessageRtl,
21 + unstableNotificationRtl,
22 unstablePaginationRtl,
23 unstableRadioRtl,
24 + unstableRowRtl,
25 + unstableScrollbarRtl,
26 unstableSelectRtl,
27 unstableSpaceRtl,
28 unstableStatisticRtl,
@@ -27,11 +30,8 @@ import {
30 unstableTableRtl,
31 unstableTagRtl,
32 unstableThingRtl,
30 - unstableTreeRtl,
31 - unstableRowRtl,
32 - unstableScrollbarRtl
33 + unstableTreeRtl
34 } from "naive-ui"
34 -import type { RtlItem } from "naive-ui/es/config-provider/src/internal-interface"
35
36 const rtlStyles: RtlItem[] = [
37 unstableAlertRtl,
frontend/src/main.ts
+13 -9
@@ -1,18 +1,22 @@
1 -const meta = document.createElement("meta")
2 -meta.name = "naive-ui-style"
3 -document.head.appendChild(meta)
1 +import App from "@/App.vue"
2 +import { getI18NConf, type Locales, type MessageSchema } from "@/lang/config"
3 +import router from "@/router"
4 +import { createPinia } from "pinia"
5
6 +import { createPersistedState } from "pinia-plugin-persistedstate"
7 import { createApp } from "vue"
6 -import { createPinia } from "pinia"
8 import { createI18n } from "vue-i18n"
8 -import piniaPluginPersistedstate from "pinia-plugin-persistedstate"
9
10 -import App from "@/App.vue"
11 -import router from "@/router"
12 -import { type Locales, type MessageSchema, getI18NConf } from "@/lang/config"
10 +const meta = document.createElement("meta")
11 +meta.name = "naive-ui-style"
12 +document.head.appendChild(meta)
13
14 const pinia = createPinia()
15 -pinia.use(piniaPluginPersistedstate)
15 +pinia.use(
16 + createPersistedState({
17 + key: id => `__persisted__${id}`
18 + })
19 +)
20
21 const i18n = createI18n<MessageSchema, Locales>(getI18NConf())
22
frontend/src/router-env.d.ts
+2 -2
@@ -1,6 +1,6 @@
1 +import type { RouteMetaAuth } from "@/types/auth.d"
2 +import type { Layout } from "@/types/theme.d"
3 import "vue-router"
2 -import { Layout } from "@/types/theme.d"
3 -import { RouteMetaAuth } from "@/types/auth.d"
4
5 // To ensure it is treated as a module, add at least one `export` statement
6 export {}
frontend/src/router/index.ts
+4 -4
@@ -1,10 +1,10 @@
1 -import { createRouter, createWebHistory } from "vue-router"
2 -import Overview from "@/views/Overview.vue"
3 -import Login from "@/views/auth/Login.vue"
1 +import type { FormType } from "@/components/auth/types.d"
2 import { UserRole } from "@/types/auth.d"
3 import { Layout } from "@/types/theme.d"
4 import { authCheck } from "@/utils/auth"
7 -import type { FormType } from "@/components/auth/types.d"
5 +import Login from "@/views/auth/Login.vue"
6 +import Overview from "@/views/Overview.vue"
7 +import { createRouter, createWebHistory } from "vue-router"
8
9 const router = createRouter({
10 history: createWebHistory(import.meta.env.BASE_URL),
frontend/src/stores/auth.ts
+6 -5
@@ -1,13 +1,14 @@
1 -import { defineStore, acceptHMRUpdate } from "pinia"
2 -import { UserRole, type User, type LoginPayload } from "@/types/auth.d"
3 -import _castArray from "lodash/castArray"
1 import Api from "@/api"
5 -import * as jose from "jose"
6 -import { scopeToRole } from "@/utils/auth"
2 +import { type LoginPayload, type User, UserRole } from "@/types/auth.d"
3 import { getAvatar, getNameInitials } from "@/utils"
4 +import { scopeToRole } from "@/utils/auth"
5 +import * as jose from "jose"
6 +import _castArray from "lodash/castArray"
7 import _toLower from "lodash/toLower"
8 import _toNumber from "lodash/toNumber"
9 +import { acceptHMRUpdate, defineStore } from "pinia"
10 import SecureLS from "secure-ls"
11 +
12 const ls = new SecureLS({ encodingType: "aes", isCompression: false })
13
14 export const useAuthStore = defineStore("auth", {
frontend/src/stores/healthcheck.ts
+3 -3
@@ -1,8 +1,8 @@
1 -import _toNumber from "lodash/toNumber"
2 -import { InfluxDBAlertLevel, type InfluxDBAlert } from "@/types/healthchecks.d"
1 import Api from "@/api"
4 -import { defineStore, acceptHMRUpdate } from "pinia"
2 +import { type InfluxDBAlert, InfluxDBAlertLevel } from "@/types/healthchecks.d"
3 import { IndexHealth } from "@/types/indices.d"
4 +import _toNumber from "lodash/toNumber"
5 +import { acceptHMRUpdate, defineStore } from "pinia"
6
7 export const usHealthcheckStore = defineStore("healthcheck", {
8 state: () => ({
frontend/src/stores/i18n.ts
+1 -1
@@ -1,4 +1,4 @@
1 -import { defineStore, acceptHMRUpdate } from "pinia"
1 +import { acceptHMRUpdate, defineStore } from "pinia"
2 import { useI18n } from "vue-i18n"
3
4 export const useLocalesStore = defineStore("i18n", {
frontend/src/stores/main.ts
+2 -2
@@ -1,5 +1,5 @@
1 -import { defineStore, acceptHMRUpdate } from "pinia"
2 -import { type LoadingBarInst } from "naive-ui/es/loading-bar/src/LoadingBarProvider"
1 +import type { LoadingBarInst } from "naive-ui/es/loading-bar/src/LoadingBarProvider"
2 +import { acceptHMRUpdate, defineStore } from "pinia"
3
4 export const useMainStore = defineStore("main", {
5 state: () => ({
frontend/src/stores/settings.ts
+1 -1
@@ -1,4 +1,4 @@
1 -import { defineStore, acceptHMRUpdate } from "pinia"
1 +import { acceptHMRUpdate, defineStore } from "pinia"
2
3 export const useSettingsStore = defineStore("settings", {
4 state: () => ({
frontend/src/stores/theme.ts
+15 -14
@@ -1,12 +1,13 @@
1 -import { defineStore, acceptHMRUpdate } from "pinia"
2 -import { type ColorType, type ThemeName, Layout, RouterTransition, ThemeEnum } from "@/types/theme.d"
3 -import { type GlobalThemeOverrides, type ThemeCommonVars, darkTheme, lightTheme, useOsTheme } from "naive-ui"
1 +import type { BuiltInGlobalTheme } from "naive-ui/es/themes/interface"
2 +import tokens from "@/design-tokens.json"
3 +import { type ColorType, Layout, RouterTransition, ThemeEnum, type ThemeName } from "@/types/theme.d"
4 import { exportPrimaryShades, getThemeColors, getTypeValue, hex2rgb, type PrimaryShade } from "@/utils/theme"
5 import _get from "lodash/get"
6 -import _set from "lodash/set"
6 import _pick from "lodash/pick"
8 -import { type BuiltInGlobalTheme } from "naive-ui/es/themes/interface"
9 -import tokens from "@/design-tokens.json"
7 +import _set from "lodash/set"
8 +import { darkTheme, type GlobalThemeOverrides, lightTheme, type ThemeCommonVars, useOsTheme } from "naive-ui"
9 +import { acceptHMRUpdate, defineStore } from "pinia"
10 +
11 const osTheme = useOsTheme()
12
13 export const useThemeStore = defineStore("theme", {
@@ -49,12 +50,12 @@ export const useThemeStore = defineStore("theme", {
50 toolbarHeight: 80,
51 viewPadding: 40,
52 headerBarHeight: 60,
52 - colors: tokens["colors"],
53 - borderRadius: tokens["borderRadius"],
54 - lineHeight: tokens["lineHeight"],
55 - fontSize: tokens["fontSize"],
56 - fontFamily: tokens["fontFamily"],
57 - typography: tokens["typography"]
53 + colors: tokens.colors,
54 + borderRadius: tokens.borderRadius,
55 + lineHeight: tokens.lineHeight,
56 + fontSize: tokens.fontSize,
57 + fontFamily: tokens.fontFamily,
58 + typography: tokens.typography
59 }),
60 actions: {
61 setLayout(layout: Layout): void {
@@ -93,8 +94,8 @@ export const useThemeStore = defineStore("theme", {
94 for (const k in primaryShades) {
95 const name = k as PrimaryShade
96 const shade = primaryShades[name]
96 - const colorsTheme = this.colors[theme]
97 - const colorKey = (colorType + name) as keyof typeof colorsTheme
97 +
98 + const colorKey = `${colorType}${name}` as keyof (typeof this.colors)[typeof theme]
99 this.colors[theme][colorKey] = shade
100 }
101 }
frontend/src/types/common.d.ts
+2 -2
@@ -1,5 +1,5 @@
1 -import { AxiosError, AxiosResponse } from "axios"
2 -import { FlaskBaseResponse } from "./flask"
1 +import type { AxiosError, AxiosResponse } from "axios"
2 +import type { FlaskBaseResponse } from "./flask"
3
4 export type OsTypesFull = "Unknown" | "Windows" | "MacOS" | "UNIX" | "Linux"
5 export type OsTypesLower = "linux" | "windows" | "macos"
frontend/src/types/connectors.d.ts
+1 -1
@@ -63,7 +63,7 @@ export type ConnectorRequestPayload =
63 connector_password?: string
64 connector_api_key?: string
65 connector_extra_data?: string
66 - /*eslint no-mixed-spaces-and-tabs: "off"*/
66 + /* eslint no-mixed-spaces-and-tabs: "off" */
67 }
68
69 export enum ConnectorSupports {
frontend/src/types/incidentManagement/cases.d.ts
+16 -1
@@ -6,8 +6,23 @@ export interface Case {
6 case_creation_time: string | Date
7 case_description: string
8 assigned_to: null | string
9 + case_status: null | CaseStatus
10 + customer_code: null | string
11 alerts: Alert[]
10 - case_status: null | AlertStatus
12 }
13
14 +export type CaseStatus = AlertStatus
15 +
16 export type CasePayload = Omit<Case, "id" | "alerts">
17 +
18 +export interface CaseDataStore {
19 + id: number
20 + case_id: number
21 + bucket_name: string
22 + object_key: string
23 + file_name: string
24 + content_type: string
25 + file_size: number
26 + upload_time: Date
27 + file_hash: string
28 +}
frontend/src/types/monitoringAlerts.d.ts
+3 -3
@@ -12,7 +12,7 @@ export interface MonitoringAlert {
12 }
13
14 export enum CustomProvisionPriority {
15 - "LOW" = 1,
16 - "MEDIUM" = 2,
17 - "HIGH" = 3
15 + LOW = 1,
16 + MEDIUM = 2,
17 + HIGH = 3
18 }
frontend/src/utils/auth.ts
+20 -57
@@ -1,19 +1,13 @@
1 +import type { RouteLocationNormalized } from "vue-router"
2 import { useAuthStore } from "@/stores/auth"
3 import { type RouteMetaAuth, UserRole } from "@/types/auth.d"
3 -import { type RouteLocationNormalized } from "vue-router"
4 +import { decodeJwt } from "jose"
5 import _castArray from "lodash/castArray"
6 import _toNumber from "lodash/toNumber"
6 -import * as jose from "jose"
7
8 export function isDebounceTimeOver(lastCheck: Date | null) {
9 - const TOKEN_DEBOUNCE_TIME = useAuthStore().tokenDebounceTime
10 -
11 - if (!lastCheck) return true
12 -
13 - const timeOver = lastCheck.getTime() + _toNumber(TOKEN_DEBOUNCE_TIME) * 1000
14 - const now = new Date().getTime()
15 -
16 - return timeOver < now
9 + const debounceTime = useAuthStore().tokenDebounceTime
10 + return !lastCheck || lastCheck.getTime() + _toNumber(debounceTime) * 1000 < Date.now()
11 }
12
13 /**
@@ -22,66 +16,35 @@ export function isDebounceTimeOver(lastCheck: Date | null) {
16 */
17 export function isJwtExpiring(token: string, threshold: number): boolean {
18 try {
25 - if (!token) {
26 - return false
27 - }
28 -
29 - const { exp } = jose.decodeJwt(token)
30 - const now = new Date().getTime() / 1000
31 - const delta = (exp || 0) - threshold
32 -
33 - if (!exp) {
34 - return true
35 - }
36 -
37 - return now > delta && now < exp
38 - } catch (err) {
19 + const { exp } = decodeJwt(token) || {}
20 + return exp ? Date.now() / 1000 > exp - threshold : true
21 + } catch {
22 return false
23 }
24 }
25
26 export function authCheck(route: RouteLocationNormalized) {
44 - const meta: RouteMetaAuth = route.meta
45 - const { checkAuth, authRedirect, auth, roles } = meta
46 -
27 + const { checkAuth, authRedirect, auth, roles }: RouteMetaAuth = route.meta
28 const authStore = useAuthStore()
29
49 - if (route?.redirectedFrom?.name === "Logout") {
50 - authStore.setLogout()
51 - }
30 + if (route?.redirectedFrom?.name === "Logout") authStore.setLogout()
31
53 - if (auth === true) {
54 - if (!authStore.isLogged) {
55 - window.location.href = "/login" + window.location.search
56 - return false
57 - }
32 + if (auth && !authStore.isLogged) {
33 + window.location.href = `/login${window.location.search}`
34 + return false
35 + }
36
59 - if (roles && !authStore.isRoleGranted(roles)) {
60 - window.location.href = "/login" + window.location.search
61 - return false
62 - }
37 + if (auth && roles && !authStore.isRoleGranted(roles)) {
38 + window.location.href = `/login${window.location.search}`
39 + return false
40 }
41
65 - if (checkAuth === true) {
66 - if (authStore.isLogged) {
67 - if (roles) {
68 - if (authStore.isRoleGranted(roles)) {
69 - return authRedirect || "/"
70 - } else {
71 - return route.path
72 - }
73 - }
74 - return authRedirect || "/"
75 - }
42 + if (checkAuth && authStore.isLogged) {
43 + return roles && !authStore.isRoleGranted(roles) ? route.path : authRedirect || "/"
44 }
45 }
46
47 export function scopeToRole(scope: string | string[]): UserRole {
80 - const scopes: string[] = _castArray(scope)
81 - if (!scopes.length) return UserRole.Unknown
82 -
83 - if (scopes[0].toLowerCase() === "admin") return UserRole.Admin
84 - if (scopes[0].toLowerCase() === "analyst") return UserRole.Analyst
85 -
86 - return UserRole.Unknown
48 + const role = _castArray(scope)[0]?.toLowerCase()
49 + return role === "admin" ? UserRole.Admin : role === "analyst" ? UserRole.Analyst : UserRole.Unknown
50 }
frontend/src/utils/dayjs.ts
+1 -1
@@ -1,9 +1,9 @@
1 import dayjs from "dayjs"
2 -import timezone from "dayjs/plugin/timezone"
2 import locale_en from "dayjs/locale/en.js"
3 import customParseFormat from "dayjs/plugin/customParseFormat"
4 import duration from "dayjs/plugin/duration"
5 import relativeTime from "dayjs/plugin/relativeTime"
6 +import timezone from "dayjs/plugin/timezone"
7 import utc from "dayjs/plugin/utc"
8 /*
9 import isSameOrAfter from "dayjs/plugin/isSameOrAfter"
frontend/src/utils/highlighter.ts
+1 -1
@@ -40,7 +40,7 @@ export function resetIndent(el: HTMLElement) {
40 lines.shift()
41 }
42
43 - const matches = /^[\s\t]+/.exec(lines[0])
43 + const matches = /^\s+/.exec(lines[0])
44 const indentation = matches !== null ? matches[0] : null
45 if (indentation) {
46 lines = lines.map(line => {
frontend/src/utils/index.ts
+21 -20
@@ -1,10 +1,11 @@
1 +import type { OsTypesFull } from "@/types/common.d"
2 +import process from "node:process"
3 import Icon from "@/components/common/Icon.vue"
2 -import { type Component, h } from "vue"
4 +import dayjs from "@/utils/dayjs"
5 import { isMobile as detectMobile } from "detect-touch-device"
6 import { md5 } from "js-md5"
5 -import dayjs from "@/utils/dayjs"
6 -import type { OsTypesFull } from "@/types/common.d"
7 import _trim from "lodash/trim"
8 +import { type Component, h } from "vue"
9
10 // Transform File Instance in base64 string
11 export function file2Base64(blob: Blob): Promise<string> {
@@ -25,12 +26,12 @@ export function isEnvProd() {
26 return process.env.NODE_ENV === "production"
27 }
28
28 -export const isMobile = () => {
29 +export function isMobile() {
30 return detectMobile
31 }
32
32 -export const isUrlLike = (text: string) => {
33 - const urlPattern = new RegExp("^(https?:\\/\\/)", "i")
33 +export function isUrlLike(text: string) {
34 + const urlPattern = /^https?:\/\//i
35 return urlPattern.test(text)
36 }
37
@@ -44,18 +45,18 @@ export function renderIcon(icon: Component | string) {
45
46 export function iconFromOs(os: string): string {
47 const test = os.toLowerCase()
47 - if (test.indexOf("mac") !== -1 || test.indexOf("darwin") !== -1 || test.indexOf("apple") !== -1) {
48 + if (test.includes("mac") || test.includes("darwin") || test.includes("apple")) {
49 return "mdi:apple"
50 }
50 - if (test.indexOf("win") !== -1 || test.indexOf("microsoft") !== -1) {
51 + if (test.includes("win") || test.includes("microsoft")) {
52 return "mdi:microsoft"
53 }
54 if (
54 - test.indexOf("linux") !== -1 ||
55 - test.indexOf("unix") !== -1 ||
56 - test.indexOf("x11") !== -1 ||
57 - test.indexOf("debian") !== -1 ||
58 - test.indexOf("centos") !== -1
55 + test.includes("linux") ||
56 + test.includes("unix") ||
57 + test.includes("x11") ||
58 + test.includes("debian") ||
59 + test.includes("centos")
60 ) {
61 return "mdi:linux"
62 }
@@ -65,19 +66,19 @@ export function iconFromOs(os: string): string {
66
67 export function getOS(): OsTypesFull {
68 let os: OsTypesFull = "Unknown"
68 - if (navigator.userAgent.indexOf("Win") != -1) os = "Windows"
69 - if (navigator.userAgent.indexOf("Mac") != -1) os = "MacOS"
70 - if (navigator.userAgent.indexOf("X11") != -1) os = "UNIX"
71 - if (navigator.userAgent.indexOf("Linux") != -1) os = "Linux"
69 + if (navigator.userAgent.includes("Win")) os = "Windows"
70 + if (navigator.userAgent.includes("Mac")) os = "MacOS"
71 + if (navigator.userAgent.includes("X11")) os = "UNIX"
72 + if (navigator.userAgent.includes("Linux")) os = "Linux"
73
74 return os
75 }
76
76 -export const delay = (t: number) => {
77 +export function delay(t: number) {
78 return new Promise(res => setTimeout(res, t))
79 }
80
80 -export const hashMD5 = (text: number | string) => {
81 +export function hashMD5(text: number | string) {
82 return md5(text.toString())
83 }
84
@@ -118,7 +119,7 @@ export function getBaseUrl() {
119 export function getNameInitials(name: string, cap?: number) {
120 let initials = name.slice(0, 2)
121
121 - if (name.indexOf(" ") !== -1) {
122 + if (name.includes(" ")) {
123 initials = name
124 .split(" ")
125 .map(chunk => chunk[0])
frontend/src/utils/theme.ts
+5 -8
@@ -1,6 +1,6 @@
1 +import type { ColorAction, ColorKey, ColorType, ThemeColor } from "@/types/theme.d"
2 import { colord } from "colord"
3 import _get from "lodash/get"
3 -import { type ColorAction, type ColorKey, type ColorType, type ThemeColor } from "@/types/theme.d"
4
5 export type PrimaryShade = "005" | "010" | "015" | "020" | "030" | "040" | "050" | "060"
6
@@ -11,13 +11,10 @@ export function toggleSidebarClass(
11 classClose: string
12 ) {
13 const el = window?.document?.getElementById(elementId)
14 - if (sidebarCollapsed) {
15 - el && el.classList.remove(classOpen)
16 - el && el.classList.add(classClose)
17 - } else {
18 - el && el.classList.add(classOpen)
19 - el && el.classList.remove(classClose)
20 - }
14 + if (!el) return
15 +
16 + el.classList.toggle(classOpen, !sidebarCollapsed)
17 + el.classList.toggle(classClose, sidebarCollapsed)
18 }
19
20 export function hex2rgb(hex: string): number[] {
frontend/src/views/Artifacts.vue
+10 -10
@@ -1,31 +1,31 @@
1 <template>
2 <div class="page">
3 - <n-tabs type="line" animated v-model:value="activeTab">
3 + <n-tabs v-model:value="activeTab" type="line" animated>
4 <n-tab-pane name="artifacts" tab="Artifacts" display-directive="show:lazy">
5 <ArtifactsList @loaded-agents="agents = $event" @loaded-artifacts="artifacts = $event" />
6 </n-tab-pane>
7 <n-tab-pane name="collect" tab="Collect" display-directive="show:lazy">
8 <ArtifactsCollect
9 - @loaded-agents="agents = $event"
10 - @loaded-artifacts="artifacts = $event"
9 :agents
10 :artifacts
11 + @loaded-agents="agents = $event"
12 + @loaded-artifacts="artifacts = $event"
13 />
14 </n-tab-pane>
15 <n-tab-pane name="command" tab="Command" display-directive="show:lazy">
16 <ArtifactsCommand
17 - @loaded-agents="agents = $event"
18 - @loaded-artifacts="artifacts = $event"
17 :agents
18 :artifacts
19 + @loaded-agents="agents = $event"
20 + @loaded-artifacts="artifacts = $event"
21 />
22 </n-tab-pane>
23 <n-tab-pane name="quarantine" tab="Quarantine" display-directive="show:lazy">
24 <ArtifactsQuarantine
25 - @loaded-agents="agents = $event"
26 - @loaded-artifacts="artifacts = $event"
25 :agents
26 :artifacts
27 + @loaded-agents="agents = $event"
28 + @loaded-artifacts="artifacts = $event"
29 />
30 </n-tab-pane>
31 </n-tabs>
@@ -33,10 +33,10 @@
33 </template>
34
35 <script setup lang="ts">
36 -import { defineAsyncComponent, ref } from "vue"
37 -import { NTabs, NTabPane } from "naive-ui"
38 -import type { Artifact } from "@/types/artifacts.d"
36 import type { Agent } from "@/types/agents.d"
37 +import type { Artifact } from "@/types/artifacts.d"
38 +import { NTabPane, NTabs } from "naive-ui"
39 +import { defineAsyncComponent, ref } from "vue"
40
41 const ArtifactsList = defineAsyncComponent(() => import("@/components/artifacts/ArtifactsList.vue"))
42 const ArtifactsCollect = defineAsyncComponent(() => import("@/components/artifacts/ArtifactsCollect.vue"))
frontend/src/views/Customers.vue
+3 -3
@@ -2,18 +2,18 @@
2 <div class="page">
3 <CustomersList :highlight="highlight" :reload="reload" @reloaded="reload = false">
4 <CustomerDefaultSettingsButton />
5 - <CustomerCreationButton v-model:openForm="openForm" @submitted="reload = true" />
5 + <CustomerCreationButton v-model:open-form="openForm" @submitted="reload = true" />
6 </CustomersList>
7 </div>
8 </template>
9
10 <script setup lang="ts">
11 -import CustomersList from "@/components/customers/CustomersList.vue"
11 import CustomerCreationButton from "@/components/customers/CustomerCreationButton.vue"
12 +import CustomersList from "@/components/customers/CustomersList.vue"
13 import CustomerDefaultSettingsButton from "@/components/customers/provision/CustomerDefaultSettingsButton.vue"
14 +import { emitter } from "@/emitter"
15 import { onBeforeMount, onMounted, onUnmounted, ref } from "vue"
16 import { useRoute, useRouter } from "vue-router"
16 -import { emitter } from "@/emitter"
17
18 const route = useRoute()
19 const router = useRouter()
frontend/src/views/Indices.vue
+8 -7
@@ -5,7 +5,7 @@
5 </div>
6
7 <div class="section">
8 - <Details :indices="indices" v-model="currentIndex" />
8 + <Details v-model="currentIndex" :indices="indices" />
9 </div>
10
11 <div class="section">
@@ -14,7 +14,7 @@
14 <ClusterHealth class="stretchy" />
15 </div>
16 <div class="col basis-1/2">
17 - <UnhealthyIndices :indices="indices" @click="setIndex" class="stretchy" />
17 + <UnhealthyIndices :indices="indices" class="stretchy" @click="setIndex" />
18 </div>
19 </div>
20 </div>
@@ -35,16 +35,17 @@
35 <script lang="ts" setup>
36 import type { IndexStats } from "@/types/indices.d"
37 import Api from "@/api"
38 -import { defineAsyncComponent, onBeforeMount, ref } from "vue"
39 -import IndicesMarquee from "@/components/indices/Marquee.vue"
40 -import NodeAllocation from "@/components/indices/NodeAllocation.vue"
38 import ClusterHealth from "@/components/indices/ClusterHealth.vue"
39 import Details from "@/components/indices/Details.vue"
40 +import IndicesMarquee from "@/components/indices/Marquee.vue"
41 +import NodeAllocation from "@/components/indices/NodeAllocation.vue"
42 import UnhealthyIndices from "@/components/indices/UnhealthyIndices.vue"
44 -const TopIndices = defineAsyncComponent(() => import("@/components/indices/TopIndices.vue"))
45 -import { useMessage, NCard } from "naive-ui"
43 +import { NCard, useMessage } from "naive-ui"
44 +import { defineAsyncComponent, onBeforeMount, ref } from "vue"
45 import { useRoute } from "vue-router"
46
47 +const TopIndices = defineAsyncComponent(() => import("@/components/indices/TopIndices.vue"))
48 +
49 const message = useMessage()
50 const route = useRoute()
51 const indices = ref<IndexStats[] | null>(null)
frontend/src/views/Logs.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <div class="page">
3 - <LogsList :userId="userId" />
3 + <LogsList :user-id="userId" />
4 </div>
5 </template>
6
frontend/src/views/NotFound.vue
+1 -1
@@ -9,7 +9,7 @@
9 </template>
10
11 <script lang="ts" setup>
12 -import { NResult, NButton } from "naive-ui"
12 +import { NButton, NResult } from "naive-ui"
13 import { useRouter } from "vue-router"
14
15 const router = useRouter()
frontend/src/views/Overview.vue
+17 -14
@@ -1,5 +1,5 @@
1 <template>
2 - <div class="page" ref="page">
2 + <div ref="page" class="page">
3 <div class="section justify-end lg:justify-between flex gap-3">
4 <div class="left-box hidden lg:flex gap-3">
5 <StackProvisioningButton size="small" type="primary" />
@@ -12,7 +12,9 @@
12 </div>
13 <div class="mobile-box block lg:hidden">
14 <n-button size="small" type="primary" @click="showQuickActions = true">
15 - <template #icon><Icon :name="QuickActionsIcon"></Icon></template>
15 + <template #icon>
16 + <Icon :name="QuickActionsIcon"></Icon>
17 + </template>
18 Quick Actions
19 </n-button>
20 </div>
@@ -51,7 +53,7 @@
53 </div>
54 </div>
55 <div class="section">
54 - <PipeList minHeight="28px" @open-rule="gotoGraylogPipelines($event)" />
56 + <PipeList min-height="28px" @open-rule="gotoGraylogPipelines($event)" />
57 </div>
58
59 <n-drawer
@@ -75,24 +77,25 @@
77 </template>
78
79 <script setup lang="ts">
78 -import { defineAsyncComponent, ref } from "vue"
79 -import { NButton, NDrawer, NDrawerContent } from "naive-ui"
80 -import ClusterHealth from "@/components/indices/ClusterHealth.vue"
81 -import NodeAllocation from "@/components/indices/NodeAllocation.vue"
82 -import IndicesMarquee from "@/components/indices/Marquee.vue"
83 -const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
80 import ActiveResponseWizardButton from "@/components/activeResponse/ActiveResponseWizardButton.vue"
85 -import StackProvisioningButton from "@/components/stackProvisioning/StackProvisioningButton.vue"
81 import CloudSecurityAssessmentButton from "@/components/cloudSecurityAssessment/CloudSecurityAssessmentButton.vue"
87 -import WebVulnerabilityAssessmentButton from "@/components/webVulnerabilityAssessment/WebVulnerabilityAssessmentButton.vue"
82 +import Icon from "@/components/common/Icon.vue"
83 +import PipeList from "@/components/graylog/Pipelines/PipeList.vue"
84 +import ClusterHealth from "@/components/indices/ClusterHealth.vue"
85 +import IndicesMarquee from "@/components/indices/Marquee.vue"
86 +import NodeAllocation from "@/components/indices/NodeAllocation.vue"
87 import AgentsCard from "@/components/overview/AgentsCard.vue"
88 import HealthcheckCard from "@/components/overview/HealthcheckCard.vue"
89 +import StackProvisioningButton from "@/components/stackProvisioning/StackProvisioningButton.vue"
90 +import WebVulnerabilityAssessmentButton from "@/components/webVulnerabilityAssessment/WebVulnerabilityAssessmentButton.vue"
91 +import { NButton, NDrawer, NDrawerContent } from "naive-ui"
92 +import { defineAsyncComponent, ref } from "vue"
93 // import SocAlertsCard from "@/components/overview/SocAlertsCard.vue"
94 import CustomersCard from "@/components/overview/CustomersCard.vue"
92 -import PipeList from "@/components/graylog/Pipelines/PipeList.vue"
93 -import { useResizeObserver } from "@vueuse/core"
94 -import Icon from "@/components/common/Icon.vue"
95 import { useGoto } from "@/composables/useGoto"
96 +import { useResizeObserver } from "@vueuse/core"
97 +
98 +const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
99
100 const QuickActionsIcon = "ant-design:thunderbolt-outlined"
101 const page = ref()
frontend/src/views/Profile.vue
+13 -13
@@ -7,11 +7,11 @@
7 <ImageCropper
8 v-if="propicEnabled"
9 v-slot="{ openCropper }"
10 - @crop="setCroppedImage"
10 shape="circle"
12 - :placeholder="'Select your profile picture'"
11 + placeholder="Select your profile picture"
12 + @crop="setCroppedImage"
13 >
14 - <Icon :name="EditIcon" :size="16" class="edit" @click="openCropper()"></Icon>
14 + <Icon :name="EditIcon" :size="16" class="edit" @click="openCropper()" />
15 </ImageCropper>
16 </div>
17 <div class="info grow flex flex-col justify-center">
@@ -23,16 +23,16 @@
23 <n-tooltip placement="top">
24 <template #trigger>
25 <div class="tooltip-wrap">
26 - <Icon :name="RoleIcon"></Icon>
26 + <Icon :name="RoleIcon" />
27 <span>{{ userRole }}</span>
28 </div>
29 </template>
30 <span>Role</span>
31 </n-tooltip>
32 </div>
33 - <div class="item" v-if="userEmail">
33 + <div v-if="userEmail" class="item">
34 <div class="tooltip-wrap">
35 - <Icon :name="EmailIcon"></Icon>
35 + <Icon :name="EmailIcon" />
36 <span>{{ userEmail }}</span>
37 </div>
38 </div>
@@ -44,9 +44,9 @@
44 <ImageCropper
45 v-if="propicEnabled"
46 v-slot="{ openCropper }"
47 - @crop="setCroppedImage"
47 shape="circle"
49 - :placeholder="'Select your profile picture'"
48 + placeholder="Select your profile picture"
49 + @crop="setCroppedImage"
50 >
51 <n-button size="large" type="primary" @click="openCropper()">Edit profile image</n-button>
52 </ImageCropper>
@@ -59,7 +59,7 @@
59 </div>
60 </n-card>
61 <div class="main">
62 - <n-tabs tab-class="!hidden" v-model:value="tabActive" animated>
62 + <n-tabs v-model:value="tabActive" tab-class="!hidden" animated>
63 <n-tab-pane name="settings">
64 <ProfileSettings />
65 </n-tab-pane>
@@ -69,13 +69,13 @@
69 </template>
70
71 <script lang="ts" setup>
72 -import { NAvatar, NButton, NTooltip, NTab, NTabs, NTabPane, NCard } from "naive-ui"
73 -import { ref } from "vue"
72 +import Icon from "@/components/common/Icon.vue"
73 import ImageCropper, { type ImageCropperResult } from "@/components/common/ImageCropper.vue"
74 import ProfileSettings from "@/components/profile/ProfileSettings.vue"
76 -import Icon from "@/components/common/Icon.vue"
77 -import { useAuthStore } from "@/stores/auth"
75 import ChangePassword from "@/components/users/ChangePassword.vue"
76 +import { useAuthStore } from "@/stores/auth"
77 +import { NAvatar, NButton, NCard, NTab, NTabPane, NTabs, NTooltip } from "naive-ui"
78 +import { ref } from "vue"
79
80 const propicEnabled = false
81
frontend/src/views/ReportCreation.vue
+7 -7
@@ -1,11 +1,11 @@
1 <template>
2 <div class="page page-wrapped page-without-footer flex flex-col gap-5">
3 <ReportWizard
4 + hide-panels-select
5 @timerange="timerange = $event"
6 @organization="org = $event"
7 @dashboard="dashboard = $event"
8 @panels="panels = $event"
8 - hide-panels-select
9 />
10 <ReportPanels :timerange="timerange" :org="org" :dashboard="dashboard" :panels="panels" />
11 <div class="over-layer mobile-layer">
@@ -16,19 +16,19 @@
16 This function is available only for desktop devices
17 </n-alert>
18 </div>
19 - <LicenseFeatureOverlay :feature="'REPORTING'" />
19 + <LicenseFeatureOverlay feature="REPORTING" />
20 </div>
21 </template>
22
23 <script setup lang="ts">
24 -import { ref } from "vue"
25 -import { NAlert } from "naive-ui"
26 -import ReportWizard from "@/components/reportCreation/Wizard.vue"
27 -import ReportPanels from "@/components/reportCreation/Panels.vue"
28 -import type { Dashboard, Org, Panel } from "@/types/reporting.d"
24 import type { ReportTimeRange } from "@/api/endpoints/reporting"
25 +import type { Dashboard, Org, Panel } from "@/types/reporting.d"
26 import Icon from "@/components/common/Icon.vue"
27 import LicenseFeatureOverlay from "@/components/license/LicenseFeatureOverlay.vue"
28 +import ReportPanels from "@/components/reportCreation/Panels.vue"
29 +import ReportWizard from "@/components/reportCreation/Wizard.vue"
30 +import { NAlert } from "naive-ui"
31 +import { ref } from "vue"
32
33 const AlertIcon = "mdi:alert-outline"
34
frontend/src/views/agents/Agents.vue
+12 -13
@@ -23,16 +23,16 @@
23 :key="agent.agent_id"
24 :agent="agent"
25 show-actions
26 + class="item-appear item-appear-bottom item-appear-005"
27 @delete="syncAgents()"
28 @click="gotoAgent(agent.agent_id)"
28 - class="item-appear item-appear-bottom item-appear-005"
29 />
30 </template>
31 <template v-else>
32 <n-empty
33 + v-if="!loadingAgents"
34 description="No items found"
35 class="justify-center h-48"
35 - v-if="!loadingAgents"
36 />
37 </template>
38 </div>
@@ -53,14 +53,14 @@
53 </template>
54
55 <script setup lang="ts">
56 -import { computed, onBeforeMount, ref, watch } from "vue"
57 -import { AgentStatus, type Agent } from "@/types/agents.d"
56 +import Api from "@/api"
57 import AgentCard from "@/components/agents/AgentCard.vue"
58 import AgentToolbar from "@/components/agents/AgentToolbar.vue"
60 -import Api from "@/api"
61 -import { useMessage, NSpin, NScrollbar, NEmpty, NPagination } from "naive-ui"
62 -import _debounce from "lodash/debounce"
59 import { useGoto } from "@/composables/useGoto"
60 +import { type Agent, AgentStatus } from "@/types/agents.d"
61 +import _debounce from "lodash/debounce"
62 +import { NEmpty, NPagination, NScrollbar, NSpin, useMessage } from "naive-ui"
63 +import { computed, onBeforeMount, ref, watch } from "vue"
64
65 const message = useMessage()
66 const { gotoAgent } = useGoto()
@@ -82,12 +82,11 @@ watch(textFilter, val => {
82 })
83
84 const agentsFiltered = computed(() => {
85 - return agents.value.filter(
86 - ({ hostname, ip_address, agent_id, label }) =>
87 - (hostname + ip_address + agent_id + label)
88 - .toString()
89 - .toLowerCase()
90 - .indexOf(textFilterDebounced.value.toString().toLowerCase()) !== -1
85 + return agents.value.filter(({ hostname, ip_address, agent_id, label }) =>
86 + (hostname + ip_address + agent_id + label)
87 + .toString()
88 + .toLowerCase()
89 + .includes(textFilterDebounced.value.toString().toLowerCase())
90 )
91 })
92
frontend/src/views/agents/Overview.vue
+13 -13
@@ -5,7 +5,7 @@
5 <Icon :name="ArrowIcon" :size="16"></Icon>
6 <span>Agents list</span>
7 </div>
8 - <div class="delete-btn" @click.stop="handleDelete" v-if="agent">Delete Agent</div>
8 + <div v-if="agent" class="delete-btn" @click.stop="handleDelete">Delete Agent</div>
9 </div>
10 <n-spin
11 class="agent-header py-5 px-7 my-4"
@@ -15,7 +15,7 @@
15 >
16 <div class="info grow">
17 <div class="title">
18 - <div class="critical" :class="{ active: agent?.critical_asset }" v-if="agent">
18 + <div v-if="agent" class="critical" :class="{ active: agent?.critical_asset }">
19 <n-tooltip>
20 Toggle Critical Assets
21 <template #trigger>
@@ -37,9 +37,9 @@
37 {{ agent?.hostname }}
38 </h1>
39
40 - <span class="online-badge" v-if="isOnline">ONLINE</span>
40 + <span v-if="isOnline" class="online-badge">ONLINE</span>
41
42 - <span class="quarantined-badge flex items-center gap-1" v-if="isQuarantined">
42 + <span v-if="isQuarantined" class="quarantined-badge flex items-center gap-1">
43 <Icon :name="QuarantinedIcon" :size="15"></Icon>
44 <span>QUARANTINED</span>
45 </span>
@@ -104,29 +104,29 @@
104 <n-tab-pane name="collect" tab="Collect" display-directive="show:lazy">
105 <ArtifactsCollect
106 v-if="agent"
107 - @loaded-artifacts="artifacts = $event"
107 :hostname="agent.hostname"
108 :artifacts
109 hide-hostname-field
110 + @loaded-artifacts="artifacts = $event"
111 />
112 </n-tab-pane>
113 <n-tab-pane name="command" tab="Command" display-directive="show:lazy">
114 <ArtifactsCommand
115 v-if="agent"
116 - @loaded-artifacts="artifacts = $event"
116 :hostname="agent.hostname"
117 :artifacts
118 hide-hostname-field
119 + @loaded-artifacts="artifacts = $event"
120 />
121 </n-tab-pane>
122 <n-tab-pane name="quarantine" tab="Quarantine" display-directive="show:lazy">
123 <ArtifactsQuarantine
124 v-if="agent"
125 - @action-performed="getAgent()"
126 - @loaded-artifacts="artifacts = $event"
125 :hostname="agent.hostname"
126 :artifacts
127 hide-hostname-field
128 + @action-performed="getAgent()"
129 + @loaded-artifacts="artifacts = $event"
130 />
131 </n-tab-pane>
132 <n-tab-pane name="active-response" tab="Active Response" display-directive="show:lazy">
@@ -139,15 +139,15 @@
139 </template>
140
141 <script setup lang="ts">
142 -import { ref, onBeforeMount, computed, nextTick, defineAsyncComponent } from "vue"
143 -import { useMessage, NSpin, NTooltip, NButton, NTabs, NTabPane, NCard, useDialog } from "naive-ui"
144 -import { useRoute, useRouter } from "vue-router"
142 +import type { Artifact } from "@/types/artifacts.d"
143 import Api from "@/api"
144 import { handleDeleteAgent, toggleAgentCritical } from "@/components/agents/utils"
145 import Icon from "@/components/common/Icon.vue"
146 import { useGoto } from "@/composables/useGoto"
149 -import type { Artifact } from "@/types/artifacts.d"
150 -import { AgentStatus, type Agent } from "@/types/agents.d"
147 +import { type Agent, AgentStatus } from "@/types/agents.d"
148 +import { NButton, NCard, NSpin, NTabPane, NTabs, NTooltip, useDialog, useMessage } from "naive-ui"
149 +import { computed, defineAsyncComponent, nextTick, onBeforeMount, ref } from "vue"
150 +import { useRoute, useRouter } from "vue-router"
151
152 const VulnerabilitiesGrid = defineAsyncComponent(
153 () => import("@/components/agents/vulnerabilities/VulnerabilitiesGrid.vue")
frontend/src/views/auth/Login.vue
+7 -7
@@ -1,11 +1,11 @@
1 <template>
2 <div class="page-auth">
3 - <div class="flex wrapper justify-center" v-if="!isLogged">
4 - <div class="image-box basis-2/3" v-if="align === 'right'"></div>
3 + <div v-if="!isLogged" class="flex wrapper justify-center">
4 + <div v-if="align === 'right'" class="image-box basis-2/3" />
5 <div class="form-box basis-1/3 flex items-center justify-center" :class="{ centered: align === 'center' }">
6 <AuthForm :type="type" />
7 </div>
8 - <div class="image-box basis-2/3" v-if="align === 'left'">
8 + <div v-if="align === 'left'" class="image-box basis-2/3">
9 <video playsinline autoplay muted loop poster="/images/login/cover.webp">
10 <source src="/images/login/video.mp4" type="video/mp4" />
11 Your browser does not support the video tag.
@@ -16,12 +16,12 @@
16 </template>
17
18 <script lang="ts" setup>
19 +import type { FormType } from "@/components/auth/types.d"
20 import AuthForm from "@/components/auth/AuthForm.vue"
20 -import { ref, computed, onBeforeMount, toRefs } from "vue"
21 -import { useRoute } from "vue-router"
22 -import { useThemeStore } from "@/stores/theme"
21 import { useAuthStore } from "@/stores/auth"
24 -import type { FormType } from "@/components/auth/types.d"
22 +import { useThemeStore } from "@/stores/theme"
23 +import { computed, onBeforeMount, ref, toRefs } from "vue"
24 +import { useRoute } from "vue-router"
25
26 type Align = "left" | "center" | "right"
27
frontend/src/views/graylog/Management.vue
+4 -4
@@ -1,6 +1,6 @@
1 <template>
2 <div class="page">
3 - <n-tabs type="line" animated v-model:value="activeTab">
3 + <n-tabs v-model:value="activeTab" type="line" animated>
4 <n-tab-pane name="messages" tab="Messages" display-directive="show:lazy">
5 <Messages />
6 </n-tab-pane>
@@ -36,10 +36,10 @@
36 </template>
37
38 <script setup lang="ts">
39 +import type { EventDefinition } from "@/types/graylog/event-definition.d"
40 +import { NButton, NDrawer, NDrawerContent, NTabPane, NTabs } from "naive-ui"
41 import { defineAsyncComponent, onBeforeMount, ref, watch } from "vue"
40 -import { NTabs, NTabPane, NButton, NDrawer, NDrawerContent } from "naive-ui"
42 import { useRoute, useRouter } from "vue-router"
42 -import type { EventDefinition } from "@/types/graylog/event-definition.d"
43
44 const Messages = defineAsyncComponent(() => import("@/components/graylog/Messages/List.vue"))
45 const Alerts = defineAsyncComponent(() => import("@/components/graylog/Alerts/List.vue"))
@@ -64,7 +64,7 @@ function gotoEventsPage(event_definition_id: string) {
64 }
65
66 watch(activeTab, val => {
67 - router.replace({ hash: "#" + val })
67 + router.replace({ hash: `#${val}` })
68 })
69
70 onBeforeMount(() => {
frontend/src/views/graylog/Metrics.vue
+18 -12
@@ -2,23 +2,29 @@
2 <div class="page">
3 <div class="header flex flex-wrap justify-between items-center gap-4">
4 <div class="info flex items-center gap-3">
5 - <n-button size="small" @click="getData()" type="primary" secondary :loading="loading">
6 - <template #icon><Icon :name="UpdatedIcon" :size="15"></Icon></template>
5 + <n-button size="small" type="primary" secondary :loading="loading" @click="getData()">
6 + <template #icon>
7 + <Icon :name="UpdatedIcon" :size="15"></Icon>
8 + </template>
9 </n-button>
10 <span>Last check:</span>
11 <strong>{{ lastCheck ? formatDate(lastCheck, dFormats.datetimesec) : "..." }}</strong>
12 </div>
13
14 <div class="toolbar flex items-center gap-3">
13 - <n-button size="small" @click="start()" v-if="!isRunning" type="primary" class="!w-24">
14 - <template #icon><Icon :name="StartIcon"></Icon></template>
15 + <n-button v-if="!isRunning" size="small" type="primary" class="!w-24" @click="start()">
16 + <template #icon>
17 + <Icon :name="StartIcon"></Icon>
18 + </template>
19 Start
20 </n-button>
17 - <n-button size="small" @click="stop()" v-if="isRunning" type="error" ghost class="!w-24">
18 - <template #icon><Icon :name="StopIcon"></Icon></template>
21 + <n-button v-if="isRunning" size="small" type="error" ghost class="!w-24" @click="stop()">
22 + <template #icon>
23 + <Icon :name="StopIcon"></Icon>
24 + </template>
25 Stop
26 </n-button>
21 - <n-select size="small" v-model:value="intervalSelected" :options="intervalOptions" class="!w-36" />
27 + <n-select v-model:value="intervalSelected" size="small" :options="intervalOptions" class="!w-36" />
28 </div>
29 </div>
30
@@ -33,16 +39,16 @@
39 </template>
40
41 <script setup lang="ts">
36 -import { ref, onBeforeMount, computed, watch, nextTick, onBeforeUnmount } from "vue"
37 -import { useMessage, NButton, NSelect } from "naive-ui"
38 -import Api from "@/api"
42 import type { ThroughputMetric } from "@/types/graylog/metrics.d"
43 +import Api from "@/api"
44 import Icon from "@/components/common/Icon.vue"
41 -import UncommittedEntries from "@/components/graylog/Metrics/UncommittedEntries.vue"
45 import MetricsList from "@/components/graylog/Metrics/List.vue"
43 -import { formatDate } from "@/utils"
46 +import UncommittedEntries from "@/components/graylog/Metrics/UncommittedEntries.vue"
47 import { useSettingsStore } from "@/stores/settings"
48 +import { formatDate } from "@/utils"
49 import { useStorage } from "@vueuse/core"
50 +import { NButton, NSelect, useMessage } from "naive-ui"
51 +import { computed, nextTick, onBeforeMount, onBeforeUnmount, ref, watch } from "vue"
52
53 const UpdatedIcon = "carbon:update-now"
54 const StopIcon = "carbon:stop"
frontend/src/views/graylog/Pipelines.vue
+6 -6
@@ -9,7 +9,7 @@
9 </n-button>
10 </div>
11
12 - <PipeList @open-rule="openRule($event)" minHeight="100px" />
12 + <PipeList min-height="100px" @open-rule="openRule($event)" />
13
14 <n-modal
15 v-model:show="showDetails"
@@ -33,22 +33,22 @@
33 <n-drawer-content closable body-content-style="padding:0">
34 <template #header>
35 <span>Rules list</span>
36 - <span class="font-mono ml-2 text-secondary-color" v-if="rulesTotal !== null">{{ rulesTotal }}</span>
36 + <span v-if="rulesTotal !== null" class="font-mono ml-2 text-secondary-color">{{ rulesTotal }}</span>
37 </template>
38 - <RulesList @loaded="rulesTotal = $event.total" :highlight="highlightRule" />
38 + <RulesList :highlight="highlightRule" @loaded="rulesTotal = $event.total" />
39 </n-drawer-content>
40 </n-drawer>
41 </div>
42 </template>
43
44 <script setup lang="ts">
45 -import { NButton, NModal, NDrawer, NDrawerContent } from "naive-ui"
46 -import { onBeforeMount, ref, watch } from "vue"
45 import type { PipelineFull } from "@/types/graylog/pipelines.d"
46 import Icon from "@/components/common/Icon.vue"
49 -import PipeList from "@/components/graylog/Pipelines/PipeList.vue"
47 import PipeInfo from "@/components/graylog/Pipelines/PipeInfo.vue"
48 +import PipeList from "@/components/graylog/Pipelines/PipeList.vue"
49 import RulesList from "@/components/graylog/Pipelines/RulesList.vue"
50 +import { NButton, NDrawer, NDrawerContent, NModal } from "naive-ui"
51 +import { onBeforeMount, ref, watch } from "vue"
52 import { useRoute } from "vue-router"
53
54 const RulesIcon = "ic:outline-swipe-right-alt"
frontend/src/views/license/Cancel.vue
+1
@@ -3,6 +3,7 @@
3 <LicenseCheckoutResponse class="max-w-96" type="error" />
4 </div>
5 </template>
6 +
7 <script setup lang="ts">
8 import LicenseCheckoutResponse from "@/components/license/LicenseCheckoutResponse.vue"
9 </script>
frontend/src/views/license/License.vue
+1 -1
@@ -9,8 +9,8 @@
9 </template>
10
11 <script setup lang="ts">
12 -import LicenseViewer from "@/components/license/LicenseViewer.vue"
12 import type { LicenseKey } from "@/types/license.d"
13 +import LicenseViewer from "@/components/license/LicenseViewer.vue"
14 import { ref } from "vue"
15
16 const licenseKey = ref<LicenseKey | undefined>(undefined)
frontend/src/views/license/Success.vue
+2 -1
@@ -3,9 +3,10 @@
3 <LicenseCheckoutResponse class="max-w-96" type="success" :data="{ email: route.query.email?.toString() }" />
4 </div>
5 </template>
6 +
7 <script setup lang="ts">
7 -import { useRoute } from "vue-router"
8 import LicenseCheckoutResponse from "@/components/license/LicenseCheckoutResponse.vue"
9 +import { useRoute } from "vue-router"
10
11 const route = useRoute()
12 </script>
frontend/src/vite-env.d.ts
+3 -1
@@ -2,12 +2,14 @@
2
3 declare module "*.vue" {
4 import type { DefineComponent } from "vue"
5 - const component: DefineComponent<{}, {}, any>
5 +
6 + const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, any>
7 export default component
8 }
9
10 declare module "*.svg" {
11 import type { DefineComponent } from "vue"
12 +
13 const component: DefineComponent
14 export default component
15 }
frontend/tailwind.config.js
+4 -3
@@ -1,6 +1,7 @@
1 -import { readFileSync } from "fs"
2 -import plugin from "tailwindcss/plugin.js"
1 +import { readFileSync } from "node:fs"
2 import _ from "lodash"
3 +import plugin from "tailwindcss/plugin.js"
4 +
5 const fileUrl = new URL("./src/design-tokens.json", import.meta.url)
6 const tokens = JSON.parse(readFileSync(fileUrl))
7
@@ -60,7 +61,7 @@ export default {
61 "!h-0.75"
62 ],
63 plugins: [
63 - plugin(function ({ addBase, theme }) {
64 + plugin(({ addBase, theme }) => {
65 addBase({
66 h1: {
67 fontFamily: getValue(tokens, tokens?.typography?.h1?.fontFamily),
frontend/tsconfig.app.json
+8 -8
@@ -1,18 +1,18 @@
1 {
2 "extends": "@vue/tsconfig/tsconfig.dom.json",
3 - "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.json", "src/**/*.js"],
4 - "exclude": ["src/**/__tests__/*"],
3 "compilerOptions": {
4 "composite": true,
5 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
6 "baseUrl": ".",
9 - "types": ["node", "naive-ui/volar"],
7 "moduleResolution": "node",
11 - "isolatedModules": true,
12 - "allowJs": true,
13 - "noEmit": true,
8 "paths": {
9 "@/*": ["./src/*"]
16 - }
17 - }
10 + },
11 + "types": ["node", "naive-ui/volar"],
12 + "allowJs": true,
13 + "noEmit": true,
14 + "isolatedModules": true
15 + },
16 + "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.json", "src/**/*.js"],
17 + "exclude": ["src/**/__tests__/*"]
18 }
frontend/tsconfig.json
+4 -4
@@ -1,5 +1,7 @@
1 {
2 - "files": [],
2 + "compilerOptions": {
3 + "module": "NodeNext"
4 + },
5 "references": [
6 {
7 "path": "./tsconfig.node.json"
@@ -11,7 +13,5 @@
13 "path": "./tsconfig.vitest.json"
14 }
15 ],
14 - "compilerOptions": {
15 - "module": "NodeNext"
16 - }
16 + "files": []
17 }
frontend/tsconfig.node.json
+4 -4
@@ -1,12 +1,12 @@
1 {
2 "extends": "@tsconfig/node20/tsconfig.json",
3 - "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*"],
3 "compilerOptions": {
4 "composite": true,
6 - "noEmit": true,
5 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
6 "module": "ESNext",
7 "moduleResolution": "Bundler",
10 - "types": ["node"]
11 - }
8 + "types": ["node"],
9 + "noEmit": true
10 + },
11 + "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*"]
12 }
frontend/tsconfig.vitest.json
+2 -2
@@ -1,11 +1,11 @@
1 {
2 "extends": "./tsconfig.app.json",
3 - "exclude": [],
3 "compilerOptions": {
4 "composite": true,
5 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
6 "lib": [],
7 "types": ["node", "jsdom"],
8 "allowImportingTsExtensions": true
10 - }
9 + },
10 + "exclude": []
11 }
frontend/vite.config.ts
+11 -3
@@ -1,11 +1,12 @@
1 +import fs from "node:fs"
2 +import process from "node:process"
3 import { fileURLToPath, URL } from "node:url"
2 -import { defineConfig, loadEnv } from "vite"
4 import vue from "@vitejs/plugin-vue"
5 import vueJsx from "@vitejs/plugin-vue-jsx"
6 +import Components from "unplugin-vue-components/vite"
7 +import { defineConfig, loadEnv } from "vite"
8 import VueDevTools from "vite-plugin-vue-devtools"
9 import svgLoader from "vite-svg-loader"
7 -import Components from "unplugin-vue-components/vite"
8 -import fs from "fs"
10 // import { analyzer } from "vite-bundle-analyzer"
11
12 // https://vitejs.dev/config/
@@ -54,6 +55,13 @@ export default defineConfig(({ mode }) => {
55 },
56 define: {
57 __APP_ENV__: JSON.stringify(process.env.APP_ENV)
58 + },
59 + css: {
60 + preprocessorOptions: {
61 + scss: {
62 + silenceDeprecations: ["legacy-js-api"]
63 + }
64 + }
65 }
66 }
67 })
frontend/vitest.config.ts
+1 -1
@@ -1,5 +1,5 @@
1 import { fileURLToPath } from "node:url"
2 -import { mergeConfig, defineConfig, configDefaults } from "vitest/config"
2 +import { configDefaults, defineConfig, mergeConfig } from "vitest/config"
3 import viteConfig from "./vite.config"
4
5 export default defineConfig(env =>