@cryptotaxi247 / CoPilot / commits / e97dfe68

769 siem dashboards (#775)

* Add enabled dashboards table and related JSON configuration for Wazuh EDR * Add dashboards router and implement dashboard management endpoints * Add dashboards feature with categories, templates, and management functionality * Add panel data endpoint and viewer component for dashboards * Add customer_code and source_name to panel data response and update related components * Add EDR DNS Queries dashboard template with various panels for monitoring * Add dashboards feature with new pages, templates, and panel data handling - Introduced `DashboardsPage.vue` for displaying enabled dashboards with customer selection. - Created `DashboardViewPage.vue` for viewing individual dashboards with dynamic panel rendering. - Updated `siem.ts` to include new interfaces for dashboard and panel data. - Enhanced routing in `index.ts` to support new dashboard views. - Integrated ECharts for visualizing dashboard panels. - Added links to dashboards in existing pages (Agents, Alerts, Cases, Event Search, Overview). - Updated package.json and pnpm-lock.yaml to include ECharts dependency. * chore(deps): update frontend dependencies Update multiple production and development dependencies in the frontend package to their latest versions, including Vue, Vite, Tailwind CSS, and various CodeMirror components. * lint * lint * chore: update naive-ui button type imports Replace internal interface imports from `naive-ui/es/button/src/interface` with the public `ButtonSize` and `ButtonType` exports across various components. * chore: update naive-ui types and component syntax - Replace internal naive-ui button type imports with public ButtonSize and ButtonType exports. - Update n-form-item slot from 'help' to 'feedback' to align with library updates. - Adjust Tailwind important utility syntax in TagRbacSettings.vue. * chore: update naive-ui button type imports Replace internal interface imports from `naive-ui/es/button/src/interface` with the public `ButtonSize` and `ButtonType` exports across several components to use the library's recommended public API. * chore: update naive-ui button type imports Replace internal interface import from `naive-ui/es/button/src/interface` with the public `ButtonSize` export to use the library's recommended public API. * chore: enable @typescript-eslint/no-non-null-assertion rule Update the ESLint configuration to treat non-null assertions as errors, encouraging safer type handling and explicit null checks instead of using the ! operator. * feat: improve dashboard template card layout and add disabled state tooltip - Add `disabledTooltipText` prop to show a tooltip and locked icon when templates cannot be enabled. - Update `CardEntity` internal wrappers to correctly support full-height layouts. - Refactor `DashboardTemplateCard` to use the default slot for descriptions and standard small sizing. * feat: improve dashboard browser layout and filter workflow - Relocate the Event Source selector to the template category card header for better contextual flow. - Move the "Enabled Dashboards" section above template categories and add a scrollable container. - Simplify the customer selection UI by replacing the top card with a form item. - Remove non-null assertions in callbacks to comply with updated linting rules. Fixes #769 * feat: implement dynamic icons and refine layout for dashboard categories - Add `getDashboardIcon` utility to map category types (CPU, memory, network, etc.) to specific icons. - Update `DashboardCategoryCard` to use a smaller footprint and consolidated tag layout within the default slot. - Ensure consistent category icon rendering in the dashboard browser header. Fixes #769 * lint * feat: consolidate dashboard categories and templates into a single card - Merge the dashboard categories and templates into a single card container for a more cohesive UI. - Transition the template grid to use container queries for improved responsiveness. - Refine the layout of the event source selector and template metadata for better alignment. Fixes #769 * feat: refactor dashboard browser into modular components - Extract category and template management into a new `DashboardCategoriesSection` component to improve code organization. - Simplify `DashboardsBrowser` to focus on customer selection and high-level dashboard oversight. - Implement container queries in the new section for more robust responsive grid layouts. Fixes #769 * feat: move dashboard template logic into a self-contained card component - Relocate enable/disable logic and API calls from `DashboardCategoriesSection` to `DashboardTemplateCard`. - Simplify the parent component by delegating template state management and validation to the card. - Enhance component modularity by making the template card responsible for its own actions. Fixes #769 * feat: add threshold alert metadata handling and event resolution services * feat: enhance threshold alert timeline retrieval with index name and ID support * feat: add functionality to associate CoPilot alert ID with OpenSearch documents * fix: correct OpenSearch index pattern for 'wazuh' source in threshold index mapping * feat: add year and month filtering for case report exports * feat: extract enabled dashboards section into a standalone component - Decouple the enabled dashboards logic from `DashboardsBrowser.vue` into a new `EnabledDashboardsSection.vue` component to improve maintainability. - Simplify the main browser component by delegating the fetching and display of active dashboards to the child component. - Utilize `defineModel` for synchronized state management of the enabled dashboards list. Fixes #769 * feat: refine UI layout and styling for dashboard sections - Utilize the `header-extra` slot in `EnabledDashboardsSection` for a cleaner header layout. - Set the event source selector size to small in `DashboardCategoriesSection` for better visual consistency. - Minor cleanup of import formatting. Fixes #769 * feat: improve responsiveness and layout of enabled dashboards table - Implement dynamic column fixing based on container width using `useElementSize`. - Refactor the table to use native `n-data-table` loading and horizontal scrolling. - Prevent header text wrapping and adjust column widths for better readability. Fixes #769 * feat: add absolute time range filtering for event queries * feat: improve DashboardViewer layout and centralize stat formatting - Update the DashboardViewer header layout and transition time range selection to a radio group. - Extract local stat formatting logic into a reusable `formatCompactNumber` utility. - Refine healthcheck store logic to conditionally fetch statuses based on threshold configuration. Fixes #769 * feat: refine DashboardViewer UI and loading state management - Ensure the loading spinner is visible during all data fetches to provide better feedback during updates. - Add white to the available color palette and set it as the default accent color. - Adjust radio button styling and include a note for future routing improvements. Fixes #769 * feat: refactor DashboardViewer with modular chart and card components - Extract ECharts implementation details into dedicated `ChartBar`, `ChartPie`, and `ChartHistogram` components. - Introduce a reusable `CardLink` component for consistent styling of dashboard stats and chart containers. - Centralize ECharts configuration and color palettes to improve maintainability and consistency. - Simplify `DashboardViewer.vue` logic by delegating data rendering to specialized child components. Fixes #769 * feat: standardize chart coloring and refine DashboardViewer layout - Transition from a single accent color to a centralized color palette (DASHBOARD_CHART_COLORS) across all chart components. - Refactor DashboardViewer to use a unified CardLink structure for all panel types, simplifying the template logic. - Update chart components to accept CSS height strings instead of numbers for improved layout flexibility. - Add informational tooltips to interactive charts to guide users on click actions. Fixes #769 * feat: implement request cancellation and refine DashboardViewer fetching logic - Add `AbortController` support to the SIEM API and DashboardViewer to prevent race conditions during rapid filter changes. - Simplify time range presets and consolidate initial data loading into a single immediate watcher. - Improve error handling to distinguish between API errors and manual request cancellations. - Adjust template structure to ensure the empty state is correctly displayed outside the loading spinner. Fixes #769 * feat: implement responsive panel layout using container queries - Replace fixed grid column spans with container-based responsive classes in DashboardViewer. - Introduce a utility mapping to handle dynamic column spans across different container breakpoints. - Ensure dashboard panels adapt their width correctly when the container size changes. Fixes #769 * feat: refine dashboard panel responsive column spans - Update container query breakpoints in the `PANEL_COL_SPAN_BY_WIDTH` mapping for more granular layout control. - Adjust column spans to improve panel distribution across different container sizes. - Simplify full-width panel logic by removing redundant breakpoints for 12-column spans. Fixes #769 * feat: update dashboard chart color palette - Expand the `DASHBOARD_CHART_COLORS` collection from 10 to 14 colors to support more data segments. - Update the hex codes to improve visual distinction and contrast across chart components. Fixes #769 * feat: migrate Pie chart component to ApexCharts - Replace the ECharts implementation with ApexCharts to standardize the charting library across the SIEM dashboard. - Simplify component logic by leveraging a declarative approach with computed properties for configuration and data. - Maintain existing functionality while improving integration with the theme store and global chart styling. Fixes #769 * feat: migrate Bar and Histogram chart components to ApexCharts - Replace ECharts-based `ChartBar` and `ChartHistogram` with ApexCharts implementations to standardize the charting library. - Introduce a `monochrome` prop to allow charts to use a single primary color instead of a multi-color palette. - Implement `ChartColumn` to replace the histogram component, including logic for dynamic label rotation. - Update `DashboardViewer` to utilize the new components and apply monochrome styling to histograms. Fixes #769 * feat: enhance pie chart visualization and legend details - Enable donut labels and data labels to improve data readability directly on the chart. - Update legend formatter to display absolute values and percentages alongside series names. - Refine legend styling, including item margins and marker positioning for better alignment. - Remove slice borders and simplify tooltip configuration to match the updated visual style. Fixes #769 * feat: introduce DashboardPanelType union for improved type safety - Define `DashboardPanelType` as a union of specific panel types to replace generic string types. - Update `DashboardPanel` and the `chartByType` registry to use the new type for better compile-time checks and exhaustive mapping. - Register `stat` as a valid panel type in the dashboard viewer. Fixes #769 * feat: refine bar and pie chart styling and interactions - Disable slice and bar expansion on click to maintain consistent chart sizing. - Remove default active state filters to prevent visual darkening on selection. - Enable data labels on bar charts and adjust their positioning for better readability. - Align grid colors and font sizes with the global theme configuration. Fixes #769 * feat: add datetime label support and refine ChartColumn styling - Implement multi-line date and time formatting for histogram labels based on user settings. - Standardize x-axis label rotation and styling for improved readability. - Disable bar expansion and active state filters to maintain consistent chart appearance. - Align border colors and font families with the global theme configuration. Fixes #769 * feat: improve chart responsiveness and refine layout styling - Add responsive breakpoints for Bar and Column charts to optimize label display on smaller screens. - Refine Column chart x-axis labels with updated rotation and font size to improve legibility. - Increase bar border radius and hide SVG foreignObject elements for a cleaner visual presentation. Fixes #769 * feat: implement named routes and simplify dashboard URL structure - Replace hardcoded routing paths with named routes to improve maintainability and consistency. - Shorten the dashboard viewer URL from `/dashboards/view/:id` to `/dashboards/:id`. - Update the router metadata title for the dashboard viewer component. Fixes #769 * feat: add new EDR dashboard templates for file operations, image load, network connections, process activity, registry activity, and threat detections * precommit-fixes * chore: update CURRENT_VERSION to 0.1.52 --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Mar 23, 2026 at 18:14 UTC e97dfe68e2a025e9c4fc67ca86469ae6e65c9e47
111 files changed +5730 -1020
.gitignore
+6
@@ -67,3 +67,9 @@ frontend/cypress/downloads/
67 frontend/tsconfig.app.tsbuildinfo
68 frontend/tsconfig.node.tsbuildinfo
69 frontend/tsconfig.vitest.tsbuildinfo
70 +
71 +AGENTS.md
72 +
73 +.cursor/hooks/state/continual-learning-index.json
74 +
75 +.cursor/hooks/state/continual-learning.json
backend/alembic/versions/895843b96397_add_incident_thresholds_tables.py new
+50
@@ -0,0 +1,50 @@
1 +"""Add incident thresholds tables
2 +
3 +Revision ID: 895843b96397
4 +Revises: 8e6302c4877d
5 +Create Date: 2026-03-19 11:48:17.809975
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 = "895843b96397"
17 +down_revision: Union[str, None] = "8e6302c4877d"
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_threshold_alert_metadata",
26 + sa.Column("group_by_fields", sa.JSON(), nullable=True),
27 + sa.Column("source_streams", sa.JSON(), nullable=True),
28 + sa.Column("id", sa.Integer(), nullable=False),
29 + sa.Column("alert_id", sa.Integer(), nullable=False),
30 + sa.Column("event_definition_id", sa.String(length=255), nullable=False),
31 + sa.Column("replay_query", sa.String(length=255), nullable=False),
32 + sa.Column("timerange_start", sa.DateTime(), nullable=False),
33 + sa.Column("timerange_end", sa.DateTime(), nullable=False),
34 + sa.Column("source", sa.String(length=50), nullable=False),
35 + sa.Column("resolved_index_name", sa.String(length=255), nullable=False),
36 + sa.Column("resolved_index_id", sa.String(length=255), nullable=False),
37 + sa.ForeignKeyConstraint(
38 + ["alert_id"],
39 + ["incident_management_alert.id"],
40 + ),
41 + sa.PrimaryKeyConstraint("id"),
42 + sa.UniqueConstraint("alert_id"),
43 + )
44 + # ### end Alembic commands ###
45 +
46 +
47 +def downgrade() -> None:
48 + # ### commands auto generated by Alembic - please adjust! ###
49 + op.drop_table("incident_management_threshold_alert_metadata")
50 + # ### end Alembic commands ###
backend/alembic/versions/8e6302c4877d_add_enabled_dashboards_tables.py new
+54
@@ -0,0 +1,54 @@
1 +"""Add enabled dashboards tables
2 +
3 +Revision ID: 8e6302c4877d
4 +Revises: 85ea2970828c
5 +Create Date: 2026-03-17 14:44:04.797718
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 = "8e6302c4877d"
17 +down_revision: Union[str, None] = "85ea2970828c"
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 + "enabled_dashboards",
26 + sa.Column("id", sa.Integer(), nullable=False),
27 + sa.Column("customer_code", sa.String(length=50), nullable=False),
28 + sa.Column("event_source_id", sa.Integer(), nullable=False),
29 + sa.Column("library_card", sa.String(length=255), nullable=False),
30 + sa.Column("template_id", sa.String(length=255), nullable=False),
31 + sa.Column("display_name", sa.String(length=255), nullable=False),
32 + sa.Column("created_at", sa.DateTime(), nullable=False),
33 + sa.ForeignKeyConstraint(
34 + ["customer_code"],
35 + ["customers.customer_code"],
36 + ),
37 + sa.ForeignKeyConstraint(
38 + ["event_source_id"],
39 + ["event_sources.id"],
40 + ),
41 + sa.PrimaryKeyConstraint("id"),
42 + sa.UniqueConstraint("customer_code", "event_source_id", "library_card", "template_id", name="uq_enabled_dashboard"),
43 + )
44 + op.create_index(op.f("ix_enabled_dashboards_customer_code"), "enabled_dashboards", ["customer_code"], unique=False)
45 + op.create_index(op.f("ix_enabled_dashboards_event_source_id"), "enabled_dashboards", ["event_source_id"], unique=False)
46 + # ### end Alembic commands ###
47 +
48 +
49 +def downgrade() -> None:
50 + # ### commands auto generated by Alembic - please adjust! ###
51 + op.drop_index(op.f("ix_enabled_dashboards_event_source_id"), table_name="enabled_dashboards")
52 + op.drop_index(op.f("ix_enabled_dashboards_customer_code"), table_name="enabled_dashboards")
53 + op.drop_table("enabled_dashboards")
54 + # ### end Alembic commands ###
backend/app/active_response/schema/graylog.py
+1 -1
@@ -33,7 +33,7 @@ class GraylogThresholdEventFields(BaseModel):
33 CUSTOMER_CODE: str
34 SOURCE: str
35 ALERT_DESCRIPTION: str
36 - ASSET_NAME: str
36 + ASSET_NAME: Optional[str] = None
37 # Allow additional fields
38 additional_fields: Dict[str, Any] = Field(default_factory=dict, alias="__extra__")
39
backend/app/connectors/wazuh_indexer/utils/universal.py
+25
@@ -337,6 +337,31 @@ class AlertsQueryBuilder:
337 self.query["query"]["bool"]["must"].append(range_query)
338 return self
339
340 + def add_absolute_time_range(self, time_from: str, time_to: str, timestamp_field: str):
341 + """
342 + Adds an absolute time range filter using ISO-formatted start/end times.
343 +
344 + Args:
345 + time_from (str): Start time in ISO format (e.g. '2025-01-01T00:00:00Z').
346 + time_to (str): End time in ISO format (e.g. '2025-01-31T23:59:59Z').
347 + timestamp_field (str): The name of the timestamp field in the index.
348 +
349 + Returns:
350 + self: The updated instance of the class.
351 + """
352 + range_query = {
353 + "range": {
354 + timestamp_field: {
355 + "gte": time_from,
356 + "lte": time_to,
357 + },
358 + },
359 + }
360 + if timestamp_field == "timestamp":
361 + range_query["range"][timestamp_field]["format"] = "strict_date_optional_time"
362 + self.query["query"]["bool"]["must"].append(range_query)
363 + return self
364 +
365 def add_matches(self, matches: Iterable[Tuple[str, str]]):
366 """
367 Adds matches to the query.
backend/app/db/universal_models.py
+31
@@ -6,6 +6,7 @@ from sqlalchemy import Column
6 from sqlalchemy import Float
7 from sqlalchemy import LargeBinary
8 from sqlalchemy import Text
9 +from sqlalchemy import UniqueConstraint
10 from sqlalchemy.dialects.mysql import LONGTEXT
11 from sqlmodel import Field
12 from sqlmodel import Relationship
@@ -538,3 +539,33 @@ class EventSources(SQLModel, table=True):
539 if hasattr(source_data, "enabled"):
540 self.enabled = source_data.enabled
541 self.updated_at = datetime.utcnow()
542 +
543 +
544 +class EnabledDashboards(SQLModel, table=True):
545 + __tablename__ = "enabled_dashboards"
546 + __table_args__ = (
547 + UniqueConstraint(
548 + "customer_code",
549 + "event_source_id",
550 + "library_card",
551 + "template_id",
552 + name="uq_enabled_dashboard",
553 + ),
554 + )
555 +
556 + id: Optional[int] = Field(primary_key=True)
557 + customer_code: str = Field(
558 + foreign_key="customers.customer_code",
559 + max_length=50,
560 + index=True,
561 + nullable=False,
562 + )
563 + event_source_id: int = Field(foreign_key="event_sources.id", nullable=False, index=True)
564 + library_card: str = Field(max_length=255, nullable=False)
565 + template_id: str = Field(max_length=255, nullable=False)
566 + display_name: str = Field(max_length=255, nullable=False)
567 + created_at: datetime = Field(default_factory=datetime.utcnow)
568 +
569 + # Relationships
570 + customer: Optional["Customers"] = Relationship()
571 + event_source: Optional["EventSources"] = Relationship()
backend/app/incidents/config/__init__.py
backend/app/incidents/config/threshold_index_mapping.py new
+45
@@ -0,0 +1,45 @@
1 +"""
2 +Mapping of Graylog threshold alert SOURCE field values to OpenSearch index patterns.
3 +
4 +When a threshold alert arrives from Graylog, the SOURCE custom field (e.g. "wazuh", "DELLSWITCH")
5 +is used to determine which OpenSearch index pattern to query for the underlying events.
6 +
7 +Add new entries here as new source types are configured in Graylog threshold alert definitions.
8 +"""
9 +
10 +from typing import Dict
11 +from typing import Tuple
12 +
13 +from loguru import logger
14 +
15 +# Maps SOURCE field value (case-insensitive lookup) to (index_pattern, time_field)
16 +SOURCE_TO_INDEX_CONFIG: Dict[str, Tuple[str, str]] = {
17 + "wazuh": ("wazuh-*", "timestamp"),
18 + "office365": ("office365-*", "timestamp"),
19 +}
20 +
21 +
22 +def get_index_config_for_source(source: str) -> Tuple[str, str]:
23 + """
24 + Look up the OpenSearch index pattern and time field for a given SOURCE value.
25 +
26 + Args:
27 + source: The SOURCE field value from the Graylog threshold alert (e.g. "wazuh", "DELLSWITCH").
28 +
29 + Returns:
30 + Tuple of (index_pattern, time_field).
31 +
32 + Raises:
33 + ValueError: If the source is not mapped.
34 + """
35 + config = SOURCE_TO_INDEX_CONFIG.get(source.lower())
36 + if config is None:
37 + logger.warning(
38 + f"No index mapping configured for threshold alert source '{source}'. "
39 + f"Available sources: {list(SOURCE_TO_INDEX_CONFIG.keys())}",
40 + )
41 + raise ValueError(
42 + f"No index mapping configured for threshold alert source '{source}'. "
43 + f"Add an entry to SOURCE_TO_INDEX_CONFIG in threshold_index_mapping.py.",
44 + )
45 + return config
backend/app/incidents/models.py
+28
@@ -257,6 +257,34 @@ class VeloSigmaExclusion(SQLModel, table=True):
257 enabled: bool = Field(default=True, description="Whether this exclusion is active")
258
259
260 +class ThresholdAlertMetadata(SQLModel, table=True):
261 + """Metadata for threshold alerts to enable event resolution and timeline retrieval."""
262 +
263 + __tablename__ = "incident_management_threshold_alert_metadata"
264 +
265 + id: Optional[int] = Field(default=None, primary_key=True)
266 + alert_id: int = Field(foreign_key="incident_management_alert.id", nullable=False, unique=True)
267 + event_definition_id: str = Field(max_length=255, nullable=False, description="Graylog event definition ID")
268 + replay_query: str = Field(sa_column=Text, nullable=False, description="Lucene query from Graylog replay_info")
269 + timerange_start: datetime = Field(nullable=False, description="Start of the threshold evaluation window")
270 + timerange_end: datetime = Field(nullable=False, description="End of the threshold evaluation window")
271 + group_by_fields: Optional[Dict] = Field(
272 + sa_column=Column(JSON),
273 + nullable=True,
274 + description="Group-by field key/value pairs from the threshold event",
275 + )
276 + source_streams: Optional[List] = Field(
277 + sa_column=Column(JSON),
278 + nullable=True,
279 + description="Graylog source stream IDs",
280 + )
281 + source: str = Field(max_length=50, nullable=False, description="SOURCE field value (e.g. wazuh)")
282 + resolved_index_name: str = Field(max_length=255, nullable=False, description="OpenSearch index of the resolved event")
283 + resolved_index_id: str = Field(max_length=255, nullable=False, description="OpenSearch document ID of the resolved event")
284 +
285 + alert: Alert = Relationship()
286 +
287 +
288 class TagAccessSettings(SQLModel, table=True):
289 """Global settings for tag-based access control."""
290
backend/app/incidents/routes/incident_alert.py
+53 -3
@@ -33,10 +33,14 @@ from app.incidents.services.alert_collection import get_alerts_not_created_in_co
33 from app.incidents.services.alert_collection import get_graylog_event_indices
34 from app.incidents.services.alert_collection import get_original_alert_id
35 from app.incidents.services.alert_collection import get_original_alert_index_name
36 +from app.incidents.services.incident_alert import add_alert_to_document
37 from app.incidents.services.incident_alert import create_alert
38 from app.incidents.services.incident_alert import create_alert_full
39 from app.incidents.services.incident_alert import get_single_alert_details
40 from app.incidents.services.incident_alert import retrieve_alert_timeline
41 +from app.incidents.services.threshold_alert import resolve_threshold_asset
42 +from app.incidents.services.threshold_alert import resolve_threshold_event
43 +from app.incidents.services.threshold_alert import save_threshold_metadata
44 from app.incidents.services.velo_sigma import VeloSigmaExclusionService
45 from app.incidents.services.velo_sigma import create_velo_sigma_alert
46
@@ -330,20 +334,66 @@ async def invoke_alert_threshold_graylog_route(
334 """
335 logger.info("Invoking alert threshold Graylog...")
336 logger.info(f"Timestamp: {request.event.timestamp}")
337 +
338 + # Resolve the underlying event from OpenSearch using the replay_info and group_by_fields
339 + resolved_index_name, resolved_index_id = await resolve_threshold_event(
340 + replay_query=request.event.replay_info.query,
341 + timerange_start=request.event.replay_info.timerange_start,
342 + timerange_end=request.event.replay_info.timerange_end,
343 + group_by_fields=request.event.group_by_fields,
344 + source=request.event.fields.SOURCE,
345 + )
346 + logger.info(f"Resolved threshold event: index={resolved_index_name}, id={resolved_index_id}")
347 +
348 + # Resolve asset name from the actual event document in OpenSearch
349 + asset_name = await resolve_threshold_asset(
350 + index_name=resolved_index_name,
351 + index_id=resolved_index_id,
352 + source=request.event.fields.SOURCE,
353 + session=session,
354 + )
355 + logger.info(f"Resolved threshold asset: {asset_name}")
356 +
357 alert_id = await create_alert_full(
358 alert_payload=CreatedAlertPayload(
359 alert_context_payload=request.event.fields.dict(),
336 - asset_payload=request.event.fields.ASSET_NAME,
360 + asset_payload=asset_name,
361 timefield_payload=str(request.event.timestamp),
362 alert_title_payload=request.event.message,
363 source=request.event.fields.SOURCE,
340 - index_name="not_applicable",
341 - index_id="not_applicable",
364 + index_name=resolved_index_name,
365 + index_id=resolved_index_id,
366 ),
367 customer_code=request.event.fields.CUSTOMER_CODE,
368 session=session,
369 threshold_alert=True,
370 )
371 +
372 + # Add the CoPilot alert_id to the resolved OpenSearch document
373 + if resolved_index_name != "not_applicable" and resolved_index_id != "not_applicable":
374 + await add_alert_to_document(
375 + CreateAlertRequest(index_name=resolved_index_name, alert_id=resolved_index_id),
376 + alert_id,
377 + )
378 +
379 + # Save threshold metadata for later timeline retrieval
380 + try:
381 + await save_threshold_metadata(
382 + alert_id=alert_id,
383 + event_definition_id=request.event.event_definition_id,
384 + replay_query=request.event.replay_info.query,
385 + timerange_start=request.event.replay_info.timerange_start,
386 + timerange_end=request.event.replay_info.timerange_end,
387 + group_by_fields=request.event.group_by_fields,
388 + source_streams=request.event.source_streams,
389 + source=request.event.fields.SOURCE,
390 + resolved_index_name=resolved_index_name,
391 + resolved_index_id=resolved_index_id,
392 + session=session,
393 + )
394 + except Exception as e:
395 + logger.error(f"Failed to save threshold metadata for alert {alert_id}: {e}")
396 +
397 return CreateAlertResponse(success=True, message="Alert threshold Graylog invoked successfully", alert_id=alert_id)
398
399
backend/app/incidents/routes/incident_report.py
+52 -16
@@ -1,13 +1,16 @@
1 +import calendar
2 import csv
3 from datetime import datetime
4 from io import StringIO
5 from typing import Any
6 from typing import Dict
7 from typing import List
8 +from typing import Optional
9
10 from fastapi import APIRouter
11 from fastapi import Depends
12 from fastapi import HTTPException
13 +from fastapi import Query
14 from fastapi.responses import FileResponse
15 from fastapi.responses import StreamingResponse
16 from sqlalchemy.ext.asyncio import AsyncSession
@@ -63,29 +66,52 @@ FIELDNAMES = [
66
67
68 # Helper Functions
66 -async def fetch_cases_with_related_data(session: AsyncSession) -> List[Case]:
69 +def _build_month_range(year: int, month: int) -> tuple[datetime, datetime]:
70 + """Return (start, end) datetimes for a given year/month."""
71 + last_day = calendar.monthrange(year, month)[1]
72 + start = datetime(year, month, 1)
73 + end = datetime(year, month, last_day, 23, 59, 59)
74 + return start, end
75 +
76 +
77 +async def fetch_cases_with_related_data(
78 + session: AsyncSession,
79 + year: Optional[int] = None,
80 + month: Optional[int] = None,
81 +) -> List[Case]:
82 """Fetch cases with related alerts, assets, tags, and comments."""
68 - result = await session.execute(
69 - select(Case).options(
70 - selectinload(Case.alerts)
71 - .selectinload(CaseAlertLink.alert)
72 - .options(selectinload(Alert.assets), selectinload(Alert.tags).selectinload(AlertToTag.tag), selectinload(Alert.comments)),
73 - ),
83 + query = select(Case).options(
84 + selectinload(Case.alerts)
85 + .selectinload(CaseAlertLink.alert)
86 + .options(selectinload(Alert.assets), selectinload(Alert.tags).selectinload(AlertToTag.tag), selectinload(Alert.comments)),
87 )
88 + if year and month:
89 + start, end = _build_month_range(year, month)
90 + query = query.where(Case.case_creation_time >= start, Case.case_creation_time <= end)
91 + result = await session.execute(query)
92 return result.scalars().all()
93
94
78 -async def fetch_cases_by_customer(session: AsyncSession, customer_code: str) -> List[Case]:
95 +async def fetch_cases_by_customer(
96 + session: AsyncSession,
97 + customer_code: str,
98 + year: Optional[int] = None,
99 + month: Optional[int] = None,
100 +) -> List[Case]:
101 """Fetch cases for a specific customer with related data."""
80 - result = await session.execute(
102 + query = (
103 select(Case)
104 .where(Case.customer_code == customer_code)
105 .options(
106 selectinload(Case.alerts)
107 .selectinload(CaseAlertLink.alert)
108 .options(selectinload(Alert.assets), selectinload(Alert.tags).selectinload(AlertToTag.tag), selectinload(Alert.comments)),
87 - ),
109 + )
110 )
111 + if year and month:
112 + start, end = _build_month_range(year, month)
113 + query = query.where(Case.case_creation_time >= start, Case.case_creation_time <= end)
114 + result = await session.execute(query)
115 return result.scalars().all()
116
117
@@ -151,15 +177,20 @@ def generate_csv_content(rows: List[Dict[str, Any]]) -> StringIO:
177 # Route Handler
178 @incidents_report_router.post(
179 "/generate-report-csv",
154 - description="Generate a report for all cases.",
180 + description="Generate a report for all cases. Optionally filter by year and month.",
181 )
182 async def get_cases_export_all_route(
183 + year: Optional[int] = Query(None, description="Filter by year (e.g. 2026)", ge=2000, le=2100),
184 + month: Optional[int] = Query(None, description="Filter by month (1-12)", ge=1, le=12),
185 session: AsyncSession = Depends(get_db),
186 ) -> StreamingResponse:
159 - cases = await fetch_cases_with_related_data(session)
187 + if (year is None) != (month is None):
188 + raise HTTPException(status_code=400, detail="Both year and month must be provided together")
189 + cases = await fetch_cases_with_related_data(session, year=year, month=month)
190 rows = [serialize_case_alert_to_row(case, alert_link.alert) for case in cases for alert_link in case.alerts]
191 csv_stream = generate_csv_content(rows)
162 - filename = f"cases_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
192 + month_suffix = f"_{year}-{month:02d}" if year and month else ""
193 + filename = f"cases_export{month_suffix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
194 response = StreamingResponse(csv_stream, media_type="text/csv")
195 response.headers["Content-Disposition"] = f"attachment; filename={filename}"
196 return response
@@ -167,17 +198,22 @@ async def get_cases_export_all_route(
198
199 @incidents_report_router.post(
200 "/generate-report-csv/{customer_code}",
170 - description="Generate a report for a customer",
201 + description="Generate a report for a customer. Optionally filter by year and month.",
202 )
203 async def get_cases_export_customer_route(
204 customer_code: str,
205 + year: Optional[int] = Query(None, description="Filter by year (e.g. 2026)", ge=2000, le=2100),
206 + month: Optional[int] = Query(None, description="Filter by month (1-12)", ge=1, le=12),
207 session: AsyncSession = Depends(get_db),
208 ) -> StreamingResponse:
209 + if (year is None) != (month is None):
210 + raise HTTPException(status_code=400, detail="Both year and month must be provided together")
211 await get_customer(customer_code=customer_code, session=session)
177 - cases = await fetch_cases_by_customer(session, customer_code)
212 + cases = await fetch_cases_by_customer(session, customer_code, year=year, month=month)
213 rows = [serialize_case_alert_to_row(case, alert_link.alert) for case in cases for alert_link in case.alerts]
214 csv_stream = generate_csv_content(rows)
180 - filename = f"cases_export_{customer_code}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
215 + month_suffix = f"_{year}-{month:02d}" if year and month else ""
216 + filename = f"cases_export_{customer_code}{month_suffix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
217 response = StreamingResponse(csv_stream, media_type="text/csv")
218 response.headers["Content-Disposition"] = f"attachment; filename={filename}"
219 return response
backend/app/incidents/services/db_operations.py
+2
@@ -48,6 +48,7 @@ from app.incidents.models import FieldName
48 from app.incidents.models import IoC
49 from app.incidents.models import IoCFieldName
50 from app.incidents.models import Notification
51 +from app.incidents.models import ThresholdAlertMetadata
52 from app.incidents.models import TimestampFieldName
53 from app.incidents.schema.db_operations import AlertContextCreate
54 from app.incidents.schema.db_operations import AlertCreate
@@ -2701,6 +2702,7 @@ async def delete_alert(alert_id: int, db: AsyncSession):
2702 await delete_assets(alert_id, db)
2703 await delete_tags(alert_id, db)
2704 await delete_iocs(alert_id, db)
2705 + await db.execute(delete(ThresholdAlertMetadata).where(ThresholdAlertMetadata.alert_id == alert_id))
2706
2707 await db.execute(delete(Alert).where(Alert.id == alert.id))
2708
backend/app/incidents/services/incident_alert.py
+16
@@ -41,6 +41,7 @@ from app.incidents.services.db_operations import get_customer_notification
41 from app.incidents.services.db_operations import get_field_names
42 from app.incidents.services.db_operations import get_ioc_names
43 from app.incidents.services.db_operations import get_timefield_names
44 +from app.incidents.services.threshold_alert import retrieve_threshold_alert_timeline
45 from app.integrations.alert_creation_settings.models.alert_creation_settings import (
46 AlertCreationSettings,
47 )
@@ -1237,6 +1238,10 @@ async def retrieve_alert_timeline(alert: CreateAlertRequestRoute, session: Async
1238 """
1239 Retrieve the alert timeline for the given alert.
1240
1241 + For threshold alerts (identified by having a ThresholdAlertMetadata record),
1242 + the timeline is built from the stored replay query and group_by_fields.
1243 + For standard alerts, the timeline is built from the process_id and agent_name.
1244 +
1245 Args:
1246 alert (CreateAlertRequestRoute): The alert details.
1247 session (AsyncSession): The database session.
@@ -1244,6 +1249,17 @@ async def retrieve_alert_timeline(alert: CreateAlertRequestRoute, session: Async
1249 Returns:
1250 List[Dict[str, Any]]: The alert timeline.
1251 """
1252 + # Check if this is a threshold alert and retrieve its timeline if so
1253 + threshold_timeline = await retrieve_threshold_alert_timeline(
1254 + alert_id=alert.alert_id,
1255 + index_name=alert.index_name,
1256 + index_id=alert.index_id,
1257 + session=session,
1258 + )
1259 + if threshold_timeline is not None:
1260 + logger.info(f"Retrieved threshold alert timeline with {len(threshold_timeline)} events")
1261 + return threshold_timeline
1262 +
1263 alert_details = await get_alert_details(alert)
1264 if alert_details._source.process_id is not None:
1265 alert_timestamp = alert_details._source.timestamp
backend/app/incidents/services/threshold_alert.py new
+303
@@ -0,0 +1,303 @@
1 +"""
2 +Service functions for threshold alert event resolution and timeline retrieval.
3 +
4 +When a Graylog threshold alert fires, there is no individual event _index/_id available.
5 +This module uses the replay_info (Lucene query + timerange) and group_by_fields from the
6 +Graylog webhook to find the first matching event in OpenSearch, and provides timeline
7 +retrieval for threshold alerts using stored metadata.
8 +"""
9 +
10 +from datetime import datetime
11 +from typing import Any
12 +from typing import Dict
13 +from typing import List
14 +from typing import Optional
15 +from typing import Tuple
16 +
17 +from loguru import logger
18 +from sqlalchemy.ext.asyncio import AsyncSession
19 +from sqlalchemy.future import select
20 +
21 +from app.connectors.wazuh_indexer.utils.universal import (
22 + create_wazuh_indexer_client_async,
23 +)
24 +from app.incidents.config.threshold_index_mapping import get_index_config_for_source
25 +from app.incidents.models import AssetFieldName
26 +from app.incidents.models import ThresholdAlertMetadata
27 +
28 +
29 +def _format_datetime(dt: datetime) -> str:
30 + """Format a datetime for OpenSearch range queries, matching the index time format."""
31 + return dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
32 +
33 +
34 +async def resolve_threshold_event(
35 + replay_query: str,
36 + timerange_start: datetime,
37 + timerange_end: datetime,
38 + group_by_fields: Dict[str, str],
39 + source: str,
40 +) -> Tuple[str, str]:
41 + """
42 + Query OpenSearch to find the first event matching a threshold alert's conditions.
43 +
44 + Uses the replay_info from the Graylog webhook to reconstruct the search and find
45 + the actual underlying event that contributed to the threshold count.
46 +
47 + Args:
48 + replay_query: Lucene query string from replay_info.query (e.g. "rule_id:5503").
49 + timerange_start: Start of the threshold evaluation window.
50 + timerange_end: End of the threshold evaluation window.
51 + group_by_fields: Group-by field values from the threshold event (e.g. {"data_dstuser": "taylor"}).
52 + source: The SOURCE field from the Graylog alert (e.g. "wazuh").
53 +
54 + Returns:
55 + Tuple of (index_name, index_id) from the first matching hit,
56 + or ("not_applicable", "not_applicable") if no event is found or source is unmapped.
57 + """
58 + try:
59 + index_pattern, time_field = get_index_config_for_source(source)
60 + except ValueError:
61 + return ("not_applicable", "not_applicable")
62 +
63 + es_client = await create_wazuh_indexer_client_async("Wazuh-Indexer")
64 + try:
65 + must_clauses: List[Dict[str, Any]] = [
66 + {"query_string": {"query": replay_query, "default_operator": "AND"}},
67 + {
68 + "range": {
69 + time_field: {
70 + "gte": _format_datetime(timerange_start),
71 + "lte": _format_datetime(timerange_end),
72 + },
73 + },
74 + },
75 + ]
76 +
77 + # Add match clauses for each group_by_fields entry
78 + for field_name, field_value in group_by_fields.items():
79 + if field_value:
80 + must_clauses.append({"match": {field_name: field_value}})
81 +
82 + query = {
83 + "query": {"bool": {"must": must_clauses}},
84 + "sort": [{time_field: {"order": "asc"}}],
85 + }
86 +
87 + logger.info(
88 + f"Resolving threshold event for source '{source}' in index '{index_pattern}' "
89 + f"with query: {replay_query}, group_by: {group_by_fields}, "
90 + f"timerange: {timerange_start} -> {timerange_end}",
91 + )
92 +
93 + response = await es_client.search(index=index_pattern, body=query, size=1)
94 + hits = response["hits"]["hits"]
95 +
96 + if hits:
97 + hit = hits[0]
98 + resolved_index = hit["_index"]
99 + resolved_id = hit["_id"]
100 + logger.info(f"Resolved threshold event: index={resolved_index}, id={resolved_id}")
101 + return (resolved_index, resolved_id)
102 +
103 + logger.warning(
104 + f"No matching event found for threshold alert in '{index_pattern}' "
105 + f"with query '{replay_query}' and group_by {group_by_fields}",
106 + )
107 + return ("not_applicable", "not_applicable")
108 +
109 + except Exception as e:
110 + logger.error(f"Error resolving threshold event: {e}")
111 + return ("not_applicable", "not_applicable")
112 + finally:
113 + await es_client.close()
114 +
115 +
116 +async def resolve_threshold_asset(
117 + index_name: str,
118 + index_id: str,
119 + source: str,
120 + session: AsyncSession,
121 +) -> str:
122 + """
123 + Fetch the resolved event document from OpenSearch and resolve the asset name
124 + using the AssetFieldName configuration for the given source.
125 +
126 + Args:
127 + index_name: The OpenSearch index name of the resolved event.
128 + index_id: The OpenSearch document ID of the resolved event.
129 + source: The source type (e.g. "wazuh").
130 + session: Database session for looking up asset field names.
131 +
132 + Returns:
133 + The resolved asset name, or "No asset found" if resolution fails.
134 + """
135 + if index_name == "not_applicable" or index_id == "not_applicable":
136 + return "No asset found"
137 +
138 + # Look up asset field names for this source from the database
139 + result = await session.execute(
140 + select(AssetFieldName.field_name).where(AssetFieldName.source == source).distinct(),
141 + )
142 + asset_field_name = result.scalars().first()
143 + if not asset_field_name:
144 + logger.warning(f"No asset field name configured for source '{source}'")
145 + return "No asset found"
146 +
147 + possible_fields = [f.strip() for f in asset_field_name.split(",")]
148 + logger.info(f"Asset field candidates for source '{source}': {possible_fields}")
149 +
150 + # Fetch the event document from OpenSearch
151 + es_client = await create_wazuh_indexer_client_async("Wazuh-Indexer")
152 + try:
153 + doc = await es_client.get(index=index_name, id=index_id)
154 + event_source = doc.get("_source", {})
155 +
156 + for field in possible_fields:
157 + if field in event_source and event_source[field]:
158 + logger.info(f"Resolved threshold asset name '{event_source[field]}' from field '{field}'")
159 + return event_source[field]
160 +
161 + logger.warning(f"No asset name found in event for fields: {possible_fields}")
162 + return "No asset found"
163 + except Exception as e:
164 + logger.error(f"Error fetching event for asset resolution: {e}")
165 + return "No asset found"
166 + finally:
167 + await es_client.close()
168 +
169 +
170 +async def save_threshold_metadata(
171 + alert_id: int,
172 + event_definition_id: str,
173 + replay_query: str,
174 + timerange_start: datetime,
175 + timerange_end: datetime,
176 + group_by_fields: Dict[str, str],
177 + source_streams: List[str],
178 + source: str,
179 + resolved_index_name: str,
180 + resolved_index_id: str,
181 + session: AsyncSession,
182 +) -> ThresholdAlertMetadata:
183 + """
184 + Persist threshold alert metadata to the database for later timeline retrieval.
185 +
186 + Args:
187 + alert_id: The CoPilot alert ID returned by create_alert_full.
188 + event_definition_id: The Graylog event definition ID.
189 + replay_query: Lucene query from replay_info.query.
190 + timerange_start: Start of the threshold evaluation window.
191 + timerange_end: End of the threshold evaluation window.
192 + group_by_fields: Group-by field key/value pairs.
193 + source_streams: Graylog source stream IDs.
194 + source: The SOURCE field value (e.g. "wazuh").
195 + resolved_index_name: OpenSearch index name of the resolved event.
196 + resolved_index_id: OpenSearch document ID of the resolved event.
197 + session: Database session.
198 +
199 + Returns:
200 + The created ThresholdAlertMetadata record.
201 + """
202 + metadata = ThresholdAlertMetadata(
203 + alert_id=alert_id,
204 + event_definition_id=event_definition_id,
205 + replay_query=replay_query,
206 + timerange_start=timerange_start,
207 + timerange_end=timerange_end,
208 + group_by_fields=group_by_fields,
209 + source_streams=source_streams,
210 + source=source,
211 + resolved_index_name=resolved_index_name,
212 + resolved_index_id=resolved_index_id,
213 + )
214 + session.add(metadata)
215 + await session.commit()
216 + logger.info(f"Saved threshold alert metadata for alert ID {alert_id}")
217 + return metadata
218 +
219 +
220 +async def retrieve_threshold_alert_timeline(
221 + alert_id: Optional[int],
222 + index_name: str,
223 + index_id: str,
224 + session: AsyncSession,
225 +) -> Optional[List[Dict[str, Any]]]:
226 + """
227 + Retrieve the timeline for a threshold alert using stored metadata.
228 +
229 + Looks up ThresholdAlertMetadata by alert_id first, then falls back to
230 + resolved_index_name + resolved_index_id (which the frontend always provides).
231 +
232 + Args:
233 + alert_id: The CoPilot alert ID (may be None if not provided by the caller).
234 + index_name: The OpenSearch index name from the asset.
235 + index_id: The OpenSearch document ID from the asset.
236 + session: Database session.
237 +
238 + Returns:
239 + List of OpenSearch hit dicts if this is a threshold alert, or None if no
240 + threshold metadata exists (meaning it's not a threshold alert).
241 + """
242 + metadata = None
243 +
244 + if alert_id is not None:
245 + result = await session.execute(
246 + select(ThresholdAlertMetadata).where(ThresholdAlertMetadata.alert_id == alert_id),
247 + )
248 + metadata = result.scalars().first()
249 +
250 + if metadata is None:
251 + result = await session.execute(
252 + select(ThresholdAlertMetadata).where(
253 + ThresholdAlertMetadata.resolved_index_name == index_name,
254 + ThresholdAlertMetadata.resolved_index_id == index_id,
255 + ),
256 + )
257 + metadata = result.scalars().first()
258 +
259 + if metadata is None:
260 + return None
261 +
262 + try:
263 + index_pattern, time_field = get_index_config_for_source(metadata.source)
264 + except ValueError:
265 + logger.warning(f"Cannot retrieve threshold timeline: source '{metadata.source}' is not mapped")
266 + return []
267 +
268 + es_client = await create_wazuh_indexer_client_async("Wazuh-Indexer")
269 + try:
270 + must_clauses: List[Dict[str, Any]] = [
271 + {"query_string": {"query": metadata.replay_query, "default_operator": "AND"}},
272 + {
273 + "range": {
274 + time_field: {
275 + "gte": _format_datetime(metadata.timerange_start),
276 + "lte": _format_datetime(metadata.timerange_end),
277 + },
278 + },
279 + },
280 + ]
281 +
282 + group_by = metadata.group_by_fields or {}
283 + for field_name, field_value in group_by.items():
284 + if field_value:
285 + must_clauses.append({"match": {field_name: field_value}})
286 +
287 + query = {
288 + "query": {"bool": {"must": must_clauses}},
289 + "sort": [{time_field: {"order": "asc"}}],
290 + }
291 +
292 + logger.info(
293 + f"Fetching threshold alert timeline for alert ID {alert_id} " f"in index '{index_pattern}' with query: {metadata.replay_query}",
294 + )
295 +
296 + response = await es_client.search(index=index_pattern, body=query, size=50)
297 + return response["hits"]["hits"]
298 +
299 + except Exception as e:
300 + logger.error(f"Error fetching threshold alert timeline for alert ID {alert_id}: {e}")
301 + return []
302 + finally:
303 + await es_client.close()
backend/app/routers/siem.py
+2
@@ -1,10 +1,12 @@
1 from fastapi import APIRouter
2
3 +from app.siem.routes.dashboards import dashboards_router
4 from app.siem.routes.event_sources import event_sources_router
5 from app.siem.routes.events import siem_events_router
6
7 # Instantiate the APIRouter
8 router = APIRouter()
9
10 +router.include_router(dashboards_router, prefix="/siem/dashboards", tags=["SIEM - Dashboards"])
11 router.include_router(event_sources_router, prefix="/siem/event_sources", tags=["SIEM - Event Sources"])
12 router.include_router(siem_events_router, prefix="/siem/events", tags=["SIEM - Events"])
backend/app/siem/dashboard_templates/wazuh_edr/EDR_DNS_QUERIES.json new
+89
@@ -0,0 +1,89 @@
1 +{
2 + "id": "EDR_DNS_QUERIES",
3 + "title": "Wazuh EDR - DNS Queries",
4 + "description": "Sysmon Event ID 22 (DNS Query). Tracks DNS lookups made by endpoints: top queried domains, requesting processes, query result status codes, and active users — useful for detecting C2 beaconing and DNS tunnelling.",
5 + "panels": [
6 + {
7 + "id": "total_dns",
8 + "title": "DNS Query Events (EID 22)",
9 + "type": "stat",
10 + "w": 4,
11 + "h": 100,
12 + "lucene": "data_win_system_eventID:22"
13 + },
14 + {
15 + "id": "high_severity_dns",
16 + "title": "High Severity DNS Alerts",
17 + "type": "stat",
18 + "w": 4,
19 + "h": 100,
20 + "lucene": "data_win_system_eventID:22 AND rule_level:>11"
21 + },
22 + {
23 + "id": "active_agents",
24 + "title": "Active Agents",
25 + "type": "stat",
26 + "w": 4,
27 + "h": 100,
28 + "lucene": "data_win_system_eventID:22"
29 + },
30 + {
31 + "id": "dns_over_time",
32 + "title": "DNS Queries Over Time",
33 + "type": "histogram",
34 + "w": 12,
35 + "h": 200,
36 + "lucene": "data_win_system_eventID:22"
37 + },
38 + {
39 + "id": "by_query_status",
40 + "title": "By Query Result Status",
41 + "type": "pie",
42 + "w": 4,
43 + "h": 300,
44 + "lucene": "data_win_system_eventID:22",
45 + "field": "data_win_eventdata_queryStatus",
46 + "size": 8
47 + },
48 + {
49 + "id": "by_agent",
50 + "title": "By Agent",
51 + "type": "pie",
52 + "w": 4,
53 + "h": 300,
54 + "lucene": "data_win_system_eventID:22",
55 + "field": "agent_name",
56 + "size": 10
57 + },
58 + {
59 + "id": "by_user",
60 + "title": "By User",
61 + "type": "pie",
62 + "w": 4,
63 + "h": 300,
64 + "lucene": "data_win_system_eventID:22",
65 + "field": "data_win_eventdata_user",
66 + "size": 10
67 + },
68 + {
69 + "id": "top_query_names",
70 + "title": "Top Queried Domains",
71 + "type": "bar_h",
72 + "w": 6,
73 + "h": 320,
74 + "lucene": "data_win_system_eventID:22",
75 + "field": "data_win_eventdata_queryName",
76 + "size": 15
77 + },
78 + {
79 + "id": "top_requesting_processes",
80 + "title": "Top Requesting Processes",
81 + "type": "bar_h",
82 + "w": 6,
83 + "h": 320,
84 + "lucene": "data_win_system_eventID:22",
85 + "field": "data_win_eventdata_image",
86 + "size": 10
87 + }
88 + ]
89 +}
backend/app/siem/dashboard_templates/wazuh_edr/EDR_FILE_OPERATIONS.json new
+97
@@ -0,0 +1,97 @@
1 +{
2 + "id": "EDR_FILE_OPERATIONS",
3 + "title": "Wazuh EDR - File Operations",
4 + "description": "Sysmon file-system events: Event ID 11 (FileCreate), 15 (FileCreateStreamHash / ADS), 23 (FileDelete), and 26 (FileDeleteDetected). Tracks file creation and deletion activity across endpoints — useful for detecting malware drops, ransomware, and data staging.",
5 + "panels": [
6 + {
7 + "id": "total_file_events",
8 + "title": "Total File Events",
9 + "type": "stat",
10 + "w": 3,
11 + "h": 100,
12 + "lucene": "data_win_system_eventID:11 OR data_win_system_eventID:15 OR data_win_system_eventID:23 OR data_win_system_eventID:26"
13 + },
14 + {
15 + "id": "files_created",
16 + "title": "Files Created (EID 11)",
17 + "type": "stat",
18 + "w": 3,
19 + "h": 100,
20 + "lucene": "data_win_system_eventID:11"
21 + },
22 + {
23 + "id": "stream_hash",
24 + "title": "ADS / Stream Hash (EID 15)",
25 + "type": "stat",
26 + "w": 3,
27 + "h": 100,
28 + "lucene": "data_win_system_eventID:15"
29 + },
30 + {
31 + "id": "files_deleted",
32 + "title": "Files Deleted (EID 23/26)",
33 + "type": "stat",
34 + "w": 3,
35 + "h": 100,
36 + "lucene": "data_win_system_eventID:23 OR data_win_system_eventID:26"
37 + },
38 + {
39 + "id": "file_events_over_time",
40 + "title": "File Events Over Time",
41 + "type": "histogram",
42 + "w": 12,
43 + "h": 200,
44 + "lucene": "data_win_system_eventID:11 OR data_win_system_eventID:15 OR data_win_system_eventID:23 OR data_win_system_eventID:26"
45 + },
46 + {
47 + "id": "by_event_type",
48 + "title": "By Event Type",
49 + "type": "pie",
50 + "w": 4,
51 + "h": 300,
52 + "lucene": "data_win_system_eventID:11 OR data_win_system_eventID:15 OR data_win_system_eventID:23 OR data_win_system_eventID:26",
53 + "field": "data_win_system_eventID",
54 + "size": 6
55 + },
56 + {
57 + "id": "by_agent",
58 + "title": "By Agent",
59 + "type": "pie",
60 + "w": 4,
61 + "h": 300,
62 + "lucene": "data_win_system_eventID:11 OR data_win_system_eventID:15 OR data_win_system_eventID:23 OR data_win_system_eventID:26",
63 + "field": "agent_name",
64 + "size": 10
65 + },
66 + {
67 + "id": "by_user",
68 + "title": "By User",
69 + "type": "pie",
70 + "w": 4,
71 + "h": 300,
72 + "lucene": "data_win_system_eventID:11 OR data_win_system_eventID:23",
73 + "field": "data_win_eventdata_user",
74 + "size": 10
75 + },
76 + {
77 + "id": "top_target_files",
78 + "title": "Top Target File Paths",
79 + "type": "bar_h",
80 + "w": 6,
81 + "h": 320,
82 + "lucene": "data_win_system_eventID:11 OR data_win_system_eventID:23 OR data_win_system_eventID:26",
83 + "field": "data_win_eventdata_targetFilename",
84 + "size": 10
85 + },
86 + {
87 + "id": "top_creating_processes",
88 + "title": "Top Creating Processes",
89 + "type": "bar_h",
90 + "w": 6,
91 + "h": 320,
92 + "lucene": "data_win_system_eventID:11",
93 + "field": "data_win_eventdata_image",
94 + "size": 10
95 + }
96 + ]
97 +}
backend/app/siem/dashboard_templates/wazuh_edr/EDR_IMAGE_LOAD.json new
+81
@@ -0,0 +1,81 @@
1 +{
2 + "id": "EDR_IMAGE_LOAD",
3 + "title": "Wazuh EDR - Driver & Image Load",
4 + "description": "Sysmon Event ID 6 (Driver Loaded) and Event ID 7 (Image Loaded / DLL). Tracks kernel drivers and shared libraries loaded by processes. Signature status visibility supports detection of unsigned or untrusted modules — key indicators of rootkits and DLL injection.",
5 + "panels": [
6 + {
7 + "id": "drivers_loaded",
8 + "title": "Drivers Loaded (EID 6)",
9 + "type": "stat",
10 + "w": 6,
11 + "h": 100,
12 + "lucene": "data_win_system_eventID:6"
13 + },
14 + {
15 + "id": "images_loaded",
16 + "title": "Images (DLL) Loaded (EID 7)",
17 + "type": "stat",
18 + "w": 6,
19 + "h": 100,
20 + "lucene": "data_win_system_eventID:7"
21 + },
22 + {
23 + "id": "image_load_over_time",
24 + "title": "Image Load Events Over Time",
25 + "type": "histogram",
26 + "w": 12,
27 + "h": 200,
28 + "lucene": "data_win_system_eventID:6 OR data_win_system_eventID:7"
29 + },
30 + {
31 + "id": "by_event_type",
32 + "title": "Driver vs Image Load",
33 + "type": "pie",
34 + "w": 4,
35 + "h": 300,
36 + "lucene": "data_win_system_eventID:6 OR data_win_system_eventID:7",
37 + "field": "data_win_system_eventID",
38 + "size": 4
39 + },
40 + {
41 + "id": "by_signed",
42 + "title": "By Signature Status",
43 + "type": "pie",
44 + "w": 4,
45 + "h": 300,
46 + "lucene": "data_win_system_eventID:7",
47 + "field": "data_win_eventdata_signed",
48 + "size": 4
49 + },
50 + {
51 + "id": "by_agent",
52 + "title": "By Agent",
53 + "type": "pie",
54 + "w": 4,
55 + "h": 300,
56 + "lucene": "data_win_system_eventID:6 OR data_win_system_eventID:7",
57 + "field": "agent_name",
58 + "size": 10
59 + },
60 + {
61 + "id": "top_loaded_images",
62 + "title": "Top Loaded Images (DLL)",
63 + "type": "bar_h",
64 + "w": 6,
65 + "h": 320,
66 + "lucene": "data_win_system_eventID:7",
67 + "field": "data_win_eventdata_imageLoaded",
68 + "size": 10
69 + },
70 + {
71 + "id": "top_loading_processes",
72 + "title": "Top Loading Processes",
73 + "type": "bar_h",
74 + "w": 6,
75 + "h": 320,
76 + "lucene": "data_win_system_eventID:7",
77 + "field": "data_win_eventdata_image",
78 + "size": 10
79 + }
80 + ]
81 +}
backend/app/siem/dashboard_templates/wazuh_edr/EDR_NETWORK_CONNECTIONS.json new
+109
@@ -0,0 +1,109 @@
1 +{
2 + "id": "EDR_NETWORK_CONNECTIONS",
3 + "title": "Wazuh EDR - Network Connections",
4 + "description": "Sysmon Event ID 3 (Network Connection), 8 (CreateRemoteThread), and 9 (RawAccessRead). Tracks outbound and inbound TCP/UDP connections by process, destination IPs, ports, and initiating users.",
5 + "panels": [
6 + {
7 + "id": "total_network",
8 + "title": "Network Connections (EID 3)",
9 + "type": "stat",
10 + "w": 4,
11 + "h": 100,
12 + "lucene": "data_win_system_eventID:3"
13 + },
14 + {
15 + "id": "remote_threads",
16 + "title": "Remote Thread Create (EID 8)",
17 + "type": "stat",
18 + "w": 4,
19 + "h": 100,
20 + "lucene": "data_win_system_eventID:8"
21 + },
22 + {
23 + "id": "raw_access",
24 + "title": "Raw Access Read (EID 9)",
25 + "type": "stat",
26 + "w": 4,
27 + "h": 100,
28 + "lucene": "data_win_system_eventID:9"
29 + },
30 + {
31 + "id": "network_over_time",
32 + "title": "Network Events Over Time",
33 + "type": "histogram",
34 + "w": 12,
35 + "h": 200,
36 + "lucene": "data_win_system_eventID:3"
37 + },
38 + {
39 + "id": "by_protocol",
40 + "title": "By Protocol",
41 + "type": "pie",
42 + "w": 4,
43 + "h": 300,
44 + "lucene": "data_win_system_eventID:3",
45 + "field": "data_win_eventdata_protocol",
46 + "size": 8
47 + },
48 + {
49 + "id": "by_initiated",
50 + "title": "Initiated vs Received",
51 + "type": "pie",
52 + "w": 4,
53 + "h": 300,
54 + "lucene": "data_win_system_eventID:3",
55 + "field": "data_win_eventdata_initiated",
56 + "size": 4
57 + },
58 + {
59 + "id": "by_agent",
60 + "title": "By Agent",
61 + "type": "pie",
62 + "w": 4,
63 + "h": 300,
64 + "lucene": "data_win_system_eventID:3",
65 + "field": "agent_name",
66 + "size": 10
67 + },
68 + {
69 + "id": "top_dst_ips",
70 + "title": "Top Destination IPs",
71 + "type": "bar_h",
72 + "w": 6,
73 + "h": 320,
74 + "lucene": "data_win_system_eventID:3",
75 + "field": "data_win_eventdata_destinationIp",
76 + "size": 10
77 + },
78 + {
79 + "id": "top_dst_ports",
80 + "title": "Top Destination Ports",
81 + "type": "bar_h",
82 + "w": 6,
83 + "h": 320,
84 + "lucene": "data_win_system_eventID:3",
85 + "field": "data_win_eventdata_destinationPort",
86 + "size": 10
87 + },
88 + {
89 + "id": "top_src_images",
90 + "title": "Top Connecting Processes",
91 + "type": "bar_h",
92 + "w": 6,
93 + "h": 320,
94 + "lucene": "data_win_system_eventID:3",
95 + "field": "data_win_eventdata_image",
96 + "size": 10
97 + },
98 + {
99 + "id": "top_users",
100 + "title": "Top Users",
101 + "type": "bar_h",
102 + "w": 6,
103 + "h": 320,
104 + "lucene": "data_win_system_eventID:3",
105 + "field": "data_win_eventdata_user",
106 + "size": 10
107 + }
108 + ]
109 +}
backend/app/siem/dashboard_templates/wazuh_edr/EDR_OVERVIEW.json new
+97
@@ -0,0 +1,97 @@
1 +{
2 + "id": "EDR_OVERVIEW",
3 + "title": "Wazuh EDR - Overview",
4 + "description": "High-level overview of all EDR endpoint activity: total events, severity distribution, most active agents, Sysmon event type breakdown, and top triggered detection rules.",
5 + "panels": [
6 + {
7 + "id": "total_events",
8 + "title": "Total Events",
9 + "type": "stat",
10 + "w": 3,
11 + "h": 100,
12 + "lucene": "*"
13 + },
14 + {
15 + "id": "high_severity",
16 + "title": "High Severity (≥ 12)",
17 + "type": "stat",
18 + "w": 3,
19 + "h": 100,
20 + "lucene": "rule_level:>11"
21 + },
22 + {
23 + "id": "critical",
24 + "title": "Critical (≥ 15)",
25 + "type": "stat",
26 + "w": 3,
27 + "h": 100,
28 + "lucene": "rule_level:>14"
29 + },
30 + {
31 + "id": "process_events",
32 + "title": "Process Create Events",
33 + "type": "stat",
34 + "w": 3,
35 + "h": 100,
36 + "lucene": "data_win_system_eventID:1"
37 + },
38 + {
39 + "id": "events_over_time",
40 + "title": "Events Over Time",
41 + "type": "histogram",
42 + "w": 12,
43 + "h": 200,
44 + "lucene": "*"
45 + },
46 + {
47 + "id": "by_rule_level",
48 + "title": "By Rule Severity Level",
49 + "type": "pie",
50 + "w": 4,
51 + "h": 300,
52 + "lucene": "*",
53 + "field": "rule_level",
54 + "size": 10
55 + },
56 + {
57 + "id": "by_sysmon_type",
58 + "title": "By Sysmon Event Type",
59 + "type": "pie",
60 + "w": 4,
61 + "h": 300,
62 + "lucene": "data_win_system_eventID:*",
63 + "field": "data_win_system_eventID",
64 + "size": 15
65 + },
66 + {
67 + "id": "by_agent",
68 + "title": "By Agent",
69 + "type": "pie",
70 + "w": 4,
71 + "h": 300,
72 + "lucene": "*",
73 + "field": "agent_name",
74 + "size": 10
75 + },
76 + {
77 + "id": "top_rules",
78 + "title": "Top Rules Triggered",
79 + "type": "bar_h",
80 + "w": 6,
81 + "h": 320,
82 + "lucene": "*",
83 + "field": "rule_description",
84 + "size": 10
85 + },
86 + {
87 + "id": "top_agents",
88 + "title": "Most Active Agents",
89 + "type": "bar_h",
90 + "w": 6,
91 + "h": 320,
92 + "lucene": "*",
93 + "field": "agent_name",
94 + "size": 10
95 + }
96 + ]
97 +}
backend/app/siem/dashboard_templates/wazuh_edr/EDR_PROCESS_ACTIVITY.json new
+79
@@ -0,0 +1,79 @@
1 +{
2 + "id": "EDR_PROCESS_ACTIVITY",
3 + "title": "Wazuh EDR - Process Activity",
4 + "description": "Sysmon Event ID 1 (Process Create), 5 (Process Terminated), and 25 (Process Tampering). Covers executed binaries, command-line arguments, parent process chains, and the users initiating execution.",
5 + "panels": [
6 + {
7 + "id": "process_create",
8 + "title": "Process Create (EID 1)",
9 + "type": "stat",
10 + "w": 4,
11 + "h": 100,
12 + "lucene": "data_win_system_eventID:1"
13 + },
14 + {
15 + "id": "process_terminate",
16 + "title": "Process Terminate (EID 5)",
17 + "type": "stat",
18 + "w": 4,
19 + "h": 100,
20 + "lucene": "data_win_system_eventID:5"
21 + },
22 + {
23 + "id": "process_tamper",
24 + "title": "Process Tampering (EID 25)",
25 + "type": "stat",
26 + "w": 4,
27 + "h": 100,
28 + "lucene": "data_win_system_eventID:25"
29 + },
30 + {
31 + "id": "process_over_time",
32 + "title": "Process Events Over Time",
33 + "type": "histogram",
34 + "w": 12,
35 + "h": 200,
36 + "lucene": "data_win_system_eventID:1 OR data_win_system_eventID:5 OR data_win_system_eventID:25"
37 + },
38 + {
39 + "id": "top_images",
40 + "title": "Top Executed Images",
41 + "type": "bar_h",
42 + "w": 6,
43 + "h": 320,
44 + "lucene": "data_win_system_eventID:1",
45 + "field": "data_win_eventdata_image",
46 + "size": 10
47 + },
48 + {
49 + "id": "top_parent_images",
50 + "title": "Top Parent Images",
51 + "type": "bar_h",
52 + "w": 6,
53 + "h": 320,
54 + "lucene": "data_win_system_eventID:1",
55 + "field": "data_win_eventdata_parentImage",
56 + "size": 10
57 + },
58 + {
59 + "id": "top_cmdlines",
60 + "title": "Top Command Lines",
61 + "type": "bar_h",
62 + "w": 6,
63 + "h": 320,
64 + "lucene": "data_win_system_eventID:1",
65 + "field": "data_win_eventdata_commandLine",
66 + "size": 10
67 + },
68 + {
69 + "id": "top_users",
70 + "title": "Top Users",
71 + "type": "bar_h",
72 + "w": 6,
73 + "h": 320,
74 + "lucene": "data_win_system_eventID:1",
75 + "field": "data_win_eventdata_user",
76 + "size": 10
77 + }
78 + ]
79 +}
backend/app/siem/dashboard_templates/wazuh_edr/EDR_REGISTRY_ACTIVITY.json new
+89
@@ -0,0 +1,89 @@
1 +{
2 + "id": "EDR_REGISTRY_ACTIVITY",
3 + "title": "Wazuh EDR - Registry Activity",
4 + "description": "Sysmon registry events: Event ID 12 (RegistryEvent – Object Added/Deleted), 13 (RegistryEvent – Value Set), and 14 (RegistryEvent – Object Renamed). Tracks Windows registry modifications — useful for detecting persistence mechanisms, malware configuration, and hijacking.",
5 + "panels": [
6 + {
7 + "id": "total_registry",
8 + "title": "Total Registry Events",
9 + "type": "stat",
10 + "w": 4,
11 + "h": 100,
12 + "lucene": "data_win_system_eventID:12 OR data_win_system_eventID:13 OR data_win_system_eventID:14"
13 + },
14 + {
15 + "id": "keys_added_deleted",
16 + "title": "Keys Added / Deleted (EID 12)",
17 + "type": "stat",
18 + "w": 4,
19 + "h": 100,
20 + "lucene": "data_win_system_eventID:12"
21 + },
22 + {
23 + "id": "values_set",
24 + "title": "Values Set (EID 13)",
25 + "type": "stat",
26 + "w": 4,
27 + "h": 100,
28 + "lucene": "data_win_system_eventID:13"
29 + },
30 + {
31 + "id": "registry_over_time",
32 + "title": "Registry Events Over Time",
33 + "type": "histogram",
34 + "w": 12,
35 + "h": 200,
36 + "lucene": "data_win_system_eventID:12 OR data_win_system_eventID:13 OR data_win_system_eventID:14"
37 + },
38 + {
39 + "id": "by_event_type",
40 + "title": "By Event Type",
41 + "type": "pie",
42 + "w": 4,
43 + "h": 300,
44 + "lucene": "data_win_system_eventID:12 OR data_win_system_eventID:13 OR data_win_system_eventID:14",
45 + "field": "data_win_system_eventID",
46 + "size": 4
47 + },
48 + {
49 + "id": "by_agent",
50 + "title": "By Agent",
51 + "type": "pie",
52 + "w": 4,
53 + "h": 300,
54 + "lucene": "data_win_system_eventID:12 OR data_win_system_eventID:13 OR data_win_system_eventID:14",
55 + "field": "agent_name",
56 + "size": 10
57 + },
58 + {
59 + "id": "by_user",
60 + "title": "By User",
61 + "type": "pie",
62 + "w": 4,
63 + "h": 300,
64 + "lucene": "data_win_system_eventID:12 OR data_win_system_eventID:13 OR data_win_system_eventID:14",
65 + "field": "data_win_eventdata_user",
66 + "size": 10
67 + },
68 + {
69 + "id": "top_target_objects",
70 + "title": "Top Target Registry Keys",
71 + "type": "bar_h",
72 + "w": 6,
73 + "h": 320,
74 + "lucene": "data_win_system_eventID:12 OR data_win_system_eventID:13 OR data_win_system_eventID:14",
75 + "field": "data_win_eventdata_targetObject",
76 + "size": 10
77 + },
78 + {
79 + "id": "top_modifying_processes",
80 + "title": "Top Modifying Processes",
81 + "type": "bar_h",
82 + "w": 6,
83 + "h": 320,
84 + "lucene": "data_win_system_eventID:12 OR data_win_system_eventID:13 OR data_win_system_eventID:14",
85 + "field": "data_win_eventdata_image",
86 + "size": 10
87 + }
88 + ]
89 +}
backend/app/siem/dashboard_templates/wazuh_edr/EDR_THREAT_DETECTIONS.json new
+99
@@ -0,0 +1,99 @@
1 +{
2 + "id": "EDR_THREAT_DETECTIONS",
3 + "title": "Wazuh EDR - Threat Detections",
4 + "description": "High and critical Wazuh rule detections (rule.level ≥ 12) across all EDR event sources. Covers suspicious process access (Event ID 10), elevated rule triggers, most affected agents, and involved users — the primary triage dashboard for EDR alerts.",
5 + "panels": [
6 + {
7 + "id": "high_severity",
8 + "title": "High Severity (≥ 12)",
9 + "type": "stat",
10 + "w": 4,
11 + "h": 100,
12 + "lucene": "rule_level:>11"
13 + },
14 + {
15 + "id": "critical",
16 + "title": "Critical (≥ 15)",
17 + "type": "stat",
18 + "w": 4,
19 + "h": 100,
20 + "lucene": "rule_level:>14"
21 + },
22 + {
23 + "id": "process_access",
24 + "title": "Process Access Events (EID 10)",
25 + "type": "stat",
26 + "w": 4,
27 + "h": 100,
28 + "lucene": "data_win_system_eventID:10"
29 + },
30 + {
31 + "id": "threats_over_time",
32 + "title": "Threat Detections Over Time",
33 + "type": "histogram",
34 + "w": 12,
35 + "h": 200,
36 + "lucene": "rule_level:>11"
37 + },
38 + {
39 + "id": "by_rule_level",
40 + "title": "By Rule Level",
41 + "type": "pie",
42 + "w": 4,
43 + "h": 300,
44 + "lucene": "rule_level:>11",
45 + "field": "rule_level",
46 + "size": 10
47 + },
48 + {
49 + "id": "by_agent",
50 + "title": "By Agent",
51 + "type": "pie",
52 + "w": 4,
53 + "h": 300,
54 + "lucene": "rule_level:>11",
55 + "field": "agent_name",
56 + "size": 10
57 + },
58 + {
59 + "id": "by_sysmon_type",
60 + "title": "By Sysmon Event Type",
61 + "type": "pie",
62 + "w": 4,
63 + "h": 300,
64 + "lucene": "rule_level:>11 AND data_win_system_eventID:*",
65 + "field": "data_win_system_eventID",
66 + "size": 15
67 + },
68 + {
69 + "id": "top_rules",
70 + "title": "Top Rules Triggered",
71 + "type": "bar_h",
72 + "w": 6,
73 + "h": 320,
74 + "lucene": "rule_level:>11",
75 + "field": "rule_description",
76 + "size": 10
77 + },
78 + {
79 + "id": "top_agents",
80 + "title": "Most Affected Agents",
81 + "type": "bar_h",
82 + "w": 6,
83 + "h": 320,
84 + "lucene": "rule_level:>11",
85 + "field": "agent_name",
86 + "size": 10
87 + },
88 + {
89 + "id": "top_users",
90 + "title": "Top Users Involved",
91 + "type": "bar_h",
92 + "w": 6,
93 + "h": 320,
94 + "lucene": "rule_level:>11",
95 + "field": "data_win_eventdata_user",
96 + "size": 10
97 + }
98 + ]
99 +}
backend/app/siem/dashboard_templates/wazuh_edr/_card.json new
+11
@@ -0,0 +1,11 @@
1 +{
2 + "id": "wazuh_edr",
3 + "title": "Wazuh - EDR",
4 + "description": "Endpoint Detection & Response dashboards for Wazuh-monitored Windows endpoints. Covers process execution, network connections, DNS queries, file operations, registry activity, driver/image loads, and threat detections powered by Sysmon telemetry.",
5 + "vendor": "Wazuh",
6 + "product": "EDR / Sysmon",
7 + "event_type": "EDR",
8 + "tags": ["wazuh", "edr", "sysmon", "windows", "endpoint", "detection"],
9 + "color": "#38bdf8",
10 + "icon": "cpu"
11 +}
backend/app/siem/routes/dashboards.py new
+167
@@ -0,0 +1,167 @@
1 +from fastapi import APIRouter
2 +from fastapi import Depends
3 +from fastapi import HTTPException
4 +from fastapi import Security
5 +from loguru import logger
6 +from sqlalchemy.ext.asyncio import AsyncSession
7 +from sqlalchemy.future import select
8 +
9 +from app.auth.models.users import User
10 +from app.auth.utils import AuthHandler
11 +from app.db.db_session import get_db
12 +from app.db.universal_models import Customers
13 +from app.middleware.customer_access import customer_access_handler
14 +from app.siem.schema.dashboards import DashboardCategoriesListResponse
15 +from app.siem.schema.dashboards import DashboardCategoryDetailResponse
16 +from app.siem.schema.dashboards import DisableDashboardResponse
17 +from app.siem.schema.dashboards import EnableDashboardRequest
18 +from app.siem.schema.dashboards import EnabledDashboardOperationResponse
19 +from app.siem.schema.dashboards import EnabledDashboardResponse
20 +from app.siem.schema.dashboards import EnabledDashboardsListResponse
21 +from app.siem.schema.dashboards import PanelDataRequest
22 +from app.siem.schema.dashboards import PanelDataResponse
23 +from app.siem.services.dashboards import disable_dashboard
24 +from app.siem.services.dashboards import enable_dashboard
25 +from app.siem.services.dashboards import get_category_detail
26 +from app.siem.services.dashboards import get_enabled_dashboards
27 +from app.siem.services.dashboards import get_panel_data
28 +from app.siem.services.dashboards import list_categories
29 +
30 +dashboards_router = APIRouter()
31 +
32 +
33 +async def verify_customer_exists(customer_code: str, db: AsyncSession) -> None:
34 + result = await db.execute(
35 + select(Customers).filter(Customers.customer_code == customer_code),
36 + )
37 + if not result.scalars().first():
38 + raise HTTPException(
39 + status_code=404,
40 + detail=f"Customer with customer_code {customer_code} not found",
41 + )
42 +
43 +
44 +# ── Browse available templates (filesystem) ─────────────────────
45 +
46 +
47 +@dashboards_router.get(
48 + "/templates",
49 + response_model=DashboardCategoriesListResponse,
50 + description="List all available dashboard categories (e.g. wazuh_edr, fortinet_edr)",
51 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
52 +)
53 +async def list_dashboard_categories() -> DashboardCategoriesListResponse:
54 + logger.info("Listing dashboard categories")
55 + categories = list_categories()
56 + return DashboardCategoriesListResponse(
57 + categories=categories,
58 + success=True,
59 + message="Dashboard categories retrieved successfully",
60 + )
61 +
62 +
63 +@dashboards_router.get(
64 + "/templates/{category_id}",
65 + response_model=DashboardCategoryDetailResponse,
66 + description="Get a dashboard category with all its template definitions",
67 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
68 +)
69 +async def get_dashboard_category(category_id: str) -> DashboardCategoryDetailResponse:
70 + logger.info(f"Getting dashboard category {category_id}")
71 + category = get_category_detail(category_id)
72 + return DashboardCategoryDetailResponse(
73 + category=category,
74 + success=True,
75 + message="Dashboard category retrieved successfully",
76 + )
77 +
78 +
79 +# ── Enabled dashboards (per-customer, DB-backed) ────────────────
80 +
81 +
82 +@dashboards_router.get(
83 + "/enabled/{customer_code}",
84 + response_model=EnabledDashboardsListResponse,
85 + description="List dashboards enabled for a customer",
86 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
87 +)
88 +async def list_enabled_dashboards(
89 + customer_code: str,
90 + current_user: User = Depends(AuthHandler().get_current_user),
91 + db: AsyncSession = Depends(get_db),
92 +) -> EnabledDashboardsListResponse:
93 + logger.info(f"Listing enabled dashboards for customer {customer_code}")
94 + if not await customer_access_handler.check_customer_access(current_user, customer_code, db):
95 + raise HTTPException(status_code=403, detail=f"Access denied to customer {customer_code}")
96 + await verify_customer_exists(customer_code, db)
97 + rows = await get_enabled_dashboards(customer_code, db)
98 + return EnabledDashboardsListResponse(
99 + enabled_dashboards=[EnabledDashboardResponse.from_orm(r) for r in rows],
100 + success=True,
101 + message="Enabled dashboards retrieved successfully",
102 + )
103 +
104 +
105 +@dashboards_router.post(
106 + "/enable",
107 + response_model=EnabledDashboardOperationResponse,
108 + description="Enable a dashboard template for a customer + event source",
109 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
110 +)
111 +async def enable_dashboard_endpoint(
112 + request: EnableDashboardRequest,
113 + db: AsyncSession = Depends(get_db),
114 +) -> EnabledDashboardOperationResponse:
115 + logger.info(f"Enabling dashboard for customer {request.customer_code}")
116 + await verify_customer_exists(request.customer_code, db)
117 + row = await enable_dashboard(request, db)
118 + return EnabledDashboardOperationResponse(
119 + enabled_dashboard=EnabledDashboardResponse.from_orm(row),
120 + success=True,
121 + message="Dashboard enabled successfully",
122 + )
123 +
124 +
125 +@dashboards_router.delete(
126 + "/disable/{dashboard_id}",
127 + response_model=DisableDashboardResponse,
128 + description="Disable (remove) an enabled dashboard",
129 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
130 +)
131 +async def disable_dashboard_endpoint(
132 + dashboard_id: int,
133 + db: AsyncSession = Depends(get_db),
134 +) -> DisableDashboardResponse:
135 + logger.info(f"Disabling dashboard {dashboard_id}")
136 + await disable_dashboard(dashboard_id, db)
137 + return DisableDashboardResponse(
138 + success=True,
139 + message="Dashboard disabled successfully",
140 + )
141 +
142 +
143 +# ── Panel data (execute queries and return chart-ready data) ─────
144 +
145 +
146 +@dashboards_router.post(
147 + "/panel-data",
148 + response_model=PanelDataResponse,
149 + description="Execute all panel queries for an enabled dashboard and return chart-ready data",
150 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
151 +)
152 +async def panel_data_endpoint(
153 + request: PanelDataRequest,
154 + db: AsyncSession = Depends(get_db),
155 +) -> PanelDataResponse:
156 + logger.info(f"Fetching panel data for dashboard {request.dashboard_id} (timerange={request.timerange})")
157 + data = await get_panel_data(request.dashboard_id, request.timerange, db)
158 + return PanelDataResponse(
159 + panels=data["results"],
160 + template=data["template"],
161 + dashboard_id=request.dashboard_id,
162 + customer_code=data["customer_code"],
163 + source_name=data["source_name"],
164 + accent_color=data["accent_color"],
165 + success=True,
166 + message="Panel data retrieved successfully",
167 + )
backend/app/siem/routes/events.py
+4
@@ -34,6 +34,8 @@ async def query_events_endpoint(
34 page_size: int = Query(50, ge=1, le=1000, description="Number of results per page"),
35 scroll_id: Optional[str] = Query(None, description="Scroll ID for fetching the next page"),
36 query: Optional[str] = Query(None, description="Lucene query string (e.g. 'agent_name:piHole AND agent_id:088')"),
37 + time_from: Optional[str] = Query(None, description="Absolute start time in ISO format. Overrides timerange."),
38 + time_to: Optional[str] = Query(None, description="Absolute end time in ISO format. Overrides timerange."),
39 current_user: User = Depends(AuthHandler().get_current_user),
40 db: AsyncSession = Depends(get_db),
41 ) -> EventsQueryResponse:
@@ -47,6 +49,8 @@ async def query_events_endpoint(
49 page_size=page_size,
50 scroll_id=scroll_id,
51 query=query,
52 + time_from=time_from,
53 + time_to=time_to,
54 )
55 return await query_events(customer_code, source_name, params, db)
56
backend/app/siem/schema/dashboards.py new
+124
@@ -0,0 +1,124 @@
1 +from datetime import datetime
2 +from typing import Any
3 +from typing import Dict
4 +from typing import List
5 +from typing import Optional
6 +
7 +from pydantic import BaseModel
8 +from pydantic import Field
9 +
10 +# ── Template browsing (read from disk) ───────────────────────────
11 +
12 +
13 +class DashboardPanel(BaseModel):
14 + id: str
15 + title: str
16 + type: str
17 + w: int
18 + h: int
19 + lucene: str
20 + field: Optional[str] = None
21 + size: Optional[int] = None
22 +
23 +
24 +class DashboardTemplate(BaseModel):
25 + id: str
26 + title: str
27 + description: str
28 + panels: List[DashboardPanel]
29 +
30 +
31 +class DashboardCategory(BaseModel):
32 + id: str
33 + title: str
34 + description: str
35 + vendor: str
36 + product: str
37 + event_type: str
38 + tags: List[str]
39 + color: str
40 + icon: str
41 +
42 +
43 +class DashboardCategoryWithTemplates(DashboardCategory):
44 + templates: List[DashboardTemplate]
45 +
46 +
47 +class DashboardCategoriesListResponse(BaseModel):
48 + categories: List[DashboardCategory]
49 + success: bool
50 + message: str
51 +
52 +
53 +class DashboardCategoryDetailResponse(BaseModel):
54 + category: DashboardCategoryWithTemplates
55 + success: bool
56 + message: str
57 +
58 +
59 +# ── Enabled dashboards (DB-backed, per customer) ────────────────
60 +
61 +
62 +class EnableDashboardRequest(BaseModel):
63 + customer_code: str = Field(..., max_length=50)
64 + event_source_id: int
65 + library_card: str = Field(..., max_length=255, description="Category id, e.g. 'wazuh_edr'")
66 + template_id: str = Field(..., max_length=255, description="Template id, e.g. 'EDR_OVERVIEW'")
67 + display_name: str = Field(..., max_length=255)
68 +
69 +
70 +class EnabledDashboardResponse(BaseModel):
71 + id: int
72 + customer_code: str
73 + event_source_id: int
74 + library_card: str
75 + template_id: str
76 + display_name: str
77 + created_at: datetime
78 +
79 + class Config:
80 + orm_mode = True
81 +
82 +
83 +class EnabledDashboardsListResponse(BaseModel):
84 + enabled_dashboards: List[EnabledDashboardResponse]
85 + success: bool
86 + message: str
87 +
88 +
89 +class EnabledDashboardOperationResponse(BaseModel):
90 + enabled_dashboard: Optional[EnabledDashboardResponse] = None
91 + success: bool
92 + message: str
93 +
94 +
95 +class DisableDashboardResponse(BaseModel):
96 + success: bool
97 + message: str
98 +
99 +
100 +# ── Panel data (for rendering dashboards) ────────────────────────
101 +
102 +
103 +class PanelDataRequest(BaseModel):
104 + dashboard_id: int
105 + timerange: str = Field("24h", description="Time range (e.g. '1h', '6h', '24h', '7d', '30d')")
106 +
107 +
108 +class PanelResult(BaseModel):
109 + type: str
110 + value: Optional[int] = None # stat panels
111 + labels: Optional[List[str]] = None # chart panels
112 + data: Optional[List[Any]] = None # chart panels
113 + error: Optional[str] = None
114 +
115 +
116 +class PanelDataResponse(BaseModel):
117 + panels: Dict[str, PanelResult]
118 + template: DashboardTemplate
119 + dashboard_id: int
120 + customer_code: str
121 + source_name: str
122 + accent_color: str = "#38bdf8"
123 + success: bool
124 + message: str
backend/app/siem/schema/events.py
+5
@@ -12,6 +12,11 @@ class EventsQueryParams(BaseModel):
12 page_size: int = Field(50, ge=1, le=1000, description="Number of results per page")
13 scroll_id: Optional[str] = Field(None, description="Scroll ID for fetching the next page")
14 query: Optional[str] = Field(None, description="Lucene query string (e.g. 'agent_name:piHole AND agent_id:088')")
15 + time_from: Optional[str] = Field(
16 + None,
17 + description="Absolute start time in ISO format (e.g. '2025-01-01T00:00:00Z'). Overrides timerange.",
18 + )
19 + time_to: Optional[str] = Field(None, description="Absolute end time in ISO format (e.g. '2025-01-31T23:59:59Z'). Overrides timerange.")
20
21
22 class EventsQueryResponse(BaseModel):
backend/app/siem/services/dashboards.py new
+290
@@ -0,0 +1,290 @@
1 +import json
2 +from pathlib import Path
3 +from typing import Any
4 +from typing import Dict
5 +from typing import List
6 +
7 +from fastapi import HTTPException
8 +from loguru import logger
9 +from sqlalchemy import select
10 +from sqlalchemy.ext.asyncio import AsyncSession
11 +
12 +from app.connectors.wazuh_indexer.utils.universal import AlertsQueryBuilder
13 +from app.connectors.wazuh_indexer.utils.universal import (
14 + create_wazuh_indexer_client_async,
15 +)
16 +from app.db.universal_models import EnabledDashboards
17 +from app.db.universal_models import EventSources
18 +from app.siem.schema.dashboards import DashboardCategory
19 +from app.siem.schema.dashboards import DashboardCategoryWithTemplates
20 +from app.siem.schema.dashboards import DashboardTemplate
21 +from app.siem.schema.dashboards import EnableDashboardRequest
22 +from app.siem.schema.dashboards import PanelResult
23 +
24 +TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "dashboard_templates"
25 +
26 +
27 +# ── Template browsing (filesystem) ──────────────────────────────
28 +
29 +
30 +def list_categories() -> List[DashboardCategory]:
31 + """Return every category that has a valid _card.json."""
32 + categories: List[DashboardCategory] = []
33 + if not TEMPLATES_DIR.is_dir():
34 + return categories
35 + for child in sorted(TEMPLATES_DIR.iterdir()):
36 + card_path = child / "_card.json"
37 + if child.is_dir() and card_path.is_file():
38 + data = json.loads(card_path.read_text())
39 + categories.append(DashboardCategory(**data))
40 + return categories
41 +
42 +
43 +def get_category_detail(category_id: str) -> DashboardCategoryWithTemplates:
44 + """Load one category card + all its dashboard templates."""
45 + category_dir = TEMPLATES_DIR / category_id
46 + card_path = category_dir / "_card.json"
47 + if not category_dir.is_dir() or not card_path.is_file():
48 + raise HTTPException(status_code=404, detail=f"Dashboard category '{category_id}' not found")
49 +
50 + card = json.loads(card_path.read_text())
51 + templates: List[DashboardTemplate] = []
52 + for tpl_file in sorted(category_dir.glob("*.json")):
53 + if tpl_file.name.startswith("_"):
54 + continue
55 + tpl_data = json.loads(tpl_file.read_text())
56 + templates.append(DashboardTemplate(**tpl_data))
57 +
58 + return DashboardCategoryWithTemplates(**card, templates=templates)
59 +
60 +
61 +# ── Enabled dashboards (database) ───────────────────────────────
62 +
63 +
64 +async def get_enabled_dashboards(
65 + customer_code: str,
66 + db: AsyncSession,
67 +) -> List[EnabledDashboards]:
68 + logger.info(f"Fetching enabled dashboards for customer {customer_code}")
69 + result = await db.execute(
70 + select(EnabledDashboards).where(EnabledDashboards.customer_code == customer_code),
71 + )
72 + return result.scalars().all()
73 +
74 +
75 +async def enable_dashboard(
76 + request: EnableDashboardRequest,
77 + db: AsyncSession,
78 +) -> EnabledDashboards:
79 + logger.info(
80 + f"Enabling dashboard {request.library_card}/{request.template_id} " f"for customer {request.customer_code}",
81 + )
82 +
83 + # Verify the category and template exist on disk
84 + category_dir = TEMPLATES_DIR / request.library_card
85 + card_path = category_dir / "_card.json"
86 + template_path = category_dir / f"{request.template_id}.json"
87 + if not card_path.is_file():
88 + raise HTTPException(status_code=404, detail=f"Dashboard category '{request.library_card}' not found")
89 + if not template_path.is_file():
90 + raise HTTPException(
91 + status_code=404,
92 + detail=f"Dashboard template '{request.template_id}' not found in category '{request.library_card}'",
93 + )
94 +
95 + # Verify event source exists
96 + es_result = await db.execute(
97 + select(EventSources).where(EventSources.id == request.event_source_id),
98 + )
99 + if not es_result.scalars().first():
100 + raise HTTPException(status_code=404, detail=f"Event source {request.event_source_id} not found")
101 +
102 + # Check for duplicate
103 + result = await db.execute(
104 + select(EnabledDashboards).where(
105 + EnabledDashboards.customer_code == request.customer_code,
106 + EnabledDashboards.event_source_id == request.event_source_id,
107 + EnabledDashboards.library_card == request.library_card,
108 + EnabledDashboards.template_id == request.template_id,
109 + ),
110 + )
111 + if result.scalars().first():
112 + raise HTTPException(
113 + status_code=400,
114 + detail="This dashboard is already enabled for this customer and event source",
115 + )
116 +
117 + row = EnabledDashboards(
118 + customer_code=request.customer_code,
119 + event_source_id=request.event_source_id,
120 + library_card=request.library_card,
121 + template_id=request.template_id,
122 + display_name=request.display_name,
123 + )
124 + db.add(row)
125 + await db.flush()
126 + await db.refresh(row)
127 + await db.commit()
128 + return row
129 +
130 +
131 +async def disable_dashboard(
132 + dashboard_id: int,
133 + db: AsyncSession,
134 +) -> None:
135 + result = await db.execute(
136 + select(EnabledDashboards).where(EnabledDashboards.id == dashboard_id),
137 + )
138 + row = result.scalars().first()
139 + if not row:
140 + raise HTTPException(status_code=404, detail="Enabled dashboard not found")
141 + await db.delete(row)
142 + await db.commit()
143 + logger.info(f"Disabled dashboard {dashboard_id}")
144 +
145 +
146 +# ── Panel data (execute queries for dashboard rendering) ─────────
147 +
148 +
149 +def _compute_histogram_interval(timerange: str) -> str:
150 + """Pick a reasonable date_histogram interval for the given timerange."""
151 + mapping = {
152 + "1h": "1m",
153 + "6h": "10m",
154 + "24h": "30m",
155 + "3d": "3h",
156 + "7d": "6h",
157 + "14d": "12h",
158 + "30d": "1d",
159 + }
160 + return mapping.get(timerange, "1h")
161 +
162 +
163 +async def get_panel_data(
164 + dashboard_id: int,
165 + timerange: str,
166 + db: AsyncSession,
167 +) -> Dict[str, Any]:
168 + """Execute each panel's query and return aggregated data for ECharts."""
169 +
170 + # Load the enabled dashboard row
171 + result = await db.execute(
172 + select(EnabledDashboards).where(EnabledDashboards.id == dashboard_id),
173 + )
174 + dashboard = result.scalars().first()
175 + if not dashboard:
176 + raise HTTPException(status_code=404, detail="Enabled dashboard not found")
177 +
178 + # Load the event source to get index_pattern + time_field
179 + es_result = await db.execute(
180 + select(EventSources).where(EventSources.id == dashboard.event_source_id),
181 + )
182 + event_source = es_result.scalars().first()
183 + if not event_source:
184 + raise HTTPException(status_code=404, detail="Event source not found")
185 + if not event_source.enabled:
186 + raise HTTPException(status_code=400, detail="Event source is disabled")
187 +
188 + # Load template JSON from disk
189 + template_path = TEMPLATES_DIR / dashboard.library_card / f"{dashboard.template_id}.json"
190 + if not template_path.is_file():
191 + raise HTTPException(status_code=404, detail="Dashboard template file not found")
192 +
193 + template = json.loads(template_path.read_text())
194 + panels = template.get("panels", [])
195 +
196 + # Load category card for accent color
197 + card_path = TEMPLATES_DIR / dashboard.library_card / "_card.json"
198 + accent_color = "#38bdf8"
199 + if card_path.is_file():
200 + card = json.loads(card_path.read_text())
201 + accent_color = card.get("color", accent_color)
202 +
203 + # Build time range filter
204 + query_builder = AlertsQueryBuilder()
205 + query_builder.add_time_range(timerange=timerange, timestamp_field=event_source.time_field)
206 + time_filter = query_builder.query["query"]["bool"]["must"]
207 +
208 + es_client = await create_wazuh_indexer_client_async("Wazuh-Indexer")
209 + results: Dict[str, PanelResult] = {}
210 +
211 + try:
212 + for panel in panels:
213 + pid = panel["id"]
214 + ptype = panel["type"]
215 + lucene = panel.get("lucene", "*")
216 + field = panel.get("field")
217 + size = panel.get("size", 10)
218 +
219 + try:
220 + # Build base query with time filter + Lucene
221 + body: dict = {
222 + "query": {
223 + "bool": {
224 + "must": [
225 + *time_filter,
226 + {"query_string": {"query": lucene, "default_operator": "AND"}},
227 + ],
228 + },
229 + },
230 + "size": 0,
231 + }
232 +
233 + if ptype == "stat":
234 + # Just need the total count
235 + resp = await es_client.search(index=event_source.index_pattern, body=body)
236 + total = resp["hits"]["total"]
237 + count = total["value"] if isinstance(total, dict) else total
238 + results[pid] = PanelResult(type="stat", value=count)
239 +
240 + elif ptype == "histogram":
241 + interval = _compute_histogram_interval(timerange)
242 + body["aggs"] = {
243 + "over_time": {
244 + "date_histogram": {
245 + "field": event_source.time_field,
246 + "fixed_interval": interval,
247 + "min_doc_count": 0,
248 + },
249 + },
250 + }
251 + resp = await es_client.search(index=event_source.index_pattern, body=body)
252 + buckets = resp["aggregations"]["over_time"]["buckets"]
253 + labels = [b["key_as_string"] for b in buckets]
254 + data = [b["doc_count"] for b in buckets]
255 + results[pid] = PanelResult(type="histogram", labels=labels, data=data)
256 +
257 + elif ptype in ("pie", "bar_h"):
258 + if not field:
259 + results[pid] = PanelResult(type=ptype, error="No field specified for aggregation")
260 + continue
261 + body["aggs"] = {
262 + "top_values": {
263 + "terms": {
264 + "field": field,
265 + "size": size,
266 + },
267 + },
268 + }
269 + resp = await es_client.search(index=event_source.index_pattern, body=body)
270 + buckets = resp["aggregations"]["top_values"]["buckets"]
271 + labels = [str(b["key"]) for b in buckets]
272 + data = [b["doc_count"] for b in buckets]
273 + results[pid] = PanelResult(type=ptype, labels=labels, data=data)
274 +
275 + else:
276 + results[pid] = PanelResult(type=ptype, error=f"Unknown panel type: {ptype}")
277 +
278 + except Exception as e:
279 + logger.error(f"Error querying panel {pid}: {e}")
280 + results[pid] = PanelResult(type=ptype, error=str(e))
281 + finally:
282 + await es_client.close()
283 +
284 + return {
285 + "results": results,
286 + "template": template,
287 + "accent_color": accent_color,
288 + "customer_code": dashboard.customer_code,
289 + "source_name": event_source.name,
290 + }
backend/app/siem/services/events.py
+8 -1
@@ -60,6 +60,8 @@ async def query_events(
60 timerange=params.timerange,
61 page_size=params.page_size,
62 query=params.query,
63 + time_from=params.time_from,
64 + time_to=params.time_to,
65 )
66
67
@@ -69,11 +71,16 @@ async def _initial_search(
71 timerange: str,
72 page_size: int,
73 query: str = None,
74 + time_from: str = None,
75 + time_to: str = None,
76 ) -> EventsQueryResponse:
77 es_client = await create_wazuh_indexer_client_async("Wazuh-Indexer")
78 try:
79 query_builder = AlertsQueryBuilder()
76 - query_builder.add_time_range(timerange=timerange, timestamp_field=time_field)
80 + if time_from and time_to:
81 + query_builder.add_absolute_time_range(time_from=time_from, time_to=time_to, timestamp_field=time_field)
82 + else:
83 + query_builder.add_time_range(timerange=timerange, timestamp_field=time_field)
84 query_builder.add_sort(time_field, order="desc")
85
86 # Add Lucene query_string if provided
backend/app/version/services/version.py
+1 -1
@@ -7,7 +7,7 @@ from loguru import logger
7 from packaging.version import Version
8
9 # Current version - update this with each release
10 -CURRENT_VERSION = "0.1.51"
10 +CURRENT_VERSION = "0.1.52"
11 VERSION_CHECK_URL = "https://api.github.com/repos/socfortress/CoPilot/releases/latest"
12
13
customer_portal/package.json
+1
@@ -26,6 +26,7 @@
26 "@vueuse/core": "^14.1.0",
27 "axios": "^1.13.2",
28 "dayjs": "^1.11.19",
29 + "echarts": "^6.0.0",
30 "jose": "^6.1.2",
31 "lodash": "^4.17.21",
32 "mitt": "^3.0.1",
customer_portal/pnpm-lock.yaml
+23
@@ -20,6 +20,9 @@ importers:
20 dayjs:
21 specifier: ^1.11.19
22 version: 1.11.19
23 + echarts:
24 + specifier: ^6.0.0
25 + version: 6.0.0
26 jose:
27 specifier: ^6.1.2
28 version: 6.1.2
@@ -1467,6 +1470,9 @@ packages:
1470 resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
1471 engines: {node: '>= 0.4'}
1472
1473 + echarts@6.0.0:
1474 + resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==}
1475 +
1476 electron-to-chromium@1.5.218:
1477 resolution: {integrity: sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==}
1478
@@ -2727,6 +2733,9 @@ packages:
2733 peerDependencies:
2734 typescript: '>=4.0.0'
2735
2736 + tslib@2.3.0:
2737 + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
2738 +
2739 tslib@2.8.1:
2740 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
2741
@@ -2952,6 +2961,9 @@ packages:
2961 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
2962 engines: {node: '>=10'}
2963
2964 + zrender@6.0.0:
2965 + resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==}
2966 +
2967 zwitch@2.0.4:
2968 resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
2969
@@ -4276,6 +4288,11 @@ snapshots:
4288 es-errors: 1.3.0
4289 gopd: 1.2.0
4290
4291 + echarts@6.0.0:
4292 + dependencies:
4293 + tslib: 2.3.0
4294 + zrender: 6.0.0
4295 +
4296 electron-to-chromium@1.5.218: {}
4297
4298 electron-to-chromium@1.5.262: {}
@@ -5652,6 +5669,8 @@ snapshots:
5669 picomatch: 4.0.3
5670 typescript: 5.9.3
5671
5672 + tslib@2.3.0: {}
5673 +
5674 tslib@2.8.1: {}
5675
5676 type-check@0.4.0:
@@ -5884,4 +5903,8 @@ snapshots:
5903
5904 yocto-queue@0.1.0: {}
5905
5906 + zrender@6.0.0:
5907 + dependencies:
5908 + tslib: 2.3.0
5909 +
5910 zwitch@2.0.4: {}
customer_portal/src/api/siem.ts
+65 -1
@@ -21,6 +21,53 @@ export interface FieldMapping {
21 type: string
22 }
23
24 +export interface DashboardPanel {
25 + id: string
26 + title: string
27 + type: string
28 + w: number
29 + h: number
30 + lucene: string
31 + field?: string
32 + size?: number
33 +}
34 +
35 +export interface DashboardTemplate {
36 + id: string
37 + title: string
38 + description: string
39 + panels: DashboardPanel[]
40 +}
41 +
42 +export interface EnabledDashboard {
43 + id: number
44 + customer_code: string
45 + event_source_id: number
46 + library_card: string
47 + template_id: string
48 + display_name: string
49 + created_at: string
50 +}
51 +
52 +export interface PanelResult {
53 + type: string
54 + value: number | null
55 + labels: string[]
56 + data: number[]
57 + error: string | null
58 +}
59 +
60 +export interface PanelDataResponse {
61 + panels: Record<string, PanelResult>
62 + template: DashboardTemplate
63 + dashboard_id: number
64 + customer_code: string
65 + source_name: string
66 + accent_color: string
67 + success: boolean
68 + message: string
69 +}
70 +
71 export class SiemAPI {
72 static async getCustomerCodes(): Promise<{ success: boolean; customer_codes: string[] }> {
73 return (await httpClient.get("/auth/me/customers")).data
@@ -35,7 +82,14 @@ export class SiemAPI {
82 static async queryEvents(
83 customerCode: string,
84 sourceName: string,
38 - params: { timerange?: string; page_size?: number; scroll_id?: string; query?: string }
85 + params: {
86 + timerange?: string
87 + page_size?: number
88 + scroll_id?: string
89 + query?: string
90 + time_from?: string
91 + time_to?: string
92 + }
93 ): Promise<{
94 success: boolean
95 message: string
@@ -59,4 +113,14 @@ export class SiemAPI {
113 }> {
114 return (await httpClient.get(`/siem/events/${customerCode}/${sourceName}/fields`)).data
115 }
116 +
117 + static async getEnabledDashboards(
118 + customerCode: string
119 + ): Promise<{ success: boolean; enabled_dashboards: EnabledDashboard[] }> {
120 + return (await httpClient.get(`/siem/dashboards/enabled/${customerCode}`)).data
121 + }
122 +
123 + static async getPanelData(dashboardId: number, timerange: string): Promise<PanelDataResponse> {
124 + return (await httpClient.post(`/siem/dashboards/panel-data`, { dashboard_id: dashboardId, timerange })).data
125 + }
126 }
customer_portal/src/router/index.ts
+14
@@ -6,6 +6,8 @@ import CasesPage from "@/views/CasesPage.vue"
6 import CaseDetailsView from "@/views/CaseDetailsView.vue"
7 import AgentsPage from "@/views/AgentsPage.vue"
8 import EventSearchPage from "@/views/EventSearchPage.vue"
9 +import DashboardsPage from "@/views/DashboardsPage.vue"
10 +import DashboardViewPage from "@/views/DashboardViewPage.vue"
11
12 const NotFound = {
13 template: `
@@ -68,6 +70,18 @@ const routes = [
70 component: EventSearchPage,
71 meta: { requiresAuth: true }
72 },
73 + {
74 + path: "/dashboards",
75 + name: "Dashboards",
76 + component: DashboardsPage,
77 + meta: { requiresAuth: true }
78 + },
79 + {
80 + path: "/dashboards/view/:id",
81 + name: "DashboardView",
82 + component: DashboardViewPage,
83 + meta: { requiresAuth: true }
84 + },
85 {
86 path: "/:pathMatch(.*)*",
87 name: "NotFound",
customer_portal/src/views/AgentsPage.vue
+6
@@ -46,6 +46,12 @@
46 >
47 Event Search
48 </router-link>
49 + <router-link
50 + to="/dashboards"
51 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
52 + >
53 + Dashboards
54 + </router-link>
55 </nav>
56 </div>
57 <div class="flex items-center space-x-4">
customer_portal/src/views/AlertsPage.vue
+6
@@ -46,6 +46,12 @@
46 >
47 Event Search
48 </router-link>
49 + <router-link
50 + to="/dashboards"
51 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
52 + >
53 + Dashboards
54 + </router-link>
55 </nav>
56 </div>
57 <div class="flex items-center space-x-4">
customer_portal/src/views/CasesPage.vue
+6
@@ -46,6 +46,12 @@
46 >
47 Event Search
48 </router-link>
49 + <router-link
50 + to="/dashboards"
51 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
52 + >
53 + Dashboards
54 + </router-link>
55 </nav>
56 </div>
57 <div class="flex items-center space-x-4">
customer_portal/src/views/DashboardViewPage.vue new
+462
@@ -0,0 +1,462 @@
1 +<template>
2 + <div class="min-h-screen bg-gray-50">
3 + <!-- Header -->
4 + <header class="border-b bg-white shadow-sm">
5 + <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
6 + <div class="flex h-16 justify-between">
7 + <div class="flex items-center">
8 + <div class="mr-3 min-h-8">
9 + <img
10 + v-if="portalLogo && showLogo"
11 + class="h-8 w-auto"
12 + :src="portalLogo"
13 + :alt="portalTitle"
14 + @error="showLogo = false"
15 + />
16 + </div>
17 + <h1 class="text-xl font-semibold text-gray-900">{{ portalTitle }}</h1>
18 + <nav class="ml-8 flex space-x-8">
19 + <router-link
20 + to="/"
21 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
22 + >
23 + Overview
24 + </router-link>
25 + <router-link
26 + to="/alerts"
27 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
28 + >
29 + Alerts
30 + </router-link>
31 + <router-link
32 + to="/cases"
33 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
34 + >
35 + Cases
36 + </router-link>
37 + <router-link
38 + to="/agents"
39 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
40 + >
41 + Agents
42 + </router-link>
43 + <router-link
44 + to="/event-search"
45 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
46 + >
47 + Event Search
48 + </router-link>
49 + <router-link
50 + to="/dashboards"
51 + class="rounded-md border-b-2 border-indigo-600 px-3 py-2 text-sm font-medium text-indigo-600"
52 + >
53 + Dashboards
54 + </router-link>
55 + </nav>
56 + </div>
57 + <div class="flex items-center space-x-4">
58 + <div class="text-sm text-gray-700">
59 + Welcome,
60 + <span class="font-medium">{{ username }}</span>
61 + </div>
62 + <button
63 + @click="logout"
64 + class="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700"
65 + >
66 + Logout
67 + </button>
68 + </div>
69 + </div>
70 + </div>
71 + </header>
72 +
73 + <!-- Main Content -->
74 + <div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
75 + <!-- Dashboard Header Bar -->
76 + <div class="mb-6 rounded-lg bg-white shadow">
77 + <div class="px-4 py-4 sm:px-6">
78 + <div class="flex flex-wrap items-center justify-between gap-4">
79 + <div class="flex items-center gap-3">
80 + <button
81 + @click="router.push('/dashboards')"
82 + class="rounded-md p-1.5 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
83 + >
84 + <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
85 + <path
86 + stroke-linecap="round"
87 + stroke-linejoin="round"
88 + stroke-width="2"
89 + d="M10 19l-7-7m0 0l7-7m-7 7h18"
90 + />
91 + </svg>
92 + </button>
93 + <div>
94 + <h2 class="text-lg font-semibold text-gray-900">
95 + {{ dashboardTitle || "Loading..." }}
96 + </h2>
97 + <p class="text-sm text-gray-500">{{ dashboardDescription }}</p>
98 + </div>
99 + </div>
100 + <div class="flex items-center gap-2">
101 + <button
102 + v-for="preset in timePresets"
103 + :key="preset.value"
104 + @click="selectTimerange(preset.value)"
105 + class="rounded-md px-3 py-1.5 text-sm font-medium transition-colors"
106 + :class="
107 + selectedTimerange === preset.value
108 + ? 'bg-indigo-600 text-white'
109 + : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
110 + "
111 + >
112 + {{ preset.label }}
113 + </button>
114 + <button
115 + @click="fetchPanelData"
116 + :disabled="loading"
117 + class="ml-2 rounded-md bg-gray-100 p-1.5 text-gray-600 hover:bg-gray-200 disabled:opacity-50"
118 + >
119 + <svg
120 + class="h-4 w-4"
121 + :class="{ 'animate-spin': loading }"
122 + fill="none"
123 + stroke="currentColor"
124 + viewBox="0 0 24 24"
125 + >
126 + <path
127 + stroke-linecap="round"
128 + stroke-linejoin="round"
129 + stroke-width="2"
130 + d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
131 + />
132 + </svg>
133 + </button>
134 + </div>
135 + </div>
136 + </div>
137 + </div>
138 +
139 + <!-- Error -->
140 + <div v-if="errorMsg" class="mb-6 rounded-md border border-red-300 bg-red-50 p-4">
141 + <p class="text-sm text-red-700">{{ errorMsg }}</p>
142 + </div>
143 +
144 + <!-- Loading -->
145 + <div v-if="loading && !hasData" class="rounded-lg bg-white px-4 py-12 text-center shadow">
146 + <div class="mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-indigo-600"></div>
147 + <p class="mt-2 text-sm text-gray-500">Loading dashboard panels...</p>
148 + </div>
149 +
150 + <!-- Panels Grid -->
151 + <div v-if="hasData || (!loading && panels.length > 0)" class="panel-grid">
152 + <div v-for="panel in panels" :key="panel.id" :style="{ gridColumn: `span ${panel.w}` }">
153 + <!-- Stat Panel -->
154 + <div
155 + v-if="panel.type === 'stat'"
156 + class="cursor-pointer rounded-lg bg-white p-6 text-center shadow transition-shadow hover:shadow-md"
157 + @click="openEventSearch(panel.lucene || '*')"
158 + >
159 + <p class="text-xs font-medium tracking-wide text-gray-500 uppercase">{{ panel.title }}</p>
160 + <p class="mt-2 text-3xl font-bold" :style="{ color: accentColor }">
161 + {{ formatStatValue(panelResults[panel.id]?.value) }}
162 + </p>
163 + <p v-if="panelResults[panel.id]?.error" class="mt-1 text-xs text-red-500">
164 + {{ panelResults[panel.id].error }}
165 + </p>
166 + </div>
167 +
168 + <!-- Chart Panel -->
169 + <div v-else class="rounded-lg bg-white shadow">
170 + <div class="border-b border-gray-100 px-4 py-3">
171 + <h3 class="text-sm font-medium text-gray-700">{{ panel.title }}</h3>
172 + </div>
173 + <div class="p-2">
174 + <div :id="`chart-${panel.id}`" :style="{ height: `${panel.h}px`, width: '100%' }"></div>
175 + </div>
176 + <p v-if="panelResults[panel.id]?.error" class="px-4 pb-2 text-xs text-red-500">
177 + {{ panelResults[panel.id].error }}
178 + </p>
179 + </div>
180 + </div>
181 + </div>
182 + </div>
183 + </div>
184 +</template>
185 +
186 +<script setup lang="ts">
187 +import type { ECharts } from "echarts/core"
188 +import type { DashboardPanel, PanelResult } from "@/api/siem"
189 +import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from "vue"
190 +import { useRoute, useRouter } from "vue-router"
191 +import { usePortalSettingsStore } from "@/stores/portalSettings"
192 +import { SiemAPI } from "@/api/siem"
193 +import { BarChart, LineChart, PieChart } from "echarts/charts"
194 +import { GridComponent, LegendComponent, TooltipComponent } from "echarts/components"
195 +import { init as echartsInit, use as echartsUse } from "echarts/core"
196 +import { CanvasRenderer } from "echarts/renderers"
197 +
198 +echartsUse([TooltipComponent, LegendComponent, GridComponent, PieChart, BarChart, LineChart, CanvasRenderer])
199 +
200 +const route = useRoute()
201 +const router = useRouter()
202 +const portalSettingsStore = usePortalSettingsStore()
203 +
204 +const showLogo = ref(true)
205 +
206 +const username = computed(() => {
207 + try {
208 + const user = JSON.parse(localStorage.getItem("customer-portal-user") || "{}")
209 + return user.username || "User"
210 + } catch {
211 + return "User"
212 + }
213 +})
214 +const portalTitle = computed(() => portalSettingsStore.portalTitle || "Customer Portal")
215 +const portalLogo = computed(() => portalSettingsStore.portalLogo)
216 +
217 +function logout() {
218 + localStorage.removeItem("customer-portal-auth-token")
219 + localStorage.removeItem("customer-portal-user")
220 + router.push("/login")
221 +}
222 +
223 +// -- Dashboard state --
224 +const dashboardId = computed(() => Number(route.params.id))
225 +
226 +const COLORS = [
227 + "#6366f1",
228 + "#818cf8",
229 + "#34d399",
230 + "#fbbf24",
231 + "#f87171",
232 + "#a78bfa",
233 + "#fb923c",
234 + "#2dd4bf",
235 + "#e879f9",
236 + "#94a3b8"
237 +]
238 +
239 +const accentColor = ref("#6366f1")
240 +const customerCode = ref("")
241 +const sourceName = ref("")
242 +const dashboardTitle = ref("")
243 +const dashboardDescription = ref("")
244 +const panels = ref<DashboardPanel[]>([])
245 +const panelResults = ref<Record<string, PanelResult>>({})
246 +const loading = ref(false)
247 +const errorMsg = ref("")
248 +const selectedTimerange = ref("24h")
249 +
250 +const timePresets = [
251 + { label: "1h", value: "1h" },
252 + { label: "6h", value: "6h" },
253 + { label: "24h", value: "24h" },
254 + { label: "7d", value: "7d" },
255 + { label: "30d", value: "30d" }
256 +]
257 +
258 +const hasData = computed(() => Object.keys(panelResults.value).length > 0)
259 +
260 +const chartInstances = ref<Map<string, ECharts>>(new Map())
261 +let resizeObservers: ResizeObserver[] = []
262 +
263 +function formatStatValue(value: number | null | undefined): string {
264 + if (value == null) return "—"
265 + if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`
266 + if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`
267 + return value.toLocaleString()
268 +}
269 +
270 +function selectTimerange(value: string) {
271 + selectedTimerange.value = value
272 + fetchPanelData()
273 +}
274 +
275 +// -- Drill-down to Event Search --
276 +function openEventSearch(luceneQuery: string) {
277 + const routeData = router.resolve({
278 + path: "/event-search",
279 + query: {
280 + customer_code: customerCode.value,
281 + source_name: sourceName.value,
282 + query: luceneQuery
283 + }
284 + })
285 + window.open(routeData.href, "_blank")
286 +}
287 +
288 +function buildDrilldownQuery(panel: DashboardPanel, clickedValue: string): string {
289 + const baseLucene = panel.lucene && panel.lucene !== "*" ? `(${panel.lucene})` : ""
290 + const fieldFilter = panel.field ? `${panel.field}:"${clickedValue}"` : ""
291 + return [baseLucene, fieldFilter].filter(Boolean).join(" AND ")
292 +}
293 +
294 +// -- Fetch panel data --
295 +async function fetchPanelData() {
296 + loading.value = true
297 + errorMsg.value = ""
298 + try {
299 + const res = await SiemAPI.getPanelData(dashboardId.value, selectedTimerange.value)
300 + if (res.success) {
301 + const tpl = res.template
302 + dashboardTitle.value = tpl.title
303 + dashboardDescription.value = tpl.description
304 + panels.value = tpl.panels
305 + accentColor.value = res.accent_color || "#6366f1"
306 + customerCode.value = res.customer_code
307 + sourceName.value = res.source_name
308 + panelResults.value = res.panels
309 + await nextTick()
310 + renderAllCharts()
311 + } else {
312 + errorMsg.value = res.message || "Failed to fetch panel data"
313 + }
314 + } catch (err: any) {
315 + errorMsg.value = err.response?.data?.detail || err.message || "Failed to fetch panel data"
316 + } finally {
317 + loading.value = false
318 + }
319 +}
320 +
321 +// -- ECharts options builders --
322 +function getHistogramOptions(result: PanelResult) {
323 + return {
324 + tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
325 + grid: { left: 50, right: 20, top: 10, bottom: 30 },
326 + xAxis: {
327 + type: "category",
328 + data: result.labels,
329 + axisLabel: { color: "#6b7280", fontSize: 10, rotate: result.labels.length > 20 ? 45 : 0 },
330 + axisLine: { lineStyle: { color: "#e5e7eb" } }
331 + },
332 + yAxis: {
333 + type: "value",
334 + axisLabel: { color: "#6b7280", fontSize: 10 },
335 + splitLine: { lineStyle: { color: "#f3f4f6" } }
336 + },
337 + series: [
338 + {
339 + type: "bar",
340 + data: result.data,
341 + itemStyle: { color: accentColor.value, borderRadius: [2, 2, 0, 0] }
342 + }
343 + ]
344 + }
345 +}
346 +
347 +function getPieOptions(result: PanelResult) {
348 + const data = result.labels.map((label, i) => ({ value: result.data[i], name: label }))
349 + return {
350 + tooltip: { trigger: "item", formatter: "{b}: <strong>{c}</strong> ({d}%)" },
351 + legend: {
352 + type: "scroll",
353 + orient: "vertical",
354 + right: 10,
355 + top: 20,
356 + bottom: 20,
357 + textStyle: { color: "#374151", fontSize: 11 }
358 + },
359 + series: [
360 + {
361 + type: "pie",
362 + radius: ["40%", "70%"],
363 + center: ["35%", "50%"],
364 + avoidLabelOverlap: true,
365 + itemStyle: { borderColor: "#ffffff", borderWidth: 2, borderRadius: 4 },
366 + label: { show: false },
367 + color: COLORS,
368 + data
369 + }
370 + ]
371 + }
372 +}
373 +
374 +function getBarHOptions(result: PanelResult) {
375 + return {
376 + tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
377 + grid: { left: 10, right: 30, top: 10, bottom: 10, containLabel: true },
378 + xAxis: {
379 + type: "value",
380 + axisLabel: { color: "#6b7280", fontSize: 10 },
381 + splitLine: { lineStyle: { color: "#f3f4f6" } }
382 + },
383 + yAxis: {
384 + type: "category",
385 + data: [...result.labels].reverse(),
386 + axisLabel: { color: "#6b7280", fontSize: 10, width: 180, overflow: "truncate" },
387 + axisLine: { lineStyle: { color: "#e5e7eb" } }
388 + },
389 + series: [
390 + {
391 + type: "bar",
392 + data: [...result.data].reverse(),
393 + itemStyle: { color: accentColor.value, borderRadius: [0, 2, 2, 0] }
394 + }
395 + ]
396 + }
397 +}
398 +
399 +function renderChart(panel: DashboardPanel) {
400 + const result = panelResults.value[panel.id]
401 + if (!result || panel.type === "stat") return
402 +
403 + const dom = document.getElementById(`chart-${panel.id}`)
404 + if (!dom) return
405 +
406 + let chart = chartInstances.value.get(panel.id)
407 + if (!chart) {
408 + chart = echartsInit(dom)
409 + chartInstances.value.set(panel.id, chart)
410 +
411 + // Click handler for drill-down to Event Search
412 + chart.on("click", (params: { name?: string }) => {
413 + if (!params.name) return
414 + const query = buildDrilldownQuery(panel, params.name)
415 + if (query) openEventSearch(query)
416 + })
417 +
418 + const observer = new ResizeObserver(() => chart?.resize())
419 + observer.observe(dom)
420 + resizeObservers.push(observer)
421 + }
422 +
423 + let options
424 + if (panel.type === "histogram") options = getHistogramOptions(result)
425 + else if (panel.type === "pie") options = getPieOptions(result)
426 + else if (panel.type === "bar_h") options = getBarHOptions(result)
427 +
428 + if (options) {
429 + chart.setOption(options, true)
430 + }
431 +}
432 +
433 +function renderAllCharts() {
434 + for (const panel of panels.value) {
435 + if (panel.type !== "stat") {
436 + renderChart(panel)
437 + }
438 + }
439 +}
440 +
441 +// -- Lifecycle --
442 +onMounted(() => {
443 + fetchPanelData()
444 +})
445 +
446 +onBeforeUnmount(() => {
447 + for (const chart of chartInstances.value.values()) {
448 + chart.dispose()
449 + }
450 + for (const obs of resizeObservers) {
451 + obs.disconnect()
452 + }
453 +})
454 +</script>
455 +
456 +<style scoped>
457 +.panel-grid {
458 + display: grid;
459 + grid-template-columns: repeat(12, 1fr);
460 + gap: 16px;
461 +}
462 +</style>
customer_portal/src/views/DashboardsPage.vue new
+277
@@ -0,0 +1,277 @@
1 +<template>
2 + <div class="min-h-screen bg-gray-50">
3 + <!-- Header -->
4 + <header class="border-b bg-white shadow-sm">
5 + <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
6 + <div class="flex h-16 justify-between">
7 + <div class="flex items-center">
8 + <div class="mr-3 min-h-8">
9 + <img
10 + v-if="portalLogo && showLogo"
11 + class="h-8 w-auto"
12 + :src="portalLogo"
13 + :alt="portalTitle"
14 + @error="showLogo = false"
15 + />
16 + </div>
17 + <h1 class="text-xl font-semibold text-gray-900">{{ portalTitle }}</h1>
18 + <nav class="ml-8 flex space-x-8">
19 + <router-link
20 + to="/"
21 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
22 + >
23 + Overview
24 + </router-link>
25 + <router-link
26 + to="/alerts"
27 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
28 + >
29 + Alerts
30 + </router-link>
31 + <router-link
32 + to="/cases"
33 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
34 + >
35 + Cases
36 + </router-link>
37 + <router-link
38 + to="/agents"
39 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
40 + >
41 + Agents
42 + </router-link>
43 + <router-link
44 + to="/event-search"
45 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
46 + >
47 + Event Search
48 + </router-link>
49 + <router-link
50 + to="/dashboards"
51 + class="rounded-md border-b-2 border-indigo-600 px-3 py-2 text-sm font-medium text-indigo-600"
52 + >
53 + Dashboards
54 + </router-link>
55 + </nav>
56 + </div>
57 + <div class="flex items-center space-x-4">
58 + <div class="text-sm text-gray-700">
59 + Welcome,
60 + <span class="font-medium">{{ username }}</span>
61 + </div>
62 + <button
63 + @click="logout"
64 + class="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700"
65 + >
66 + Logout
67 + </button>
68 + </div>
69 + </div>
70 + </div>
71 + </header>
72 +
73 + <!-- Main Content -->
74 + <div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
75 + <!-- Customer Selector -->
76 + <div class="mb-6 rounded-lg bg-white shadow">
77 + <div class="px-4 py-5 sm:p-6">
78 + <div class="grid grid-cols-1 gap-4 md:grid-cols-4">
79 + <div>
80 + <label for="customer-select" class="block text-sm font-medium text-gray-700">
81 + Customer
82 + </label>
83 + <select
84 + id="customer-select"
85 + v-model="selectedCustomerCode"
86 + @change="onCustomerChange"
87 + class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
88 + >
89 + <option value="">Select a customer</option>
90 + <option v-for="code in customerCodes" :key="code" :value="code">
91 + {{ code }}
92 + </option>
93 + </select>
94 + </div>
95 + </div>
96 + </div>
97 + </div>
98 +
99 + <!-- Error -->
100 + <div v-if="error" class="mb-6 rounded-md border border-red-300 bg-red-50 p-4">
101 + <p class="text-sm text-red-700">{{ error }}</p>
102 + </div>
103 +
104 + <!-- Loading -->
105 + <div v-if="loading" class="rounded-lg bg-white px-4 py-12 text-center shadow">
106 + <div class="mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-indigo-600"></div>
107 + <p class="mt-2 text-sm text-gray-500">Loading dashboards...</p>
108 + </div>
109 +
110 + <!-- Dashboards Table -->
111 + <div v-else-if="selectedCustomerCode">
112 + <div class="mb-4 flex items-center justify-between">
113 + <h2 class="text-lg font-medium text-gray-900">Enabled Dashboards</h2>
114 + <span class="text-sm text-gray-500">{{ dashboards.length }} dashboard(s)</span>
115 + </div>
116 +
117 + <div v-if="dashboards.length > 0" class="overflow-hidden rounded-lg bg-white shadow">
118 + <div class="overflow-x-auto">
119 + <table class="min-w-full divide-y divide-gray-200">
120 + <thead class="bg-gray-50">
121 + <tr>
122 + <th
123 + class="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase"
124 + >
125 + Dashboard
126 + </th>
127 + <th
128 + class="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase"
129 + >
130 + Category
131 + </th>
132 + <th
133 + class="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase"
134 + >
135 + Template
136 + </th>
137 + <th
138 + class="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase"
139 + >
140 + Created
141 + </th>
142 + <th
143 + class="px-6 py-3 text-right text-xs font-medium tracking-wider text-gray-500 uppercase"
144 + >
145 + Action
146 + </th>
147 + </tr>
148 + </thead>
149 + <tbody class="divide-y divide-gray-200 bg-white">
150 + <tr v-for="dash in dashboards" :key="dash.id" class="hover:bg-gray-50">
151 + <td class="px-6 py-4 text-sm font-medium text-gray-900">
152 + {{ dash.display_name }}
153 + </td>
154 + <td class="px-6 py-4 text-sm text-gray-500">
155 + {{ dash.library_card }}
156 + </td>
157 + <td class="px-6 py-4 text-sm text-gray-500">
158 + {{ dash.template_id }}
159 + </td>
160 + <td class="px-6 py-4 text-sm whitespace-nowrap text-gray-500">
161 + {{ formatDate(dash.created_at) }}
162 + </td>
163 + <td class="px-6 py-4 text-right text-sm whitespace-nowrap">
164 + <router-link
165 + :to="`/dashboards/view/${dash.id}`"
166 + class="font-medium text-indigo-600 hover:text-indigo-900"
167 + >
168 + View Dashboard
169 + </router-link>
170 + </td>
171 + </tr>
172 + </tbody>
173 + </table>
174 + </div>
175 + </div>
176 +
177 + <!-- Empty State -->
178 + <div v-else class="rounded-lg bg-white px-4 py-12 text-center shadow">
179 + <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
180 + <path
181 + stroke-linecap="round"
182 + stroke-linejoin="round"
183 + stroke-width="2"
184 + d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
185 + />
186 + </svg>
187 + <h3 class="mt-2 text-sm font-medium text-gray-900">No dashboards enabled</h3>
188 + <p class="mt-1 text-sm text-gray-500">
189 + Contact your administrator to enable dashboards for your account.
190 + </p>
191 + </div>
192 + </div>
193 + </div>
194 + </div>
195 +</template>
196 +
197 +<script setup lang="ts">
198 +import { ref, computed, onBeforeMount } from "vue"
199 +import { useRouter } from "vue-router"
200 +import { usePortalSettingsStore } from "@/stores/portalSettings"
201 +import { SiemAPI, type EnabledDashboard } from "@/api/siem"
202 +
203 +const router = useRouter()
204 +const portalSettingsStore = usePortalSettingsStore()
205 +
206 +const showLogo = ref(true)
207 +const error = ref("")
208 +const loading = ref(false)
209 +
210 +const username = computed(() => {
211 + try {
212 + const user = JSON.parse(localStorage.getItem("customer-portal-user") || "{}")
213 + return user.username || "User"
214 + } catch {
215 + return "User"
216 + }
217 +})
218 +const portalTitle = computed(() => portalSettingsStore.portalTitle || "Customer Portal")
219 +const portalLogo = computed(() => portalSettingsStore.portalLogo)
220 +
221 +function logout() {
222 + localStorage.removeItem("customer-portal-auth-token")
223 + localStorage.removeItem("customer-portal-user")
224 + router.push("/login")
225 +}
226 +
227 +// -- Customer selection --
228 +const customerCodes = ref<string[]>([])
229 +const selectedCustomerCode = ref("")
230 +const dashboards = ref<EnabledDashboard[]>([])
231 +
232 +async function loadCustomerCodes() {
233 + try {
234 + const response = await SiemAPI.getCustomerCodes()
235 + customerCodes.value = response.customer_codes.filter(c => c !== "*")
236 + // Auto-select if only one customer
237 + if (customerCodes.value.length === 1) {
238 + selectedCustomerCode.value = customerCodes.value[0]
239 + loadDashboards(selectedCustomerCode.value)
240 + }
241 + } catch (err: any) {
242 + error.value = err.response?.data?.detail || err.message || "Failed to load customer codes"
243 + }
244 +}
245 +
246 +function onCustomerChange() {
247 + dashboards.value = []
248 + error.value = ""
249 + if (selectedCustomerCode.value) {
250 + loadDashboards(selectedCustomerCode.value)
251 + }
252 +}
253 +
254 +async function loadDashboards(customerCode: string) {
255 + loading.value = true
256 + try {
257 + const response = await SiemAPI.getEnabledDashboards(customerCode)
258 + dashboards.value = response.enabled_dashboards
259 + } catch (err: any) {
260 + error.value = err.response?.data?.detail || err.message || "Failed to load dashboards"
261 + } finally {
262 + loading.value = false
263 + }
264 +}
265 +
266 +function formatDate(dateStr: string): string {
267 + try {
268 + return new Date(dateStr).toLocaleDateString()
269 + } catch {
270 + return dateStr
271 + }
272 +}
273 +
274 +onBeforeMount(() => {
275 + loadCustomerCodes()
276 +})
277 +</script>
customer_portal/src/views/EventSearchPage.vue
+76 -8
@@ -46,6 +46,12 @@
46 >
47 Event Search
48 </router-link>
49 + <router-link
50 + to="/dashboards"
51 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
52 + >
53 + Dashboards
54 + </router-link>
55 </nav>
56 </div>
57 <div class="flex items-center space-x-4">
@@ -124,6 +130,7 @@
130 <option value="7d">Last 7 days</option>
131 <option value="14d">Last 14 days</option>
132 <option value="30d">Last 30 days</option>
133 + <option value="custom">Custom range</option>
134 </select>
135 </div>
136
@@ -145,6 +152,28 @@
152 </div>
153 </div>
154
155 + <!-- Custom Date Range -->
156 + <div v-if="timerange === 'custom'" class="col-span-full grid grid-cols-1 gap-4 md:grid-cols-2">
157 + <div>
158 + <label for="time-from" class="block text-sm font-medium text-gray-700">From</label>
159 + <input
160 + id="time-from"
161 + v-model="customTimeFrom"
162 + type="datetime-local"
163 + class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
164 + />
165 + </div>
166 + <div>
167 + <label for="time-to" class="block text-sm font-medium text-gray-700">To</label>
168 + <input
169 + id="time-to"
170 + v-model="customTimeTo"
171 + type="datetime-local"
172 + class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
173 + />
174 + </div>
175 + </div>
176 +
177 <!-- Query Bar -->
178 <div class="relative">
179 <label for="query-input" class="block text-sm font-medium text-gray-700">Lucene Query</label>
@@ -465,11 +494,12 @@
494
495 <script setup lang="ts">
496 import { ref, computed, onBeforeMount } from "vue"
468 -import { useRouter } from "vue-router"
497 +import { useRouter, useRoute } from "vue-router"
498 import { usePortalSettingsStore } from "@/stores/portalSettings"
499 import { SiemAPI, type EventSource, type EventSearchResult, type FieldMapping } from "@/api/siem"
500
501 const router = useRouter()
502 +const route = useRoute()
503 const portalSettingsStore = usePortalSettingsStore()
504
505 const showLogo = ref(true)
@@ -550,6 +580,8 @@ function onSourceChange() {
580 const timerange = ref("24h")
581 const pageSize = ref(50)
582 const query = ref("")
583 +const customTimeFrom = ref("")
584 +const customTimeTo = ref("")
585
586 // -- Field mappings / autocomplete --
587 const fieldMappings = ref<FieldMapping[]>([])
@@ -623,11 +655,19 @@ async function searchEvents() {
655 resetResults()
656
657 try {
626 - const response = await SiemAPI.queryEvents(selectedCustomerCode.value, selectedSourceName.value, {
627 - timerange: timerange.value,
628 - page_size: pageSize.value,
629 - query: query.value || undefined
630 - })
658 + const params: { timerange?: string; page_size: number; query?: string; time_from?: string; time_to?: string } =
659 + {
660 + page_size: pageSize.value,
661 + query: query.value || undefined
662 + }
663 + if (timerange.value === "custom" && customTimeFrom.value && customTimeTo.value) {
664 + params.time_from = new Date(customTimeFrom.value).toISOString()
665 + params.time_to = new Date(customTimeTo.value).toISOString()
666 + } else {
667 + params.timerange = timerange.value === "custom" ? "24h" : timerange.value
668 + }
669 +
670 + const response = await SiemAPI.queryEvents(selectedCustomerCode.value, selectedSourceName.value, params)
671 events.value = response.events
672 totalEvents.value = response.total
673 scrollId.value = response.scroll_id
@@ -709,8 +749,36 @@ function levelClass(level: number | undefined): string {
749 }
750
751 // -- Lifecycle --
712 -onBeforeMount(() => {
713 - loadCustomerCodes()
752 +async function applyRouteParams() {
753 + const qCustomer = route.query.customer_code as string | undefined
754 + const qSource = route.query.source_name as string | undefined
755 + const qQuery = route.query.query as string | undefined
756 +
757 + if (!qCustomer) return
758 +
759 + selectedCustomerCode.value = qCustomer
760 + await loadEventSources(qCustomer)
761 +
762 + if (qSource) {
763 + const match = enabledSources.value.find(s => s.name === qSource)
764 + if (match) {
765 + selectedSourceName.value = match.name
766 + loadFieldMappings()
767 + }
768 + }
769 +
770 + if (qQuery) {
771 + query.value = qQuery
772 + }
773 +
774 + if (selectedCustomerCode.value && selectedSourceName.value) {
775 + searchEvents()
776 + }
777 +}
778 +
779 +onBeforeMount(async () => {
780 + await loadCustomerCodes()
781 + applyRouteParams()
782 })
783 </script>
784
customer_portal/src/views/OverviewPage.vue
+6
@@ -46,6 +46,12 @@
46 >
47 Event Search
48 </router-link>
49 + <router-link
50 + to="/dashboards"
51 + class="rounded-md px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-900"
52 + >
53 + Dashboards
54 + </router-link>
55 </nav>
56 </div>
57 <div class="flex items-center space-x-4">
frontend/eslint.config.mjs
+1
@@ -14,6 +14,7 @@ export default antfu(
14 rules: {
15 "e18e/ban-dependencies": ["error", { allowed: ["axios", "lodash", "depcheck", "fs-extra"] }],
16 "antfu/if-newline": "off",
17 + "@typescript-eslint/no-non-null-assertion": "error",
18 "style/operator-linebreak": "off",
19 "style/arrow-parens": "off",
20 "style/brace-style": "off",
frontend/package.json
+24 -24
@@ -36,18 +36,18 @@
36 },
37 "dependencies": {
38 "@ajoelp/json-to-formdata": "^1.5.0",
39 - "@codemirror/commands": "^6.10.2",
39 + "@codemirror/commands": "^6.10.3",
40 "@codemirror/lang-javascript": "^6.2.5",
41 "@codemirror/lang-xml": "^6.1.0",
42 "@codemirror/lint": "^6.9.5",
43 "@codemirror/theme-one-dark": "^6.1.3",
44 - "@codemirror/view": "^6.39.16",
44 + "@codemirror/view": "^6.40.0",
45 "@f3ve/vue-markdown-it": "^0.2.3",
46 "@fontsource/jetbrains-mono": "^5.2.8",
47 "@fontsource/lexend": "^5.2.11",
48 "@fontsource/public-sans": "^5.2.7",
49 "@microsoft/fetch-event-source": "^2.0.1",
50 - "@shikijs/markdown-it": "^4.0.1",
50 + "@shikijs/markdown-it": "^4.0.2",
51 "@singulio/app-auth-search": "^0.0.3",
52 "@types/codemirror": "^5.60.17",
53 "@vueuse/core": "^14.2.1",
@@ -56,30 +56,30 @@
56 "bytes": "^3.1.2",
57 "codemirror": "~6.0.2",
58 "colord": "^2.9.3",
59 - "dayjs": "^1.11.19",
59 + "dayjs": "^1.11.20",
60 "detect-touch-device": "^1.1.6",
61 "echarts": "^6.0.0",
62 - "fast-xml-parser": "^5.4.2",
62 + "fast-xml-parser": "^5.5.6",
63 "file-saver": "^2.0.5",
64 "html-entities": "^2.6.0",
65 - "jose": "^6.2.0",
65 + "jose": "^6.2.2",
66 "js-md5": "^0.8.3",
67 "lodash": "^4.17.23",
68 "mitt": "^3.0.1",
69 - "naive-ui": "^2.43.2",
70 - "nanoid": "^5.1.6",
69 + "naive-ui": "^2.44.1",
70 + "nanoid": "^5.1.7",
71 "password-validator": "^5.3.0",
72 "pinia": "^3.0.4",
73 "pinia-plugin-persistedstate": "^4.7.1",
74 "secure-ls": "^2.0.0",
75 - "shiki": "^4.0.1",
75 + "shiki": "^4.0.2",
76 "thememirror": "^2.0.1",
77 "validator": "^13.15.26",
78 - "vue": "^3.5.29",
78 + "vue": "^3.5.30",
79 "vue-advanced-cropper": "^2.8.9",
80 "vue-codemirror": "^6.1.1",
81 "vue-highlight-words": "^3.0.1",
82 - "vue-i18n": "^11.2.8",
82 + "vue-i18n": "^11.3.0",
83 "vue-router": "^5.0.3",
84 "vue-sjv": "^0.0.6",
85 "vue3-apexcharts": "^1.11.1",
@@ -94,10 +94,10 @@
94 "vueuc": "^0.4.64"
95 },
96 "devDependencies": {
97 - "@antfu/eslint-config": "~7.7.0",
97 + "@antfu/eslint-config": "~7.7.3",
98 "@clack/prompts": "^1.1.0",
99 "@iconify/vue": "^5.0.0",
100 - "@tailwindcss/vite": "^4.2.1",
100 + "@tailwindcss/vite": "^4.2.2",
101 "@tsconfig/node20": "^20.1.9",
102 "@types/bytes": "^3.1.5",
103 "@types/file-saver": "^2.0.7",
@@ -105,35 +105,35 @@
105 "@types/jsdom": "^28.0.0",
106 "@types/lodash": "^4.17.24",
107 "@types/markdown-it": "^14.1.2",
108 - "@types/node": "^25.3.5",
108 + "@types/node": "^25.5.0",
109 "@types/validator": "^13.15.10",
110 - "@vitejs/plugin-vue": "^6.0.4",
111 - "@vitejs/plugin-vue-jsx": "^5.1.4",
110 + "@vitejs/plugin-vue": "^6.0.5",
111 + "@vitejs/plugin-vue-jsx": "^5.1.5",
112 "@vue/test-utils": "^2.4.6",
113 "@vue/tsconfig": "~0.9.0",
114 - "baseline-browser-mapping": "^2.10.0",
114 + "baseline-browser-mapping": "^2.10.8",
115 "depcheck": "^1.4.7",
116 - "eslint": "~10.0.2",
116 + "eslint": "~10.0.3",
117 "flourite": "^1.3.0",
118 "fs-extra": "^11.3.4",
119 "jscpd": "^4.0.8",
120 - "jsdom": "^28.1.0",
120 + "jsdom": "^29.0.0",
121 "npm-run-all2": "^8.0.4",
122 "prettier": "^3.8.1",
123 "prettier-plugin-tailwindcss": "^0.7.2",
124 - "sass": "^1.97.3",
124 + "sass": "^1.98.0",
125 "start-server-and-test": "^2.1.5",
126 - "tailwindcss": "^4.2.1",
126 + "tailwindcss": "^4.2.2",
127 "taze": "^19.10.0",
128 "type-fest": "^5.4.4",
129 "typescript": "~5.9.3",
130 "vite": "^7.3.1",
131 "vite-bundle-visualizer": "^1.2.1",
132 "vite-plugin-inspect": "^11.3.3",
133 - "vite-plugin-vue-devtools": "^8.0.7",
133 + "vite-plugin-vue-devtools": "^8.1.0",
134 "vite-svg-loader": "^5.1.1",
135 - "vitest": "^4.0.18",
136 - "vue-tsc": "^3.2.5"
135 + "vitest": "^4.1.0",
136 + "vue-tsc": "^3.2.6"
137 },
138 "pnpm": {
139 "peerDependencyRules": {
frontend/pnpm-lock.yaml
+976 -805
@@ -12,8 +12,8 @@ importers:
12 specifier: ^1.5.0
13 version: 1.5.0
14 '@codemirror/commands':
15 - specifier: ^6.10.2
16 - version: 6.10.2
15 + specifier: ^6.10.3
16 + version: 6.10.3
17 '@codemirror/lang-javascript':
18 specifier: ^6.2.5
19 version: 6.2.5
@@ -27,11 +27,11 @@ importers:
27 specifier: ^6.1.3
28 version: 6.1.3
29 '@codemirror/view':
30 - specifier: ^6.39.16
31 - version: 6.39.16
30 + specifier: ^6.40.0
31 + version: 6.40.0
32 '@f3ve/vue-markdown-it':
33 specifier: ^0.2.3
34 - version: 0.2.3(vue@3.5.29(typescript@5.9.3))
34 + version: 0.2.3(vue@3.5.30(typescript@5.9.3))
35 '@fontsource/jetbrains-mono':
36 specifier: ^5.2.8
37 version: 5.2.8
@@ -45,8 +45,8 @@ importers:
45 specifier: ^2.0.1
46 version: 2.0.1
47 '@shikijs/markdown-it':
48 - specifier: ^4.0.1
49 - version: 4.0.1
48 + specifier: ^4.0.2
49 + version: 4.0.2
50 '@singulio/app-auth-search':
51 specifier: ^0.0.3
52 version: 0.0.3
@@ -55,10 +55,10 @@ importers:
55 version: 5.60.17
56 '@vueuse/core':
57 specifier: ^14.2.1
58 - version: 14.2.1(vue@3.5.29(typescript@5.9.3))
58 + version: 14.2.1(vue@3.5.30(typescript@5.9.3))
59 '@vueuse/motion':
60 specifier: ^3.0.3
61 - version: 3.0.3(vue@3.5.29(typescript@5.9.3))
61 + version: 3.0.3(vue@3.5.30(typescript@5.9.3))
62 axios:
63 specifier: ^1.13.6
64 version: 1.13.6(debug@4.4.3)
@@ -72,8 +72,8 @@ importers:
72 specifier: ^2.9.3
73 version: 2.9.3
74 dayjs:
75 - specifier: ^1.11.19
76 - version: 1.11.19
75 + specifier: ^1.11.20
76 + version: 1.11.20
77 detect-touch-device:
78 specifier: ^1.1.6
79 version: 1.1.6
@@ -81,8 +81,8 @@ importers:
81 specifier: ^6.0.0
82 version: 6.0.0
83 fast-xml-parser:
84 - specifier: ^5.4.2
85 - version: 5.4.2
84 + specifier: ^5.5.6
85 + version: 5.5.6
86 file-saver:
87 specifier: ^2.0.5
88 version: 2.0.5
@@ -90,8 +90,8 @@ importers:
90 specifier: ^2.6.0
91 version: 2.6.0
92 jose:
93 - specifier: ^6.2.0
94 - version: 6.2.0
93 + specifier: ^6.2.2
94 + version: 6.2.2
95 js-md5:
96 specifier: ^0.8.3
97 version: 0.8.3
@@ -102,81 +102,81 @@ importers:
102 specifier: ^3.0.1
103 version: 3.0.1
104 naive-ui:
105 - specifier: ^2.43.2
106 - version: 2.43.2(vue@3.5.29(typescript@5.9.3))
105 + specifier: ^2.44.1
106 + version: 2.44.1(vue@3.5.30(typescript@5.9.3))
107 nanoid:
108 - specifier: ^5.1.6
109 - version: 5.1.6
108 + specifier: ^5.1.7
109 + version: 5.1.7
110 password-validator:
111 specifier: ^5.3.0
112 version: 5.3.0
113 pinia:
114 specifier: ^3.0.4
115 - version: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
115 + version: 3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))
116 pinia-plugin-persistedstate:
117 specifier: ^4.7.1
118 - version: 4.7.1(@nuxt/kit@3.21.1)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))
118 + version: 4.7.1(@nuxt/kit@3.21.1)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)))
119 secure-ls:
120 specifier: ^2.0.0
121 version: 2.0.0
122 shiki:
123 - specifier: ^4.0.1
124 - version: 4.0.1
123 + specifier: ^4.0.2
124 + version: 4.0.2
125 thememirror:
126 specifier: ^2.0.1
127 - version: 2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)
127 + version: 2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)
128 validator:
129 specifier: ^13.15.26
130 version: 13.15.26
131 vue:
132 - specifier: ^3.5.29
133 - version: 3.5.29(typescript@5.9.3)
132 + specifier: ^3.5.30
133 + version: 3.5.30(typescript@5.9.3)
134 vue-advanced-cropper:
135 specifier: ^2.8.9
136 - version: 2.8.9(vue@3.5.29(typescript@5.9.3))
136 + version: 2.8.9(vue@3.5.30(typescript@5.9.3))
137 vue-codemirror:
138 specifier: ^6.1.1
139 - version: 6.1.1(codemirror@6.0.2)(vue@3.5.29(typescript@5.9.3))
139 + version: 6.1.1(codemirror@6.0.2)(vue@3.5.30(typescript@5.9.3))
140 vue-highlight-words:
141 specifier: ^3.0.1
142 - version: 3.0.1(vue@3.5.29(typescript@5.9.3))
142 + version: 3.0.1(vue@3.5.30(typescript@5.9.3))
143 vue-i18n:
144 - specifier: ^11.2.8
145 - version: 11.2.8(vue@3.5.29(typescript@5.9.3))
144 + specifier: ^11.3.0
145 + version: 11.3.0(vue@3.5.30(typescript@5.9.3))
146 vue-router:
147 specifier: ^5.0.3
148 - version: 5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
148 + version: 5.0.3(@vue/compiler-sfc@3.5.30)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))
149 vue-sjv:
150 specifier: ^0.0.6
151 - version: 0.0.6(vue@3.5.29(typescript@5.9.3))
151 + version: 0.0.6(vue@3.5.30(typescript@5.9.3))
152 vue3-apexcharts:
153 specifier: ^1.11.1
154 - version: 1.11.1(apexcharts@5.5.1)(vue@3.5.29(typescript@5.9.3))
154 + version: 1.11.1(apexcharts@5.5.1)(vue@3.5.30(typescript@5.9.3))
155 vue3-marquee:
156 specifier: ^4.2.2
157 - version: 4.2.2(vue@3.5.29(typescript@5.9.3))
157 + version: 4.2.2(vue@3.5.30(typescript@5.9.3))
158 vuedraggable:
159 specifier: ^4.1.0
160 - version: 4.1.0(vue@3.5.29(typescript@5.9.3))
160 + version: 4.1.0(vue@3.5.30(typescript@5.9.3))
161 xmllint:
162 specifier: ^0.1.1
163 version: 0.1.1
164 xmllint-wasm:
165 specifier: ^5.1.0
166 - version: 5.1.0(@types/node@25.3.5)
166 + version: 5.1.0(@types/node@25.5.0)
167 devDependencies:
168 '@antfu/eslint-config':
169 - specifier: ~7.7.0
170 - version: 7.7.0(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3))(@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.29)(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
169 + specifier: ~7.7.3
170 + version: 7.7.3(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)))
171 '@clack/prompts':
172 specifier: ^1.1.0
173 version: 1.1.0
174 '@iconify/vue':
175 specifier: ^5.0.0
176 - version: 5.0.0(vue@3.5.29(typescript@5.9.3))
176 + version: 5.0.0(vue@3.5.30(typescript@5.9.3))
177 '@tailwindcss/vite':
178 - specifier: ^4.2.1
179 - version: 4.2.1(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
178 + specifier: ^4.2.2
179 + version: 4.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
180 '@tsconfig/node20':
181 specifier: ^20.1.9
182 version: 20.1.9
@@ -199,32 +199,32 @@ importers:
199 specifier: ^14.1.2
200 version: 14.1.2
201 '@types/node':
202 - specifier: ^25.3.5
203 - version: 25.3.5
202 + specifier: ^25.5.0
203 + version: 25.5.0
204 '@types/validator':
205 specifier: ^13.15.10
206 version: 13.15.10
207 '@vitejs/plugin-vue':
208 - specifier: ^6.0.4
209 - version: 6.0.4(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
208 + specifier: ^6.0.5
209 + version: 6.0.5(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))
210 '@vitejs/plugin-vue-jsx':
211 - specifier: ^5.1.4
212 - version: 5.1.4(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
211 + specifier: ^5.1.5
212 + version: 5.1.5(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))
213 '@vue/test-utils':
214 specifier: ^2.4.6
215 version: 2.4.6
216 '@vue/tsconfig':
217 specifier: ~0.9.0
218 - version: 0.9.0(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
218 + version: 0.9.0(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))
219 baseline-browser-mapping:
220 - specifier: ^2.10.0
221 - version: 2.10.0
220 + specifier: ^2.10.8
221 + version: 2.10.8
222 depcheck:
223 specifier: ^1.4.7
224 version: 1.4.7
225 eslint:
226 - specifier: ~10.0.2
227 - version: 10.0.2(jiti@2.6.1)
226 + specifier: ~10.0.3
227 + version: 10.0.3(jiti@2.6.1)
228 flourite:
229 specifier: ^1.3.0
230 version: 1.3.0
@@ -235,8 +235,8 @@ importers:
235 specifier: ^4.0.8
236 version: 4.0.8
237 jsdom:
238 - specifier: ^28.1.0
239 - version: 28.1.0
238 + specifier: ^29.0.0
239 + version: 29.0.0
240 npm-run-all2:
241 specifier: ^8.0.4
242 version: 8.0.4
@@ -247,14 +247,14 @@ importers:
247 specifier: ^0.7.2
248 version: 0.7.2(prettier@3.8.1)
249 sass:
250 - specifier: ^1.97.3
251 - version: 1.97.3
250 + specifier: ^1.98.0
251 + version: 1.98.0
252 start-server-and-test:
253 specifier: ^2.1.5
254 version: 2.1.5
255 tailwindcss:
256 - specifier: ^4.2.1
257 - version: 4.2.1
256 + specifier: ^4.2.2
257 + version: 4.2.2
258 taze:
259 specifier: ^19.10.0
260 version: 19.10.0
@@ -266,25 +266,25 @@ importers:
266 version: 5.9.3
267 vite:
268 specifier: ^7.3.1
269 - version: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
269 + version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
270 vite-bundle-visualizer:
271 specifier: ^1.2.1
272 version: 1.2.1(rollup@4.57.1)
273 vite-plugin-inspect:
274 specifier: ^11.3.3
275 - version: 11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
275 + version: 11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
276 vite-plugin-vue-devtools:
277 - specifier: ^8.0.7
278 - version: 8.0.7(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
277 + specifier: ^8.1.0
278 + version: 8.1.0(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))
279 vite-svg-loader:
280 specifier: ^5.1.1
281 - version: 5.1.1(vue@3.5.29(typescript@5.9.3))
281 + version: 5.1.1(vue@3.5.30(typescript@5.9.3))
282 vitest:
283 - specifier: ^4.0.18
284 - version: 4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
283 + specifier: ^4.1.0
284 + version: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
285 vue-tsc:
286 - specifier: ^3.2.5
287 - version: 3.2.5(typescript@5.9.3)
286 + specifier: ^3.2.6
287 + version: 3.2.6(typescript@5.9.3)
288 optionalDependencies:
289 '@rollup/rollup-linux-x64-gnu':
290 specifier: ^4.46.2
@@ -294,13 +294,10 @@ importers:
294 version: 0.3.11
295 vueuc:
296 specifier: ^0.4.64
297 - version: 0.4.65(vue@3.5.29(typescript@5.9.3))
297 + version: 0.4.65(vue@3.5.30(typescript@5.9.3))
298
299 packages:
300
301 - '@acemir/cssom@0.9.31':
302 - resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==}
303 -
301 '@ajoelp/json-to-formdata@1.5.0':
302 resolution: {integrity: sha512-nrlfeTSL0X0dtx5r2KpzPiqLSIQquiiJjUKsQAKzWaCmO2QoYZCyb5ENZwF3YoffKronOCJr25mxaD8JRJmK8w==}
303
@@ -360,8 +357,8 @@ packages:
357 resolution: {integrity: sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==}
358 engines: {node: '>= 14.0.0'}
359
363 - '@antfu/eslint-config@7.7.0':
364 - resolution: {integrity: sha512-lkxb84o8z4v1+me51XlrHHF6zvOZfvTu6Y11t6h6v17JSMl9yoNHwC0Sqp/NfMTHie/LGgjyXOupXpQCXxfs1Q==}
360 + '@antfu/eslint-config@7.7.3':
361 + resolution: {integrity: sha512-BtroDxTvmWtvr3yJkdWVCvwsKlnEdkreoeOyrdNezc/W5qaiQNf2xjcsQ3N5Yy0x27h+0WFfW8rG8YlVioG6dw==}
362 hasBin: true
363 peerDependencies:
364 '@angular-eslint/eslint-plugin': ^21.1.0
@@ -432,11 +429,13 @@ packages:
429 engines: {node: '>=20'}
430 hasBin: true
431
435 - '@asamuzakjp/css-color@4.1.2':
436 - resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==}
432 + '@asamuzakjp/css-color@5.0.1':
433 + resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==}
434 + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
435
438 - '@asamuzakjp/dom-selector@6.8.1':
439 - resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==}
436 + '@asamuzakjp/dom-selector@7.0.3':
437 + resolution: {integrity: sha512-Q6mU0Z6bfj6YvnX2k9n0JxiIwrCFN59x/nWmYQnAqP000ruX/yV+5bp/GRcF5T8ncvfwJQ7fgfP74DlpKExILA==}
438 + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
439
440 '@asamuzakjp/nwsapi@2.3.9':
441 resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
@@ -594,8 +593,8 @@ packages:
593 '@codemirror/autocomplete@6.20.0':
594 resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
595
597 - '@codemirror/commands@6.10.2':
598 - resolution: {integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==}
596 + '@codemirror/commands@6.10.3':
597 + resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==}
598
599 '@codemirror/lang-javascript@6.2.5':
600 resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==}
@@ -615,11 +614,14 @@ packages:
614 '@codemirror/state@6.5.4':
615 resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==}
616
617 + '@codemirror/state@6.6.0':
618 + resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==}
619 +
620 '@codemirror/theme-one-dark@6.1.3':
621 resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
622
621 - '@codemirror/view@6.39.16':
622 - resolution: {integrity: sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==}
623 + '@codemirror/view@6.40.0':
624 + resolution: {integrity: sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==}
625
626 '@colors/colors@1.5.0':
627 resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
@@ -635,8 +637,8 @@ packages:
637 peerDependencies:
638 vue: ^3.0.11
639
638 - '@csstools/color-helpers@6.0.1':
639 - resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==}
640 + '@csstools/color-helpers@6.0.2':
641 + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==}
642 engines: {node: '>=20.19.0'}
643
644 '@csstools/css-calc@3.1.1':
@@ -646,8 +648,8 @@ packages:
648 '@csstools/css-parser-algorithms': ^4.0.0
649 '@csstools/css-tokenizer': ^4.0.0
650
649 - '@csstools/css-color-parser@4.0.1':
650 - resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==}
651 + '@csstools/css-color-parser@4.0.2':
652 + resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==}
653 engines: {node: '>=20.19.0'}
654 peerDependencies:
655 '@csstools/css-parser-algorithms': ^4.0.0
@@ -659,8 +661,13 @@ packages:
661 peerDependencies:
662 '@csstools/css-tokenizer': ^4.0.0
663
662 - '@csstools/css-syntax-patches-for-csstree@1.0.27':
663 - resolution: {integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==}
664 + '@csstools/css-syntax-patches-for-csstree@1.1.1':
665 + resolution: {integrity: sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==}
666 + peerDependencies:
667 + css-tree: ^3.2.1
668 + peerDependenciesMeta:
669 + css-tree:
670 + optional: true
671
672 '@csstools/css-tokenizer@4.0.0':
673 resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
@@ -869,14 +876,18 @@ packages:
876 eslint:
877 optional: true
878
872 - '@eslint/config-array@0.23.2':
873 - resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==}
879 + '@eslint/config-array@0.23.3':
880 + resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==}
881 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
882
883 '@eslint/config-helpers@0.5.2':
884 resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==}
885 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
886
887 + '@eslint/config-helpers@0.5.3':
888 + resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==}
889 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
890 +
891 '@eslint/core@0.17.0':
892 resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
893 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -885,12 +896,16 @@ packages:
896 resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
897 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
898
899 + '@eslint/core@1.1.1':
900 + resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==}
901 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
902 +
903 '@eslint/markdown@7.5.1':
904 resolution: {integrity: sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ==}
905 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
906
892 - '@eslint/object-schema@3.0.2':
893 - resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==}
907 + '@eslint/object-schema@3.0.3':
908 + resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==}
909 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
910
911 '@eslint/plugin-kit@0.4.1':
@@ -901,8 +916,12 @@ packages:
916 resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==}
917 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
918
904 - '@exodus/bytes@1.14.1':
905 - resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==}
919 + '@eslint/plugin-kit@0.6.1':
920 + resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==}
921 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
922 +
923 + '@exodus/bytes@1.15.0':
924 + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==}
925 engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
926 peerDependencies:
927 '@noble/hashes': ^1.8.0 || ^2.0.0
@@ -971,16 +990,20 @@ packages:
990 peerDependencies:
991 vue: '>=3'
992
974 - '@intlify/core-base@11.2.8':
975 - resolution: {integrity: sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==}
993 + '@intlify/core-base@11.3.0':
994 + resolution: {integrity: sha512-NNX5jIwF4TJBe7RtSKDMOA6JD9mp2mRcBHAwt2X+Q8PvnZub0yj5YYXlFu2AcESdgQpEv/5Yx2uOCV/yh7YkZg==}
995 + engines: {node: '>= 16'}
996 +
997 + '@intlify/devtools-types@11.3.0':
998 + resolution: {integrity: sha512-G9CNL4WpANWVdUjubOIIS7/D2j/0j+1KJmhBJxHilWNKr9mmt3IjFV3Hq4JoBP23uOoC5ynxz/FHZ42M+YxfGw==}
999 engines: {node: '>= 16'}
1000
978 - '@intlify/message-compiler@11.2.8':
979 - resolution: {integrity: sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==}
1001 + '@intlify/message-compiler@11.3.0':
1002 + resolution: {integrity: sha512-RAJp3TMsqohg/Wa7bVF3cChRhecSYBLrTCQSj7j0UtWVFLP+6iEJoE2zb7GU5fp+fmG5kCbUdzhmlAUCWXiUJw==}
1003 engines: {node: '>= 16'}
1004
982 - '@intlify/shared@11.2.8':
983 - resolution: {integrity: sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==}
1005 + '@intlify/shared@11.3.0':
1006 + resolution: {integrity: sha512-LC6P/uay7rXL5zZ5+5iRJfLs/iUN8apu9tm8YqQVmW3Uq3X4A0dOFUIDuAmB7gAC29wTHOS3EiN/IosNSz0eNQ==}
1007 engines: {node: '>= 16'}
1008
1009 '@isaacs/cliui@8.0.2':
@@ -1315,24 +1338,24 @@ packages:
1338 cpu: [x64]
1339 os: [win32]
1340
1318 - '@shikijs/core@4.0.1':
1319 - resolution: {integrity: sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg==}
1341 + '@shikijs/core@4.0.2':
1342 + resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==}
1343 engines: {node: '>=20'}
1344
1322 - '@shikijs/engine-javascript@4.0.1':
1323 - resolution: {integrity: sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg==}
1345 + '@shikijs/engine-javascript@4.0.2':
1346 + resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==}
1347 engines: {node: '>=20'}
1348
1326 - '@shikijs/engine-oniguruma@4.0.1':
1327 - resolution: {integrity: sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg==}
1349 + '@shikijs/engine-oniguruma@4.0.2':
1350 + resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==}
1351 engines: {node: '>=20'}
1352
1330 - '@shikijs/langs@4.0.1':
1331 - resolution: {integrity: sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w==}
1353 + '@shikijs/langs@4.0.2':
1354 + resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==}
1355 engines: {node: '>=20'}
1356
1334 - '@shikijs/markdown-it@4.0.1':
1335 - resolution: {integrity: sha512-JxF2AfpQ0Mr9+t4JkPqLocS9+FSTO49mqVR3lqgFQH1fJQ0+mq5Gq7eM71kp8cFm2oOzEf24rrZfMIDjBmVAYw==}
1357 + '@shikijs/markdown-it@4.0.2':
1358 + resolution: {integrity: sha512-7DDEhknj/mXTN7ME8CjKWBv5O/4YgOiJBZLgs/NbUFMC7Ik1x/VEhaK+aBjX60bJdok0E2mxEYan/GzJ2xRx+A==}
1359 engines: {node: '>=20'}
1360 peerDependencies:
1361 markdown-it-async: ^2.2.0
@@ -1340,16 +1363,16 @@ packages:
1363 markdown-it-async:
1364 optional: true
1365
1343 - '@shikijs/primitive@4.0.1':
1344 - resolution: {integrity: sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA==}
1366 + '@shikijs/primitive@4.0.2':
1367 + resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==}
1368 engines: {node: '>=20'}
1369
1347 - '@shikijs/themes@4.0.1':
1348 - resolution: {integrity: sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==}
1370 + '@shikijs/themes@4.0.2':
1371 + resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==}
1372 engines: {node: '>=20'}
1373
1351 - '@shikijs/types@4.0.1':
1352 - resolution: {integrity: sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw==}
1374 + '@shikijs/types@4.0.2':
1375 + resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==}
1376 engines: {node: '>=20'}
1377
1378 '@shikijs/vscode-textmate@10.0.2':
@@ -1371,69 +1394,69 @@ packages:
1394 peerDependencies:
1395 eslint: ^9.0.0 || ^10.0.0
1396
1374 - '@tailwindcss/node@4.2.1':
1375 - resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==}
1397 + '@tailwindcss/node@4.2.2':
1398 + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
1399
1377 - '@tailwindcss/oxide-android-arm64@4.2.1':
1378 - resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==}
1400 + '@tailwindcss/oxide-android-arm64@4.2.2':
1401 + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==}
1402 engines: {node: '>= 20'}
1403 cpu: [arm64]
1404 os: [android]
1405
1383 - '@tailwindcss/oxide-darwin-arm64@4.2.1':
1384 - resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==}
1406 + '@tailwindcss/oxide-darwin-arm64@4.2.2':
1407 + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==}
1408 engines: {node: '>= 20'}
1409 cpu: [arm64]
1410 os: [darwin]
1411
1389 - '@tailwindcss/oxide-darwin-x64@4.2.1':
1390 - resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==}
1412 + '@tailwindcss/oxide-darwin-x64@4.2.2':
1413 + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==}
1414 engines: {node: '>= 20'}
1415 cpu: [x64]
1416 os: [darwin]
1417
1395 - '@tailwindcss/oxide-freebsd-x64@4.2.1':
1396 - resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==}
1418 + '@tailwindcss/oxide-freebsd-x64@4.2.2':
1419 + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==}
1420 engines: {node: '>= 20'}
1421 cpu: [x64]
1422 os: [freebsd]
1423
1401 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1':
1402 - resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==}
1424 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
1425 + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==}
1426 engines: {node: '>= 20'}
1427 cpu: [arm]
1428 os: [linux]
1429
1407 - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1':
1408 - resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==}
1430 + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
1431 + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==}
1432 engines: {node: '>= 20'}
1433 cpu: [arm64]
1434 os: [linux]
1435 libc: [glibc]
1436
1414 - '@tailwindcss/oxide-linux-arm64-musl@4.2.1':
1415 - resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==}
1437 + '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
1438 + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
1439 engines: {node: '>= 20'}
1440 cpu: [arm64]
1441 os: [linux]
1442 libc: [musl]
1443
1421 - '@tailwindcss/oxide-linux-x64-gnu@4.2.1':
1422 - resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==}
1444 + '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
1445 + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
1446 engines: {node: '>= 20'}
1447 cpu: [x64]
1448 os: [linux]
1449 libc: [glibc]
1450
1428 - '@tailwindcss/oxide-linux-x64-musl@4.2.1':
1429 - resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==}
1451 + '@tailwindcss/oxide-linux-x64-musl@4.2.2':
1452 + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
1453 engines: {node: '>= 20'}
1454 cpu: [x64]
1455 os: [linux]
1456 libc: [musl]
1457
1435 - '@tailwindcss/oxide-wasm32-wasi@4.2.1':
1436 - resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==}
1458 + '@tailwindcss/oxide-wasm32-wasi@4.2.2':
1459 + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
1460 engines: {node: '>=14.0.0'}
1461 cpu: [wasm32]
1462 bundledDependencies:
@@ -1444,26 +1467,26 @@ packages:
1467 - '@emnapi/wasi-threads'
1468 - tslib
1469
1447 - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1':
1448 - resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==}
1470 + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
1471 + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==}
1472 engines: {node: '>= 20'}
1473 cpu: [arm64]
1474 os: [win32]
1475
1453 - '@tailwindcss/oxide-win32-x64-msvc@4.2.1':
1454 - resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==}
1476 + '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
1477 + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==}
1478 engines: {node: '>= 20'}
1479 cpu: [x64]
1480 os: [win32]
1481
1459 - '@tailwindcss/oxide@4.2.1':
1460 - resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==}
1482 + '@tailwindcss/oxide@4.2.2':
1483 + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
1484 engines: {node: '>= 20'}
1485
1463 - '@tailwindcss/vite@4.2.1':
1464 - resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==}
1486 + '@tailwindcss/vite@4.2.2':
1487 + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==}
1488 peerDependencies:
1466 - vite: ^5.2.0 || ^6 || ^7
1489 + vite: ^5.2.0 || ^6 || ^7 || ^8
1490
1491 '@tsconfig/node20@20.1.9':
1492 resolution: {integrity: sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==}
@@ -1507,9 +1530,6 @@ packages:
1530 '@types/jsonfile@6.1.4':
1531 resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
1532
1510 - '@types/katex@0.16.8':
1511 - resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==}
1512 -
1533 '@types/linkify-it@5.0.0':
1534 resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
1535
@@ -1534,8 +1554,8 @@ packages:
1554 '@types/ms@2.1.0':
1555 resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
1556
1537 - '@types/node@25.3.5':
1538 - resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==}
1557 + '@types/node@25.5.0':
1558 + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
1559
1560 '@types/parse-json@4.0.2':
1561 resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -1558,11 +1578,11 @@ packages:
1578 '@types/web-bluetooth@0.0.21':
1579 resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
1580
1561 - '@typescript-eslint/eslint-plugin@8.56.1':
1562 - resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==}
1581 + '@typescript-eslint/eslint-plugin@8.57.1':
1582 + resolution: {integrity: sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==}
1583 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1584 peerDependencies:
1565 - '@typescript-eslint/parser': ^8.56.1
1585 + '@typescript-eslint/parser': ^8.57.1
1586 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1587 typescript: '>=4.8.4 <6.0.0'
1588
@@ -1573,12 +1593,25 @@ packages:
1593 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1594 typescript: '>=4.8.4 <6.0.0'
1595
1596 + '@typescript-eslint/parser@8.57.1':
1597 + resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==}
1598 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1599 + peerDependencies:
1600 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1601 + typescript: '>=4.8.4 <6.0.0'
1602 +
1603 '@typescript-eslint/project-service@8.56.1':
1604 resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==}
1605 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1606 peerDependencies:
1607 typescript: '>=4.8.4 <6.0.0'
1608
1609 + '@typescript-eslint/project-service@8.57.1':
1610 + resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==}
1611 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1612 + peerDependencies:
1613 + typescript: '>=4.8.4 <6.0.0'
1614 +
1615 '@typescript-eslint/rule-tester@8.56.1':
1616 resolution: {integrity: sha512-EWuV5Vq1EFYJEOVcILyWPO35PjnT0c6tv99PCpD12PgfZae5/Jo+F17hGjsEs2Moe+Dy1J7KIr8y037cK8+/rQ==}
1617 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1589,14 +1622,24 @@ packages:
1622 resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==}
1623 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1624
1625 + '@typescript-eslint/scope-manager@8.57.1':
1626 + resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==}
1627 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1628 +
1629 '@typescript-eslint/tsconfig-utils@8.56.1':
1630 resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==}
1631 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1632 peerDependencies:
1633 typescript: '>=4.8.4 <6.0.0'
1634
1598 - '@typescript-eslint/type-utils@8.56.1':
1599 - resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==}
1635 + '@typescript-eslint/tsconfig-utils@8.57.1':
1636 + resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==}
1637 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1638 + peerDependencies:
1639 + typescript: '>=4.8.4 <6.0.0'
1640 +
1641 + '@typescript-eslint/type-utils@8.57.1':
1642 + resolution: {integrity: sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==}
1643 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1644 peerDependencies:
1645 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -1606,12 +1649,22 @@ packages:
1649 resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==}
1650 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1651
1652 + '@typescript-eslint/types@8.57.1':
1653 + resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==}
1654 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1655 +
1656 '@typescript-eslint/typescript-estree@8.56.1':
1657 resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==}
1658 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1659 peerDependencies:
1660 typescript: '>=4.8.4 <6.0.0'
1661
1662 + '@typescript-eslint/typescript-estree@8.57.1':
1663 + resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==}
1664 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1665 + peerDependencies:
1666 + typescript: '>=4.8.4 <6.0.0'
1667 +
1668 '@typescript-eslint/utils@8.56.1':
1669 resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==}
1670 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1619,29 +1672,40 @@ packages:
1672 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1673 typescript: '>=4.8.4 <6.0.0'
1674
1675 + '@typescript-eslint/utils@8.57.1':
1676 + resolution: {integrity: sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==}
1677 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1678 + peerDependencies:
1679 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1680 + typescript: '>=4.8.4 <6.0.0'
1681 +
1682 '@typescript-eslint/visitor-keys@8.56.1':
1683 resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
1684 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1685
1686 + '@typescript-eslint/visitor-keys@8.57.1':
1687 + resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==}
1688 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1689 +
1690 '@ungap/structured-clone@1.3.0':
1691 resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
1692
1629 - '@vitejs/plugin-vue-jsx@5.1.4':
1630 - resolution: {integrity: sha512-70LmoVk9riR7qc4W2CpjsbNMWTPnuZb9dpFKX1emru0yP57nsc9k8nhLA6U93ngQapv5VDIUq2JatNfLbBIkrA==}
1693 + '@vitejs/plugin-vue-jsx@5.1.5':
1694 + resolution: {integrity: sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA==}
1695 engines: {node: ^20.19.0 || >=22.12.0}
1696 peerDependencies:
1633 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
1697 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
1698 vue: ^3.0.0
1699
1636 - '@vitejs/plugin-vue@6.0.4':
1637 - resolution: {integrity: sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==}
1700 + '@vitejs/plugin-vue@6.0.5':
1701 + resolution: {integrity: sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==}
1702 engines: {node: ^20.19.0 || >=22.12.0}
1703 peerDependencies:
1640 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
1704 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
1705 vue: ^3.2.25
1706
1643 - '@vitest/eslint-plugin@1.6.9':
1644 - resolution: {integrity: sha512-9WfPx1OwJ19QLCSRLkqVO7//1WcWnK3fE/3fJhKMAmDe8+9G4rB47xCNIIeCq3FdEzkIoLTfDlwDlPBaUTMhow==}
1707 + '@vitest/eslint-plugin@1.6.12':
1708 + resolution: {integrity: sha512-4kI47BJNFE+EQ5bmPbHzBF+ibNzx2Fj0Jo9xhWsTPxMddlHwIWl6YAxagefh461hrwx/W0QwBZpxGS404kBXyg==}
1709 engines: {node: '>=18'}
1710 peerDependencies:
1711 eslint: '>=8.57.0'
@@ -1653,34 +1717,34 @@ packages:
1717 vitest:
1718 optional: true
1719
1656 - '@vitest/expect@4.0.18':
1657 - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
1720 + '@vitest/expect@4.1.0':
1721 + resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==}
1722
1659 - '@vitest/mocker@4.0.18':
1660 - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==}
1723 + '@vitest/mocker@4.1.0':
1724 + resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==}
1725 peerDependencies:
1726 msw: ^2.4.9
1663 - vite: ^6.0.0 || ^7.0.0-0
1727 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0
1728 peerDependenciesMeta:
1729 msw:
1730 optional: true
1731 vite:
1732 optional: true
1733
1670 - '@vitest/pretty-format@4.0.18':
1671 - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
1734 + '@vitest/pretty-format@4.1.0':
1735 + resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==}
1736
1673 - '@vitest/runner@4.0.18':
1674 - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
1737 + '@vitest/runner@4.1.0':
1738 + resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==}
1739
1676 - '@vitest/snapshot@4.0.18':
1677 - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==}
1740 + '@vitest/snapshot@4.1.0':
1741 + resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==}
1742
1679 - '@vitest/spy@4.0.18':
1680 - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==}
1743 + '@vitest/spy@4.1.0':
1744 + resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==}
1745
1682 - '@vitest/utils@4.0.18':
1683 - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
1746 + '@vitest/utils@4.1.0':
1747 + resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==}
1748
1749 '@volar/language-core@2.4.28':
1750 resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==}
@@ -1738,24 +1802,36 @@ packages:
1802 '@vue/compiler-core@3.5.29':
1803 resolution: {integrity: sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==}
1804
1805 + '@vue/compiler-core@3.5.30':
1806 + resolution: {integrity: sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==}
1807 +
1808 '@vue/compiler-dom@3.5.28':
1809 resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==}
1810
1811 '@vue/compiler-dom@3.5.29':
1812 resolution: {integrity: sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==}
1813
1814 + '@vue/compiler-dom@3.5.30':
1815 + resolution: {integrity: sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==}
1816 +
1817 '@vue/compiler-sfc@3.5.28':
1818 resolution: {integrity: sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==}
1819
1820 '@vue/compiler-sfc@3.5.29':
1821 resolution: {integrity: sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==}
1822
1823 + '@vue/compiler-sfc@3.5.30':
1824 + resolution: {integrity: sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==}
1825 +
1826 '@vue/compiler-ssr@3.5.28':
1827 resolution: {integrity: sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==}
1828
1829 '@vue/compiler-ssr@3.5.29':
1830 resolution: {integrity: sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==}
1831
1832 + '@vue/compiler-ssr@3.5.30':
1833 + resolution: {integrity: sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==}
1834 +
1835 '@vue/devtools-api@6.6.4':
1836 resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
1837
@@ -1765,8 +1841,8 @@ packages:
1841 '@vue/devtools-api@8.0.6':
1842 resolution: {integrity: sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA==}
1843
1768 - '@vue/devtools-core@8.0.7':
1769 - resolution: {integrity: sha512-PmpiPxvg3Of80ODHVvyckxwEW1Z02VIAvARIZS1xegINn3VuNQLm9iHUmKD+o6cLkMNWV8OG8x7zo0kgydZgdg==}
1844 + '@vue/devtools-core@8.1.0':
1845 + resolution: {integrity: sha512-LvD1VgDpoHmYL00IgKRLKktF6SsPAb0yaV8wB8q2jRwsAWvqhS8+vsMLEGKNs7uoKyymXhT92dhxgf/wir6YGQ==}
1846 peerDependencies:
1847 vue: ^3.0.0
1848
@@ -1776,8 +1852,8 @@ packages:
1852 '@vue/devtools-kit@8.0.6':
1853 resolution: {integrity: sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw==}
1854
1779 - '@vue/devtools-kit@8.0.7':
1780 - resolution: {integrity: sha512-H6esJGHGl5q0E9iV3m2EoBQHJ+V83WMW83A0/+Fn95eZ2iIvdsq4+UCS6yT/Fdd4cGZSchx/MdWDreM3WqMsDw==}
1855 + '@vue/devtools-kit@8.1.0':
1856 + resolution: {integrity: sha512-/NZlS4WtGIB54DA/z10gzk+n/V7zaqSzYZOVlg2CfdnpIKdB61bd7JDIMxf/zrtX41zod8E2/bbEBoW/d7x70Q==}
1857
1858 '@vue/devtools-shared@7.7.9':
1859 resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
@@ -1785,25 +1861,25 @@ packages:
1861 '@vue/devtools-shared@8.0.6':
1862 resolution: {integrity: sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg==}
1863
1788 - '@vue/devtools-shared@8.0.7':
1789 - resolution: {integrity: sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA==}
1864 + '@vue/devtools-shared@8.1.0':
1865 + resolution: {integrity: sha512-h8uCb4Qs8UT8VdTT5yjY6tOJ//qH7EpxToixR0xqejR55t5OdISIg7AJ7eBkhBs8iu1qG5gY3QQNN1DF1EelAA==}
1866
1791 - '@vue/language-core@3.2.5':
1792 - resolution: {integrity: sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g==}
1867 + '@vue/language-core@3.2.6':
1868 + resolution: {integrity: sha512-xYYYX3/aVup576tP/23sEUpgiEnujrENaoNRbaozC1/MA9I6EGFQRJb4xrt/MmUCAGlxTKL2RmT8JLTPqagCkg==}
1869
1794 - '@vue/reactivity@3.5.29':
1795 - resolution: {integrity: sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==}
1870 + '@vue/reactivity@3.5.30':
1871 + resolution: {integrity: sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==}
1872
1797 - '@vue/runtime-core@3.5.29':
1798 - resolution: {integrity: sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==}
1873 + '@vue/runtime-core@3.5.30':
1874 + resolution: {integrity: sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==}
1875
1800 - '@vue/runtime-dom@3.5.29':
1801 - resolution: {integrity: sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==}
1876 + '@vue/runtime-dom@3.5.30':
1877 + resolution: {integrity: sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==}
1878
1803 - '@vue/server-renderer@3.5.29':
1804 - resolution: {integrity: sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==}
1879 + '@vue/server-renderer@3.5.30':
1880 + resolution: {integrity: sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==}
1881 peerDependencies:
1806 - vue: 3.5.29
1882 + vue: 3.5.30
1883
1884 '@vue/shared@3.5.28':
1885 resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==}
@@ -1811,6 +1887,9 @@ packages:
1887 '@vue/shared@3.5.29':
1888 resolution: {integrity: sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==}
1889
1890 + '@vue/shared@3.5.30':
1891 + resolution: {integrity: sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==}
1892 +
1893 '@vue/test-utils@2.4.6':
1894 resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
1895
@@ -1883,10 +1962,6 @@ packages:
1962 engines: {node: '>=0.4.0'}
1963 hasBin: true
1964
1886 - agent-base@7.1.4:
1887 - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
1888 - engines: {node: '>= 14'}
1889 -
1965 ajv@6.14.0:
1966 resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
1967
@@ -1986,8 +2061,8 @@ packages:
2061 resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
2062 engines: {node: 20 || >=22}
2063
1989 - baseline-browser-mapping@2.10.0:
1990 - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==}
2064 + baseline-browser-mapping@2.10.8:
2065 + resolution: {integrity: sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==}
2066 engines: {node: '>=6.0.0'}
2067 hasBin: true
2068
@@ -2235,8 +2310,8 @@ packages:
2310 resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
2311 engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
2312
2238 - css-tree@3.1.0:
2239 - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
2313 + css-tree@3.2.1:
2314 + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
2315 engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
2316
2317 css-what@6.2.2:
@@ -2252,10 +2327,6 @@ packages:
2327 resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
2328 engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
2329
2255 - cssstyle@6.0.1:
2256 - resolution: {integrity: sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==}
2257 - engines: {node: '>=20'}
2258 -
2330 csstype@3.0.11:
2331 resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==}
2332
@@ -2274,8 +2345,8 @@ packages:
2345 date-fns@4.1.0:
2346 resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
2347
2277 - dayjs@1.11.19:
2278 - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
2348 + dayjs@1.11.20:
2349 + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
2350
2351 debounce@1.2.1:
2352 resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
@@ -2444,8 +2515,8 @@ packages:
2515 resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
2516 engines: {node: '>= 0.4'}
2517
2447 - es-module-lexer@1.7.0:
2448 - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
2518 + es-module-lexer@2.0.0:
2519 + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
2520
2521 es-object-atoms@1.1.1:
2522 resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
@@ -2487,8 +2558,8 @@ packages:
2558 peerDependencies:
2559 eslint: ^9.5.0 || ^10.0.0
2560
2490 - eslint-flat-config-utils@3.0.1:
2491 - resolution: {integrity: sha512-VMA3u86bLzNAwD/7DkLtQ9lolgIOx2Sj0kTMMnBvrvEz7w0rQj4aGCR+lqsqtld63gKiLyT4BnQZ3gmGDXtvjg==}
2561 + eslint-flat-config-utils@3.0.2:
2562 + resolution: {integrity: sha512-mPvevWSDQFwgABvyCurwIu6ZdKxGI5NW22/BGDwA1T49NO6bXuxbV9VfJK/tkQoNyPogT6Yu1d57iM0jnZVWmg==}
2563
2564 eslint-json-compat-utils@0.2.1:
2565 resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==}
@@ -2569,8 +2640,8 @@ packages:
2640 peerDependencies:
2641 eslint: ^9.0.0 || ^10.0.0
2642
2572 - eslint-plugin-regexp@3.0.0:
2573 - resolution: {integrity: sha512-iW7hgAV8NOG6E2dz+VeKpq67YLQ9jaajOKYpoOSic2/q8y9BMdXBKkSR9gcMtbqEhNQzdW41E3wWzvhp8ExYwQ==}
2643 + eslint-plugin-regexp@3.1.0:
2644 + resolution: {integrity: sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg==}
2645 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2646 peerDependencies:
2647 eslint: '>=9.38.0'
@@ -2626,6 +2697,10 @@ packages:
2697 resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==}
2698 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2699
2700 + eslint-scope@9.1.2:
2701 + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==}
2702 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2703 +
2704 eslint-visitor-keys@3.4.3:
2705 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
2706 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2638,8 +2713,8 @@ packages:
2713 resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
2714 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2715
2641 - eslint@10.0.2:
2642 - resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==}
2716 + eslint@10.0.3:
2717 + resolution: {integrity: sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==}
2718 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2719 hasBin: true
2720 peerDependencies:
@@ -2724,11 +2799,11 @@ packages:
2799 fast-levenshtein@2.0.6:
2800 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
2801
2727 - fast-xml-builder@1.0.0:
2728 - resolution: {integrity: sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==}
2802 + fast-xml-builder@1.1.4:
2803 + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==}
2804
2730 - fast-xml-parser@5.4.2:
2731 - resolution: {integrity: sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ==}
2805 + fast-xml-parser@5.5.6:
2806 + resolution: {integrity: sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==}
2807 hasBin: true
2808
2809 fastq@1.20.1:
@@ -2948,14 +3023,6 @@ packages:
3023 html-void-elements@3.0.0:
3024 resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
3025
2951 - http-proxy-agent@7.0.2:
2952 - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
2953 - engines: {node: '>= 14'}
2954 -
2955 - https-proxy-agent@7.0.6:
2956 - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
2957 - engines: {node: '>= 14'}
2958 -
3026 human-signals@1.1.1:
3027 resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==}
3028 engines: {node: '>=8.12.0'}
@@ -2972,8 +3039,8 @@ packages:
3039 resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
3040 engines: {node: '>= 4'}
3041
2975 - immutable@5.1.4:
2976 - resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
3042 + immutable@5.1.5:
3043 + resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==}
3044
3045 import-fresh@3.3.1:
3046 resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
@@ -3094,8 +3161,8 @@ packages:
3161 resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==}
3162 engines: {node: '>= 20'}
3163
3097 - jose@6.2.0:
3098 - resolution: {integrity: sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ==}
3164 + jose@6.2.2:
3165 + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
3166
3167 js-beautify@1.15.4:
3168 resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}
@@ -3130,9 +3197,9 @@ packages:
3197 resolution: {integrity: sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==}
3198 engines: {node: '>=20.0.0'}
3199
3133 - jsdom@28.1.0:
3134 - resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==}
3135 - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
3200 + jsdom@29.0.0:
3201 + resolution: {integrity: sha512-9FshNB6OepopZ08unmmGpsF7/qCjxGPbo3NbgfJAnPeHXnsODE9WWffXZtRFRFe0ntzaAOcSKNJFz8wiyvF1jQ==}
3202 + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
3203 peerDependencies:
3204 canvas: ^3.0.0
3205 peerDependenciesMeta:
@@ -3196,78 +3263,78 @@ packages:
3263 resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
3264 engines: {node: '>= 0.8.0'}
3265
3199 - lightningcss-android-arm64@1.31.1:
3200 - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==}
3266 + lightningcss-android-arm64@1.32.0:
3267 + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
3268 engines: {node: '>= 12.0.0'}
3269 cpu: [arm64]
3270 os: [android]
3271
3205 - lightningcss-darwin-arm64@1.31.1:
3206 - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==}
3272 + lightningcss-darwin-arm64@1.32.0:
3273 + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
3274 engines: {node: '>= 12.0.0'}
3275 cpu: [arm64]
3276 os: [darwin]
3277
3211 - lightningcss-darwin-x64@1.31.1:
3212 - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==}
3278 + lightningcss-darwin-x64@1.32.0:
3279 + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
3280 engines: {node: '>= 12.0.0'}
3281 cpu: [x64]
3282 os: [darwin]
3283
3217 - lightningcss-freebsd-x64@1.31.1:
3218 - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==}
3284 + lightningcss-freebsd-x64@1.32.0:
3285 + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
3286 engines: {node: '>= 12.0.0'}
3287 cpu: [x64]
3288 os: [freebsd]
3289
3223 - lightningcss-linux-arm-gnueabihf@1.31.1:
3224 - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==}
3290 + lightningcss-linux-arm-gnueabihf@1.32.0:
3291 + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
3292 engines: {node: '>= 12.0.0'}
3293 cpu: [arm]
3294 os: [linux]
3295
3229 - lightningcss-linux-arm64-gnu@1.31.1:
3230 - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==}
3296 + lightningcss-linux-arm64-gnu@1.32.0:
3297 + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
3298 engines: {node: '>= 12.0.0'}
3299 cpu: [arm64]
3300 os: [linux]
3301 libc: [glibc]
3302
3236 - lightningcss-linux-arm64-musl@1.31.1:
3237 - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==}
3303 + lightningcss-linux-arm64-musl@1.32.0:
3304 + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
3305 engines: {node: '>= 12.0.0'}
3306 cpu: [arm64]
3307 os: [linux]
3308 libc: [musl]
3309
3243 - lightningcss-linux-x64-gnu@1.31.1:
3244 - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==}
3310 + lightningcss-linux-x64-gnu@1.32.0:
3311 + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
3312 engines: {node: '>= 12.0.0'}
3313 cpu: [x64]
3314 os: [linux]
3315 libc: [glibc]
3316
3250 - lightningcss-linux-x64-musl@1.31.1:
3251 - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==}
3317 + lightningcss-linux-x64-musl@1.32.0:
3318 + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
3319 engines: {node: '>= 12.0.0'}
3320 cpu: [x64]
3321 os: [linux]
3322 libc: [musl]
3323
3257 - lightningcss-win32-arm64-msvc@1.31.1:
3258 - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==}
3324 + lightningcss-win32-arm64-msvc@1.32.0:
3325 + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
3326 engines: {node: '>= 12.0.0'}
3327 cpu: [arm64]
3328 os: [win32]
3329
3263 - lightningcss-win32-x64-msvc@1.31.1:
3264 - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==}
3330 + lightningcss-win32-x64-msvc@1.32.0:
3331 + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
3332 engines: {node: '>= 12.0.0'}
3333 cpu: [x64]
3334 os: [win32]
3335
3269 - lightningcss@1.31.1:
3270 - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==}
3336 + lightningcss@1.32.0:
3337 + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
3338 engines: {node: '>= 12.0.0'}
3339
3340 lines-and-columns@1.2.4:
@@ -3302,8 +3369,8 @@ packages:
3369 lru-cache@10.4.3:
3370 resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
3371
3305 - lru-cache@11.2.6:
3306 - resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==}
3372 + lru-cache@11.2.7:
3373 + resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
3374 engines: {node: 20 || >=22}
3375
3376 lru-cache@5.1.1:
@@ -3382,8 +3449,8 @@ packages:
3449 mdn-data@2.0.30:
3450 resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
3451
3385 - mdn-data@2.12.2:
3386 - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
3452 + mdn-data@2.27.1:
3453 + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
3454
3455 mdurl@2.0.0:
3456 resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
@@ -3506,10 +3573,6 @@ packages:
3573 resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
3574 engines: {node: '>=18'}
3575
3509 - minimatch@10.2.1:
3510 - resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==}
3511 - engines: {node: 20 || >=22}
3512 -
3576 minimatch@10.2.4:
3577 resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
3578 engines: {node: 18 || 20 || >=22}
@@ -3559,8 +3622,9 @@ packages:
3622 resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==}
3623 engines: {node: '>=10'}
3624
3562 - naive-ui@2.43.2:
3563 - resolution: {integrity: sha512-YlLMnGrwGTOc+zMj90sG3ubaH5/7czsgLgGcjTLA981IUaz8r6t4WIujNt8r9PNr+dqv6XNEr0vxkARgPPjfBQ==}
3625 + naive-ui@2.44.1:
3626 + resolution: {integrity: sha512-reo8Esw0p58liZwbUutC7meW24Xbn3EwNv91zReWKm2W4JPu+zfgJRn/F7aO0BFmvN+h2brA2M5lRvYqLq4kuA==}
3627 + engines: {node: '>=20'}
3628 peerDependencies:
3629 vue: ^3.0.0
3630
@@ -3569,8 +3633,8 @@ packages:
3633 engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
3634 hasBin: true
3635
3572 - nanoid@5.1.6:
3573 - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
3636 + nanoid@5.1.7:
3637 + resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==}
3638 engines: {node: ^18 || >=20}
3639 hasBin: true
3640
@@ -3718,6 +3782,10 @@ packages:
3782 resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
3783 engines: {node: '>=8'}
3784
3785 + path-expression-matcher@1.1.3:
3786 + resolution: {integrity: sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==}
3787 + engines: {node: '>=14.0.0'}
3788 +
3789 path-key@3.1.1:
3790 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
3791 engines: {node: '>=8'}
@@ -3811,6 +3879,10 @@ packages:
3879 resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
3880 engines: {node: ^10 || ^12 || >=14}
3881
3882 + postcss@8.5.8:
3883 + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
3884 + engines: {node: ^10 || ^12 || >=14}
3885 +
3886 prelude-ls@1.2.1:
3887 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
3888 engines: {node: '>= 0.8.0'}
@@ -4076,8 +4148,8 @@ packages:
4148 rxjs@7.8.2:
4149 resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
4150
4079 - sass@1.97.3:
4080 - resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==}
4151 + sass@1.98.0:
4152 + resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==}
4153 engines: {node: '>=14.0.0'}
4154 hasBin: true
4155
@@ -4127,8 +4199,8 @@ packages:
4199 resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
4200 engines: {node: '>= 0.4'}
4201
4130 - shiki@4.0.1:
4131 - resolution: {integrity: sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ==}
4202 + shiki@4.0.2:
4203 + resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==}
4204 engines: {node: '>=20'}
4205
4206 siginfo@2.0.0:
@@ -4192,8 +4264,8 @@ packages:
4264 engines: {node: '>=16'}
4265 hasBin: true
4266
4195 - std-env@3.10.0:
4196 - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
4267 + std-env@4.0.0:
4268 + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
4269
4270 stream-combiner@0.0.4:
4271 resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==}
@@ -4258,8 +4330,8 @@ packages:
4330 resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
4331 engines: {node: '>=20'}
4332
4261 - tailwindcss@4.2.1:
4262 - resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==}
4333 + tailwindcss@4.2.2:
4334 + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
4335
4336 tapable@2.3.0:
4337 resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
@@ -4324,8 +4396,8 @@ packages:
4396 resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
4397 engines: {node: '>=6'}
4398
4327 - tough-cookie@6.0.0:
4328 - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
4399 + tough-cookie@6.0.1:
4400 + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
4401 engines: {node: '>=16'}
4402
4403 tr46@6.0.0:
@@ -4392,8 +4464,8 @@ packages:
4464 undici-types@7.22.0:
4465 resolution: {integrity: sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw==}
4466
4395 - undici@7.22.0:
4396 - resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
4467 + undici@7.24.4:
4468 + resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==}
4469 engines: {node: '>=20.18.1'}
4470
4471 unist-util-is@6.0.1:
@@ -4483,11 +4555,11 @@ packages:
4555 '@nuxt/kit':
4556 optional: true
4557
4486 - vite-plugin-vue-devtools@8.0.7:
4487 - resolution: {integrity: sha512-BWj/ykGpqVAJVdPyHmSTUm44buz3jPv+6jnvuFdQSRH0kAgP1cEIE4doHiFyqHXOmuB5EQVR/nh2g9YRiRNs9g==}
4558 + vite-plugin-vue-devtools@8.1.0:
4559 + resolution: {integrity: sha512-4AvNRePfni3+PqOunACmAImC6SJVpUv6f7/g4oakyre9hYdEMrvDYlNmTZQsJPzVLMcGzn1FvSEqJ/n4HQ9cDg==}
4560 engines: {node: '>=v14.21.3'}
4561 peerDependencies:
4490 - vite: ^6.0.0 || ^7.0.0-0 || ^8.0.0-0
4562 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0
4563
4564 vite-plugin-vue-inspector@5.3.2:
4565 resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==}
@@ -4539,20 +4611,21 @@ packages:
4611 yaml:
4612 optional: true
4613
4542 - vitest@4.0.18:
4543 - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==}
4614 + vitest@4.1.0:
4615 + resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==}
4616 engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
4617 hasBin: true
4618 peerDependencies:
4619 '@edge-runtime/vm': '*'
4620 '@opentelemetry/api': ^1.9.0
4621 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
4550 - '@vitest/browser-playwright': 4.0.18
4551 - '@vitest/browser-preview': 4.0.18
4552 - '@vitest/browser-webdriverio': 4.0.18
4553 - '@vitest/ui': 4.0.18
4622 + '@vitest/browser-playwright': 4.1.0
4623 + '@vitest/browser-preview': 4.1.0
4624 + '@vitest/browser-webdriverio': 4.1.0
4625 + '@vitest/ui': 4.1.0
4626 happy-dom: '*'
4627 jsdom: '*'
4628 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0
4629 peerDependenciesMeta:
4630 '@edge-runtime/vm':
4631 optional: true
@@ -4611,8 +4684,8 @@ packages:
4684 peerDependencies:
4685 vue: ^3.0.0
4686
4614 - vue-i18n@11.2.8:
4615 - resolution: {integrity: sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==}
4687 + vue-i18n@11.3.0:
4688 + resolution: {integrity: sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==}
4689 engines: {node: '>= 16'}
4690 peerDependencies:
4691 vue: ^3.0.0
@@ -4637,8 +4710,8 @@ packages:
4710 peerDependencies:
4711 vue: ^3.3.4
4712
4640 - vue-tsc@3.2.5:
4641 - resolution: {integrity: sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA==}
4713 + vue-tsc@3.2.6:
4714 + resolution: {integrity: sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q==}
4715 hasBin: true
4716 peerDependencies:
4717 typescript: '>=5.0.0'
@@ -4655,8 +4728,8 @@ packages:
4728 peerDependencies:
4729 vue: ^3.2
4730
4658 - vue@3.5.29:
4659 - resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==}
4731 + vue@3.5.30:
4732 + resolution: {integrity: sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==}
4733 peerDependencies:
4734 typescript: '*'
4735 peerDependenciesMeta:
@@ -4696,8 +4769,8 @@ packages:
4769 resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
4770 engines: {node: '>=20'}
4771
4699 - whatwg-url@16.0.0:
4700 - resolution: {integrity: sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==}
4772 + whatwg-url@16.0.1:
4773 + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
4774 engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
4775
4776 which@1.3.1:
@@ -4810,8 +4883,6 @@ packages:
4883
4884 snapshots:
4885
4813 - '@acemir/cssom@0.9.31': {}
4814 -
4886 '@ajoelp/json-to-formdata@1.5.0':
4887 dependencies:
4888 lodash: 4.17.21
@@ -4900,44 +4971,44 @@ snapshots:
4971 dependencies:
4972 '@algolia/client-common': 5.48.1
4973
4903 - '@antfu/eslint-config@7.7.0(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3))(@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.29)(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))':
4974 + '@antfu/eslint-config@7.7.3(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)))':
4975 dependencies:
4976 '@antfu/install-pkg': 1.1.0
4977 '@clack/prompts': 1.1.0
4907 - '@e18e/eslint-plugin': 0.2.0(eslint@10.0.2(jiti@2.6.1))
4908 - '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.0.2(jiti@2.6.1))
4978 + '@e18e/eslint-plugin': 0.2.0(eslint@10.0.3(jiti@2.6.1))
4979 + '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.0.3(jiti@2.6.1))
4980 '@eslint/markdown': 7.5.1
4910 - '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.2(jiti@2.6.1))
4911 - '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
4912 - '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
4913 - '@vitest/eslint-plugin': 1.6.9(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
4981 + '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.3(jiti@2.6.1))
4982 + '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
4983 + '@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
4984 + '@vitest/eslint-plugin': 1.6.12(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)))
4985 ansis: 4.2.0
4986 cac: 7.0.0
4916 - eslint: 10.0.2(jiti@2.6.1)
4917 - eslint-config-flat-gitignore: 2.2.1(eslint@10.0.2(jiti@2.6.1))
4918 - eslint-flat-config-utils: 3.0.1
4919 - eslint-merge-processors: 2.0.0(eslint@10.0.2(jiti@2.6.1))
4920 - eslint-plugin-antfu: 3.2.2(eslint@10.0.2(jiti@2.6.1))
4921 - eslint-plugin-command: 3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3))(@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))
4922 - eslint-plugin-import-lite: 0.5.2(eslint@10.0.2(jiti@2.6.1))
4923 - eslint-plugin-jsdoc: 62.7.1(eslint@10.0.2(jiti@2.6.1))
4924 - eslint-plugin-jsonc: 3.1.1(eslint@10.0.2(jiti@2.6.1))
4925 - eslint-plugin-n: 17.24.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
4987 + eslint: 10.0.3(jiti@2.6.1)
4988 + eslint-config-flat-gitignore: 2.2.1(eslint@10.0.3(jiti@2.6.1))
4989 + eslint-flat-config-utils: 3.0.2
4990 + eslint-merge-processors: 2.0.0(eslint@10.0.3(jiti@2.6.1))
4991 + eslint-plugin-antfu: 3.2.2(eslint@10.0.3(jiti@2.6.1))
4992 + eslint-plugin-command: 3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))
4993 + eslint-plugin-import-lite: 0.5.2(eslint@10.0.3(jiti@2.6.1))
4994 + eslint-plugin-jsdoc: 62.7.1(eslint@10.0.3(jiti@2.6.1))
4995 + eslint-plugin-jsonc: 3.1.1(eslint@10.0.3(jiti@2.6.1))
4996 + eslint-plugin-n: 17.24.0(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
4997 eslint-plugin-no-only-tests: 3.3.0
4927 - eslint-plugin-perfectionist: 5.6.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
4928 - eslint-plugin-pnpm: 1.6.0(eslint@10.0.2(jiti@2.6.1))
4929 - eslint-plugin-regexp: 3.0.0(eslint@10.0.2(jiti@2.6.1))
4930 - eslint-plugin-toml: 1.3.1(eslint@10.0.2(jiti@2.6.1))
4931 - eslint-plugin-unicorn: 63.0.0(eslint@10.0.2(jiti@2.6.1))
4932 - eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))
4933 - eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.0.2(jiti@2.6.1)))(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.0.2(jiti@2.6.1)))
4934 - eslint-plugin-yml: 3.3.1(eslint@10.0.2(jiti@2.6.1))
4935 - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.29)(eslint@10.0.2(jiti@2.6.1))
4998 + eslint-plugin-perfectionist: 5.6.0(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
4999 + eslint-plugin-pnpm: 1.6.0(eslint@10.0.3(jiti@2.6.1))
5000 + eslint-plugin-regexp: 3.1.0(eslint@10.0.3(jiti@2.6.1))
5001 + eslint-plugin-toml: 1.3.1(eslint@10.0.3(jiti@2.6.1))
5002 + eslint-plugin-unicorn: 63.0.0(eslint@10.0.3(jiti@2.6.1))
5003 + eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))
5004 + eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.0.3(jiti@2.6.1)))(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.0.3(jiti@2.6.1)))
5005 + eslint-plugin-yml: 3.3.1(eslint@10.0.3(jiti@2.6.1))
5006 + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.30)(eslint@10.0.3(jiti@2.6.1))
5007 globals: 17.4.0
5008 local-pkg: 1.1.2
5009 parse-gitignore: 2.0.0
5010 toml-eslint-parser: 1.0.3
4940 - vue-eslint-parser: 10.4.0(eslint@10.0.2(jiti@2.6.1))
5011 + vue-eslint-parser: 10.4.0(eslint@10.0.3(jiti@2.6.1))
5012 yaml-eslint-parser: 2.0.0
5013 transitivePeerDependencies:
5014 - '@eslint/json'
@@ -4963,21 +5034,21 @@ snapshots:
5034 tinyexec: 1.0.2
5035 tinyglobby: 0.2.15
5036
4966 - '@asamuzakjp/css-color@4.1.2':
5037 + '@asamuzakjp/css-color@5.0.1':
5038 dependencies:
5039 '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
4969 - '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
5040 + '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
5041 '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
5042 '@csstools/css-tokenizer': 4.0.0
4972 - lru-cache: 11.2.6
5043 + lru-cache: 11.2.7
5044
4974 - '@asamuzakjp/dom-selector@6.8.1':
5045 + '@asamuzakjp/dom-selector@7.0.3':
5046 dependencies:
5047 '@asamuzakjp/nwsapi': 2.3.9
5048 bidi-js: 1.0.3
4978 - css-tree: 3.1.0
5049 + css-tree: 3.2.1
5050 is-potential-custom-element-name: 1.0.1
4980 - lru-cache: 11.2.6
5051 + lru-cache: 11.2.7
5052
5053 '@asamuzakjp/nwsapi@2.3.9': {}
5054
@@ -5174,7 +5245,7 @@ snapshots:
5245
5246 '@bramus/specificity@2.4.2':
5247 dependencies:
5177 - css-tree: 3.1.0
5248 + css-tree: 3.2.1
5249
5250 '@clack/core@1.1.0':
5251 dependencies:
@@ -5189,14 +5260,14 @@ snapshots:
5260 dependencies:
5261 '@codemirror/language': 6.12.1
5262 '@codemirror/state': 6.5.4
5192 - '@codemirror/view': 6.39.16
5263 + '@codemirror/view': 6.40.0
5264 '@lezer/common': 1.5.1
5265
5195 - '@codemirror/commands@6.10.2':
5266 + '@codemirror/commands@6.10.3':
5267 dependencies:
5268 '@codemirror/language': 6.12.1
5198 - '@codemirror/state': 6.5.4
5199 - '@codemirror/view': 6.39.16
5269 + '@codemirror/state': 6.6.0
5270 + '@codemirror/view': 6.40.0
5271 '@lezer/common': 1.5.1
5272
5273 '@codemirror/lang-javascript@6.2.5':
@@ -5205,7 +5276,7 @@ snapshots:
5276 '@codemirror/language': 6.12.1
5277 '@codemirror/lint': 6.9.5
5278 '@codemirror/state': 6.5.4
5208 - '@codemirror/view': 6.39.16
5279 + '@codemirror/view': 6.40.0
5280 '@lezer/common': 1.5.1
5281 '@lezer/javascript': 1.5.4
5282
@@ -5214,14 +5285,14 @@ snapshots:
5285 '@codemirror/autocomplete': 6.20.0
5286 '@codemirror/language': 6.12.1
5287 '@codemirror/state': 6.5.4
5217 - '@codemirror/view': 6.39.16
5288 + '@codemirror/view': 6.40.0
5289 '@lezer/common': 1.5.1
5290 '@lezer/xml': 1.0.6
5291
5292 '@codemirror/language@6.12.1':
5293 dependencies:
5294 '@codemirror/state': 6.5.4
5224 - '@codemirror/view': 6.39.16
5295 + '@codemirror/view': 6.40.0
5296 '@lezer/common': 1.5.1
5297 '@lezer/highlight': 1.2.3
5298 '@lezer/lr': 1.4.8
@@ -5230,29 +5301,33 @@ snapshots:
5301 '@codemirror/lint@6.9.5':
5302 dependencies:
5303 '@codemirror/state': 6.5.4
5233 - '@codemirror/view': 6.39.16
5304 + '@codemirror/view': 6.40.0
5305 crelt: 1.0.6
5306
5307 '@codemirror/search@6.6.0':
5308 dependencies:
5309 '@codemirror/state': 6.5.4
5239 - '@codemirror/view': 6.39.16
5310 + '@codemirror/view': 6.40.0
5311 crelt: 1.0.6
5312
5313 '@codemirror/state@6.5.4':
5314 dependencies:
5315 '@marijn/find-cluster-break': 1.0.2
5316
5317 + '@codemirror/state@6.6.0':
5318 + dependencies:
5319 + '@marijn/find-cluster-break': 1.0.2
5320 +
5321 '@codemirror/theme-one-dark@6.1.3':
5322 dependencies:
5323 '@codemirror/language': 6.12.1
5324 '@codemirror/state': 6.5.4
5250 - '@codemirror/view': 6.39.16
5325 + '@codemirror/view': 6.40.0
5326 '@lezer/highlight': 1.2.3
5327
5253 - '@codemirror/view@6.39.16':
5328 + '@codemirror/view@6.40.0':
5329 dependencies:
5255 - '@codemirror/state': 6.5.4
5330 + '@codemirror/state': 6.6.0
5331 crelt: 1.0.6
5332 style-mod: 4.1.3
5333 w3c-keyname: 2.2.8
@@ -5264,20 +5339,20 @@ snapshots:
5339 dependencies:
5340 css-render: 0.15.14
5341
5267 - '@css-render/vue3-ssr@0.15.14(vue@3.5.29(typescript@5.9.3))':
5342 + '@css-render/vue3-ssr@0.15.14(vue@3.5.30(typescript@5.9.3))':
5343 dependencies:
5269 - vue: 3.5.29(typescript@5.9.3)
5344 + vue: 3.5.30(typescript@5.9.3)
5345
5271 - '@csstools/color-helpers@6.0.1': {}
5346 + '@csstools/color-helpers@6.0.2': {}
5347
5348 '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
5349 dependencies:
5350 '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
5351 '@csstools/css-tokenizer': 4.0.0
5352
5278 - '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
5353 + '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
5354 dependencies:
5280 - '@csstools/color-helpers': 6.0.1
5355 + '@csstools/color-helpers': 6.0.2
5356 '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
5357 '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
5358 '@csstools/css-tokenizer': 4.0.0
@@ -5286,15 +5361,17 @@ snapshots:
5361 dependencies:
5362 '@csstools/css-tokenizer': 4.0.0
5363
5289 - '@csstools/css-syntax-patches-for-csstree@1.0.27': {}
5364 + '@csstools/css-syntax-patches-for-csstree@1.1.1(css-tree@3.2.1)':
5365 + optionalDependencies:
5366 + css-tree: 3.2.1
5367
5368 '@csstools/css-tokenizer@4.0.0': {}
5369
5293 - '@e18e/eslint-plugin@0.2.0(eslint@10.0.2(jiti@2.6.1))':
5370 + '@e18e/eslint-plugin@0.2.0(eslint@10.0.3(jiti@2.6.1))':
5371 dependencies:
5295 - eslint-plugin-depend: 1.5.0(eslint@10.0.2(jiti@2.6.1))
5372 + eslint-plugin-depend: 1.5.0(eslint@10.0.3(jiti@2.6.1))
5373 optionalDependencies:
5297 - eslint: 10.0.2(jiti@2.6.1)
5374 + eslint: 10.0.3(jiti@2.6.1)
5375
5376 '@emotion/hash@0.8.0': {}
5377
@@ -5386,36 +5463,40 @@ snapshots:
5463 '@esbuild/win32-x64@0.27.3':
5464 optional: true
5465
5389 - '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.0.2(jiti@2.6.1))':
5466 + '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.0.3(jiti@2.6.1))':
5467 dependencies:
5468 escape-string-regexp: 4.0.0
5392 - eslint: 10.0.2(jiti@2.6.1)
5469 + eslint: 10.0.3(jiti@2.6.1)
5470 ignore: 7.0.5
5471
5395 - '@eslint-community/eslint-utils@4.9.1(eslint@10.0.2(jiti@2.6.1))':
5472 + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.3(jiti@2.6.1))':
5473 dependencies:
5397 - eslint: 10.0.2(jiti@2.6.1)
5474 + eslint: 10.0.3(jiti@2.6.1)
5475 eslint-visitor-keys: 3.4.3
5476
5477 '@eslint-community/regexpp@4.12.2': {}
5478
5402 - '@eslint/compat@2.0.2(eslint@10.0.2(jiti@2.6.1))':
5479 + '@eslint/compat@2.0.2(eslint@10.0.3(jiti@2.6.1))':
5480 dependencies:
5481 '@eslint/core': 1.1.0
5482 optionalDependencies:
5406 - eslint: 10.0.2(jiti@2.6.1)
5483 + eslint: 10.0.3(jiti@2.6.1)
5484
5408 - '@eslint/config-array@0.23.2':
5485 + '@eslint/config-array@0.23.3':
5486 dependencies:
5410 - '@eslint/object-schema': 3.0.2
5487 + '@eslint/object-schema': 3.0.3
5488 debug: 4.4.3
5412 - minimatch: 10.2.1
5489 + minimatch: 10.2.4
5490 transitivePeerDependencies:
5491 - supports-color
5492
5493 '@eslint/config-helpers@0.5.2':
5494 dependencies:
5418 - '@eslint/core': 1.1.0
5495 + '@eslint/core': 1.1.1
5496 +
5497 + '@eslint/config-helpers@0.5.3':
5498 + dependencies:
5499 + '@eslint/core': 1.1.1
5500
5501 '@eslint/core@0.17.0':
5502 dependencies:
@@ -5425,6 +5506,10 @@ snapshots:
5506 dependencies:
5507 '@types/json-schema': 7.0.15
5508
5509 + '@eslint/core@1.1.1':
5510 + dependencies:
5511 + '@types/json-schema': 7.0.15
5512 +
5513 '@eslint/markdown@7.5.1':
5514 dependencies:
5515 '@eslint/core': 0.17.0
@@ -5439,7 +5524,7 @@ snapshots:
5524 transitivePeerDependencies:
5525 - supports-color
5526
5442 - '@eslint/object-schema@3.0.2': {}
5527 + '@eslint/object-schema@3.0.3': {}
5528
5529 '@eslint/plugin-kit@0.4.1':
5530 dependencies:
@@ -5451,12 +5536,17 @@ snapshots:
5536 '@eslint/core': 1.1.0
5537 levn: 0.4.1
5538
5454 - '@exodus/bytes@1.14.1': {}
5539 + '@eslint/plugin-kit@0.6.1':
5540 + dependencies:
5541 + '@eslint/core': 1.1.1
5542 + levn: 0.4.1
5543 +
5544 + '@exodus/bytes@1.15.0': {}
5545
5456 - '@f3ve/vue-markdown-it@0.2.3(vue@3.5.29(typescript@5.9.3))':
5546 + '@f3ve/vue-markdown-it@0.2.3(vue@3.5.30(typescript@5.9.3))':
5547 dependencies:
5548 markdown-it: 14.1.1
5459 - vue: 3.5.29(typescript@5.9.3)
5549 + vue: 3.5.30(typescript@5.9.3)
5550
5551 '@fontsource/jetbrains-mono@5.2.8': {}
5552
@@ -5495,22 +5585,28 @@ snapshots:
5585
5586 '@iconify/types@2.0.0': {}
5587
5498 - '@iconify/vue@5.0.0(vue@3.5.29(typescript@5.9.3))':
5588 + '@iconify/vue@5.0.0(vue@3.5.30(typescript@5.9.3))':
5589 dependencies:
5590 '@iconify/types': 2.0.0
5501 - vue: 3.5.29(typescript@5.9.3)
5591 + vue: 3.5.30(typescript@5.9.3)
5592
5503 - '@intlify/core-base@11.2.8':
5593 + '@intlify/core-base@11.3.0':
5594 dependencies:
5505 - '@intlify/message-compiler': 11.2.8
5506 - '@intlify/shared': 11.2.8
5595 + '@intlify/devtools-types': 11.3.0
5596 + '@intlify/message-compiler': 11.3.0
5597 + '@intlify/shared': 11.3.0
5598
5508 - '@intlify/message-compiler@11.2.8':
5599 + '@intlify/devtools-types@11.3.0':
5600 dependencies:
5510 - '@intlify/shared': 11.2.8
5601 + '@intlify/core-base': 11.3.0
5602 + '@intlify/shared': 11.3.0
5603 +
5604 + '@intlify/message-compiler@11.3.0':
5605 + dependencies:
5606 + '@intlify/shared': 11.3.0
5607 source-map-js: 1.2.1
5608
5513 - '@intlify/shared@11.2.8': {}
5609 + '@intlify/shared@11.3.0': {}
5610
5611 '@isaacs/cliui@8.0.2':
5612 dependencies:
@@ -5799,45 +5895,45 @@ snapshots:
5895 '@rollup/rollup-win32-x64-msvc@4.57.1':
5896 optional: true
5897
5802 - '@shikijs/core@4.0.1':
5898 + '@shikijs/core@4.0.2':
5899 dependencies:
5804 - '@shikijs/primitive': 4.0.1
5805 - '@shikijs/types': 4.0.1
5900 + '@shikijs/primitive': 4.0.2
5901 + '@shikijs/types': 4.0.2
5902 '@shikijs/vscode-textmate': 10.0.2
5903 '@types/hast': 3.0.4
5904 hast-util-to-html: 9.0.5
5905
5810 - '@shikijs/engine-javascript@4.0.1':
5906 + '@shikijs/engine-javascript@4.0.2':
5907 dependencies:
5812 - '@shikijs/types': 4.0.1
5908 + '@shikijs/types': 4.0.2
5909 '@shikijs/vscode-textmate': 10.0.2
5910 oniguruma-to-es: 4.3.4
5911
5816 - '@shikijs/engine-oniguruma@4.0.1':
5912 + '@shikijs/engine-oniguruma@4.0.2':
5913 dependencies:
5818 - '@shikijs/types': 4.0.1
5914 + '@shikijs/types': 4.0.2
5915 '@shikijs/vscode-textmate': 10.0.2
5916
5821 - '@shikijs/langs@4.0.1':
5917 + '@shikijs/langs@4.0.2':
5918 dependencies:
5823 - '@shikijs/types': 4.0.1
5919 + '@shikijs/types': 4.0.2
5920
5825 - '@shikijs/markdown-it@4.0.1':
5921 + '@shikijs/markdown-it@4.0.2':
5922 dependencies:
5923 markdown-it: 14.1.1
5828 - shiki: 4.0.1
5924 + shiki: 4.0.2
5925
5830 - '@shikijs/primitive@4.0.1':
5926 + '@shikijs/primitive@4.0.2':
5927 dependencies:
5832 - '@shikijs/types': 4.0.1
5928 + '@shikijs/types': 4.0.2
5929 '@shikijs/vscode-textmate': 10.0.2
5930 '@types/hast': 3.0.4
5931
5836 - '@shikijs/themes@4.0.1':
5932 + '@shikijs/themes@4.0.2':
5933 dependencies:
5838 - '@shikijs/types': 4.0.1
5934 + '@shikijs/types': 4.0.2
5935
5840 - '@shikijs/types@4.0.1':
5936 + '@shikijs/types@4.0.2':
5937 dependencies:
5938 '@shikijs/vscode-textmate': 10.0.2
5939 '@types/hast': 3.0.4
@@ -5852,83 +5948,83 @@ snapshots:
5948
5949 '@standard-schema/spec@1.1.0': {}
5950
5855 - '@stylistic/eslint-plugin@5.10.0(eslint@10.0.2(jiti@2.6.1))':
5951 + '@stylistic/eslint-plugin@5.10.0(eslint@10.0.3(jiti@2.6.1))':
5952 dependencies:
5857 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
5953 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
5954 '@typescript-eslint/types': 8.56.1
5859 - eslint: 10.0.2(jiti@2.6.1)
5955 + eslint: 10.0.3(jiti@2.6.1)
5956 eslint-visitor-keys: 4.2.1
5957 espree: 10.4.0
5958 estraverse: 5.3.0
5959 picomatch: 4.0.3
5960
5865 - '@tailwindcss/node@4.2.1':
5961 + '@tailwindcss/node@4.2.2':
5962 dependencies:
5963 '@jridgewell/remapping': 2.3.5
5964 enhanced-resolve: 5.19.0
5965 jiti: 2.6.1
5870 - lightningcss: 1.31.1
5966 + lightningcss: 1.32.0
5967 magic-string: 0.30.21
5968 source-map-js: 1.2.1
5873 - tailwindcss: 4.2.1
5969 + tailwindcss: 4.2.2
5970
5875 - '@tailwindcss/oxide-android-arm64@4.2.1':
5971 + '@tailwindcss/oxide-android-arm64@4.2.2':
5972 optional: true
5973
5878 - '@tailwindcss/oxide-darwin-arm64@4.2.1':
5974 + '@tailwindcss/oxide-darwin-arm64@4.2.2':
5975 optional: true
5976
5881 - '@tailwindcss/oxide-darwin-x64@4.2.1':
5977 + '@tailwindcss/oxide-darwin-x64@4.2.2':
5978 optional: true
5979
5884 - '@tailwindcss/oxide-freebsd-x64@4.2.1':
5980 + '@tailwindcss/oxide-freebsd-x64@4.2.2':
5981 optional: true
5982
5887 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1':
5983 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
5984 optional: true
5985
5890 - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1':
5986 + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
5987 optional: true
5988
5893 - '@tailwindcss/oxide-linux-arm64-musl@4.2.1':
5989 + '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
5990 optional: true
5991
5896 - '@tailwindcss/oxide-linux-x64-gnu@4.2.1':
5992 + '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
5993 optional: true
5994
5899 - '@tailwindcss/oxide-linux-x64-musl@4.2.1':
5995 + '@tailwindcss/oxide-linux-x64-musl@4.2.2':
5996 optional: true
5997
5902 - '@tailwindcss/oxide-wasm32-wasi@4.2.1':
5998 + '@tailwindcss/oxide-wasm32-wasi@4.2.2':
5999 optional: true
6000
5905 - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1':
6001 + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
6002 optional: true
6003
5908 - '@tailwindcss/oxide-win32-x64-msvc@4.2.1':
6004 + '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
6005 optional: true
6006
5911 - '@tailwindcss/oxide@4.2.1':
6007 + '@tailwindcss/oxide@4.2.2':
6008 optionalDependencies:
5913 - '@tailwindcss/oxide-android-arm64': 4.2.1
5914 - '@tailwindcss/oxide-darwin-arm64': 4.2.1
5915 - '@tailwindcss/oxide-darwin-x64': 4.2.1
5916 - '@tailwindcss/oxide-freebsd-x64': 4.2.1
5917 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1
5918 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1
5919 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.1
5920 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.1
5921 - '@tailwindcss/oxide-linux-x64-musl': 4.2.1
5922 - '@tailwindcss/oxide-wasm32-wasi': 4.2.1
5923 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
5924 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.1
5925 -
5926 - '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))':
5927 - dependencies:
5928 - '@tailwindcss/node': 4.2.1
5929 - '@tailwindcss/oxide': 4.2.1
5930 - tailwindcss: 4.2.1
5931 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
6009 + '@tailwindcss/oxide-android-arm64': 4.2.2
6010 + '@tailwindcss/oxide-darwin-arm64': 4.2.2
6011 + '@tailwindcss/oxide-darwin-x64': 4.2.2
6012 + '@tailwindcss/oxide-freebsd-x64': 4.2.2
6013 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2
6014 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2
6015 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2
6016 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2
6017 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2
6018 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2
6019 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
6020 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2
6021 +
6022 + '@tailwindcss/vite@4.2.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))':
6023 + dependencies:
6024 + '@tailwindcss/node': 4.2.2
6025 + '@tailwindcss/oxide': 4.2.2
6026 + tailwindcss: 4.2.2
6027 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
6028
6029 '@tsconfig/node20@20.1.9': {}
6030
@@ -5958,7 +6054,7 @@ snapshots:
6054 '@types/fs-extra@11.0.4':
6055 dependencies:
6056 '@types/jsonfile': 6.1.4
5961 - '@types/node': 25.3.5
6057 + '@types/node': 25.5.0
6058
6059 '@types/hast@3.0.4':
6060 dependencies:
@@ -5966,7 +6062,7 @@ snapshots:
6062
6063 '@types/jsdom@28.0.0':
6064 dependencies:
5969 - '@types/node': 25.3.5
6065 + '@types/node': 25.5.0
6066 '@types/tough-cookie': 4.0.5
6067 parse5: 7.3.0
6068 undici-types: 7.22.0
@@ -5975,9 +6071,7 @@ snapshots:
6071
6072 '@types/jsonfile@6.1.4':
6073 dependencies:
5978 - '@types/node': 25.3.5
5979 -
5980 - '@types/katex@0.16.8': {}
6074 + '@types/node': 25.5.0
6075
6076 '@types/linkify-it@5.0.0': {}
6077
@@ -6002,7 +6096,7 @@ snapshots:
6096
6097 '@types/ms@2.1.0': {}
6098
6005 - '@types/node@25.3.5':
6099 + '@types/node@25.5.0':
6100 dependencies:
6101 undici-types: 7.18.2
6102
@@ -6022,15 +6116,15 @@ snapshots:
6116
6117 '@types/web-bluetooth@0.0.21': {}
6118
6025 - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6119 + '@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
6120 dependencies:
6121 '@eslint-community/regexpp': 4.12.2
6028 - '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6029 - '@typescript-eslint/scope-manager': 8.56.1
6030 - '@typescript-eslint/type-utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6031 - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6032 - '@typescript-eslint/visitor-keys': 8.56.1
6033 - eslint: 10.0.2(jiti@2.6.1)
6122 + '@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
6123 + '@typescript-eslint/scope-manager': 8.57.1
6124 + '@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
6125 + '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
6126 + '@typescript-eslint/visitor-keys': 8.57.1
6127 + eslint: 10.0.3(jiti@2.6.1)
6128 ignore: 7.0.5
6129 natural-compare: 1.4.0
6130 ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -6038,14 +6132,26 @@ snapshots:
6132 transitivePeerDependencies:
6133 - supports-color
6134
6041 - '@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6135 + '@typescript-eslint/parser@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
6136 dependencies:
6137 '@typescript-eslint/scope-manager': 8.56.1
6138 '@typescript-eslint/types': 8.56.1
6139 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
6140 '@typescript-eslint/visitor-keys': 8.56.1
6141 debug: 4.4.3
6048 - eslint: 10.0.2(jiti@2.6.1)
6142 + eslint: 10.0.3(jiti@2.6.1)
6143 + typescript: 5.9.3
6144 + transitivePeerDependencies:
6145 + - supports-color
6146 +
6147 + '@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
6148 + dependencies:
6149 + '@typescript-eslint/scope-manager': 8.57.1
6150 + '@typescript-eslint/types': 8.57.1
6151 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
6152 + '@typescript-eslint/visitor-keys': 8.57.1
6153 + debug: 4.4.3
6154 + eslint: 10.0.3(jiti@2.6.1)
6155 typescript: 5.9.3
6156 transitivePeerDependencies:
6157 - supports-color
@@ -6059,13 +6165,22 @@ snapshots:
6165 transitivePeerDependencies:
6166 - supports-color
6167
6062 - '@typescript-eslint/rule-tester@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6168 + '@typescript-eslint/project-service@8.57.1(typescript@5.9.3)':
6169 dependencies:
6064 - '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6170 + '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
6171 + '@typescript-eslint/types': 8.57.1
6172 + debug: 4.4.3
6173 + typescript: 5.9.3
6174 + transitivePeerDependencies:
6175 + - supports-color
6176 +
6177 + '@typescript-eslint/rule-tester@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
6178 + dependencies:
6179 + '@typescript-eslint/parser': 8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
6180 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
6066 - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6181 + '@typescript-eslint/utils': 8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
6182 ajv: 6.14.0
6068 - eslint: 10.0.2(jiti@2.6.1)
6183 + eslint: 10.0.3(jiti@2.6.1)
6184 json-stable-stringify-without-jsonify: 1.0.1
6185 lodash.merge: 4.6.2
6186 semver: 7.7.4
@@ -6078,17 +6193,26 @@ snapshots:
6193 '@typescript-eslint/types': 8.56.1
6194 '@typescript-eslint/visitor-keys': 8.56.1
6195
6196 + '@typescript-eslint/scope-manager@8.57.1':
6197 + dependencies:
6198 + '@typescript-eslint/types': 8.57.1
6199 + '@typescript-eslint/visitor-keys': 8.57.1
6200 +
6201 '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)':
6202 dependencies:
6203 typescript: 5.9.3
6204
6085 - '@typescript-eslint/type-utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6205 + '@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)':
6206 dependencies:
6087 - '@typescript-eslint/types': 8.56.1
6088 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
6089 - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6207 + typescript: 5.9.3
6208 +
6209 + '@typescript-eslint/type-utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
6210 + dependencies:
6211 + '@typescript-eslint/types': 8.57.1
6212 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
6213 + '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
6214 debug: 4.4.3
6091 - eslint: 10.0.2(jiti@2.6.1)
6215 + eslint: 10.0.3(jiti@2.6.1)
6216 ts-api-utils: 2.4.0(typescript@5.9.3)
6217 typescript: 5.9.3
6218 transitivePeerDependencies:
@@ -6096,6 +6220,8 @@ snapshots:
6220
6221 '@typescript-eslint/types@8.56.1': {}
6222
6223 + '@typescript-eslint/types@8.57.1': {}
6224 +
6225 '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)':
6226 dependencies:
6227 '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3)
@@ -6111,13 +6237,39 @@ snapshots:
6237 transitivePeerDependencies:
6238 - supports-color
6239
6114 - '@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6240 + '@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3)':
6241 dependencies:
6116 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
6242 + '@typescript-eslint/project-service': 8.57.1(typescript@5.9.3)
6243 + '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
6244 + '@typescript-eslint/types': 8.57.1
6245 + '@typescript-eslint/visitor-keys': 8.57.1
6246 + debug: 4.4.3
6247 + minimatch: 10.2.4
6248 + semver: 7.7.4
6249 + tinyglobby: 0.2.15
6250 + ts-api-utils: 2.4.0(typescript@5.9.3)
6251 + typescript: 5.9.3
6252 + transitivePeerDependencies:
6253 + - supports-color
6254 +
6255 + '@typescript-eslint/utils@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
6256 + dependencies:
6257 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
6258 '@typescript-eslint/scope-manager': 8.56.1
6259 '@typescript-eslint/types': 8.56.1
6260 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
6120 - eslint: 10.0.2(jiti@2.6.1)
6261 + eslint: 10.0.3(jiti@2.6.1)
6262 + typescript: 5.9.3
6263 + transitivePeerDependencies:
6264 + - supports-color
6265 +
6266 + '@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
6267 + dependencies:
6268 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
6269 + '@typescript-eslint/scope-manager': 8.57.1
6270 + '@typescript-eslint/types': 8.57.1
6271 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
6272 + eslint: 10.0.3(jiti@2.6.1)
6273 typescript: 5.9.3
6274 transitivePeerDependencies:
6275 - supports-color
@@ -6127,74 +6279,81 @@ snapshots:
6279 '@typescript-eslint/types': 8.56.1
6280 eslint-visitor-keys: 5.0.1
6281
6282 + '@typescript-eslint/visitor-keys@8.57.1':
6283 + dependencies:
6284 + '@typescript-eslint/types': 8.57.1
6285 + eslint-visitor-keys: 5.0.1
6286 +
6287 '@ungap/structured-clone@1.3.0': {}
6288
6132 - '@vitejs/plugin-vue-jsx@5.1.4(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))':
6289 + '@vitejs/plugin-vue-jsx@5.1.5(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))':
6290 dependencies:
6291 '@babel/core': 7.29.0
6292 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
6293 '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
6294 '@rolldown/pluginutils': 1.0.0-rc.4
6295 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0)
6139 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
6140 - vue: 3.5.29(typescript@5.9.3)
6296 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
6297 + vue: 3.5.30(typescript@5.9.3)
6298 transitivePeerDependencies:
6299 - supports-color
6300
6144 - '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))':
6301 + '@vitejs/plugin-vue@6.0.5(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))':
6302 dependencies:
6303 '@rolldown/pluginutils': 1.0.0-rc.2
6147 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
6148 - vue: 3.5.29(typescript@5.9.3)
6304 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
6305 + vue: 3.5.30(typescript@5.9.3)
6306
6150 - '@vitest/eslint-plugin@1.6.9(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))':
6307 + '@vitest/eslint-plugin@1.6.12(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)))':
6308 dependencies:
6309 '@typescript-eslint/scope-manager': 8.56.1
6153 - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6154 - eslint: 10.0.2(jiti@2.6.1)
6310 + '@typescript-eslint/utils': 8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
6311 + eslint: 10.0.3(jiti@2.6.1)
6312 optionalDependencies:
6313 typescript: 5.9.3
6157 - vitest: 4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
6314 + vitest: 4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
6315 transitivePeerDependencies:
6316 - supports-color
6317
6161 - '@vitest/expect@4.0.18':
6318 + '@vitest/expect@4.1.0':
6319 dependencies:
6320 '@standard-schema/spec': 1.1.0
6321 '@types/chai': 5.2.3
6165 - '@vitest/spy': 4.0.18
6166 - '@vitest/utils': 4.0.18
6322 + '@vitest/spy': 4.1.0
6323 + '@vitest/utils': 4.1.0
6324 chai: 6.2.2
6325 tinyrainbow: 3.0.3
6326
6170 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))':
6327 + '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))':
6328 dependencies:
6172 - '@vitest/spy': 4.0.18
6329 + '@vitest/spy': 4.1.0
6330 estree-walker: 3.0.3
6331 magic-string: 0.30.21
6332 optionalDependencies:
6176 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
6333 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
6334
6178 - '@vitest/pretty-format@4.0.18':
6335 + '@vitest/pretty-format@4.1.0':
6336 dependencies:
6337 tinyrainbow: 3.0.3
6338
6182 - '@vitest/runner@4.0.18':
6339 + '@vitest/runner@4.1.0':
6340 dependencies:
6184 - '@vitest/utils': 4.0.18
6341 + '@vitest/utils': 4.1.0
6342 pathe: 2.0.3
6343
6187 - '@vitest/snapshot@4.0.18':
6344 + '@vitest/snapshot@4.1.0':
6345 dependencies:
6189 - '@vitest/pretty-format': 4.0.18
6346 + '@vitest/pretty-format': 4.1.0
6347 + '@vitest/utils': 4.1.0
6348 magic-string: 0.30.21
6349 pathe: 2.0.3
6350
6193 - '@vitest/spy@4.0.18': {}
6351 + '@vitest/spy@4.1.0': {}
6352
6195 - '@vitest/utils@4.0.18':
6353 + '@vitest/utils@4.1.0':
6354 dependencies:
6197 - '@vitest/pretty-format': 4.0.18
6355 + '@vitest/pretty-format': 4.1.0
6356 + convert-source-map: 2.0.0
6357 tinyrainbow: 3.0.3
6358
6359 '@volar/language-core@2.4.28':
@@ -6209,7 +6368,7 @@ snapshots:
6368 path-browserify: 1.0.1
6369 vscode-uri: 3.1.0
6370
6212 - '@vue-macros/common@3.1.2(vue@3.5.29(typescript@5.9.3))':
6371 + '@vue-macros/common@3.1.2(vue@3.5.30(typescript@5.9.3))':
6372 dependencies:
6373 '@vue/compiler-sfc': 3.5.28
6374 ast-kit: 2.2.0
@@ -6217,7 +6376,7 @@ snapshots:
6376 magic-string-ast: 1.0.3
6377 unplugin-utils: 0.3.1
6378 optionalDependencies:
6220 - vue: 3.5.29(typescript@5.9.3)
6379 + vue: 3.5.30(typescript@5.9.3)
6380
6381 '@vue/babel-helper-vue-transform-on@1.5.0': {}
6382
@@ -6233,7 +6392,7 @@ snapshots:
6392 '@babel/types': 7.29.0
6393 '@vue/babel-helper-vue-transform-on': 1.5.0
6394 '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.0)
6236 - '@vue/shared': 3.5.28
6395 + '@vue/shared': 3.5.29
6396 optionalDependencies:
6397 '@babel/core': 7.29.0
6398 transitivePeerDependencies:
@@ -6249,7 +6408,7 @@ snapshots:
6408 '@babel/types': 7.29.0
6409 '@vue/babel-helper-vue-transform-on': 2.0.1
6410 '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0)
6252 - '@vue/shared': 3.5.28
6411 + '@vue/shared': 3.5.29
6412 optionalDependencies:
6413 '@babel/core': 7.29.0
6414 transitivePeerDependencies:
@@ -6262,7 +6421,7 @@ snapshots:
6421 '@babel/helper-module-imports': 7.28.6
6422 '@babel/helper-plugin-utils': 7.28.6
6423 '@babel/parser': 7.29.0
6265 - '@vue/compiler-sfc': 3.5.28
6424 + '@vue/compiler-sfc': 3.5.29
6425 transitivePeerDependencies:
6426 - supports-color
6427
@@ -6273,7 +6432,7 @@ snapshots:
6432 '@babel/helper-module-imports': 7.28.6
6433 '@babel/helper-plugin-utils': 7.28.6
6434 '@babel/parser': 7.29.0
6276 - '@vue/compiler-sfc': 3.5.28
6435 + '@vue/compiler-sfc': 3.5.29
6436 transitivePeerDependencies:
6437 - supports-color
6438
@@ -6293,6 +6452,14 @@ snapshots:
6452 estree-walker: 2.0.2
6453 source-map-js: 1.2.1
6454
6455 + '@vue/compiler-core@3.5.30':
6456 + dependencies:
6457 + '@babel/parser': 7.29.0
6458 + '@vue/shared': 3.5.30
6459 + entities: 7.0.1
6460 + estree-walker: 2.0.2
6461 + source-map-js: 1.2.1
6462 +
6463 '@vue/compiler-dom@3.5.28':
6464 dependencies:
6465 '@vue/compiler-core': 3.5.28
@@ -6303,6 +6470,11 @@ snapshots:
6470 '@vue/compiler-core': 3.5.29
6471 '@vue/shared': 3.5.29
6472
6473 + '@vue/compiler-dom@3.5.30':
6474 + dependencies:
6475 + '@vue/compiler-core': 3.5.30
6476 + '@vue/shared': 3.5.30
6477 +
6478 '@vue/compiler-sfc@3.5.28':
6479 dependencies:
6480 '@babel/parser': 7.29.0
@@ -6327,6 +6499,18 @@ snapshots:
6499 postcss: 8.5.6
6500 source-map-js: 1.2.1
6501
6502 + '@vue/compiler-sfc@3.5.30':
6503 + dependencies:
6504 + '@babel/parser': 7.29.0
6505 + '@vue/compiler-core': 3.5.30
6506 + '@vue/compiler-dom': 3.5.30
6507 + '@vue/compiler-ssr': 3.5.30
6508 + '@vue/shared': 3.5.30
6509 + estree-walker: 2.0.2
6510 + magic-string: 0.30.21
6511 + postcss: 8.5.8
6512 + source-map-js: 1.2.1
6513 +
6514 '@vue/compiler-ssr@3.5.28':
6515 dependencies:
6516 '@vue/compiler-dom': 3.5.28
@@ -6337,6 +6521,11 @@ snapshots:
6521 '@vue/compiler-dom': 3.5.29
6522 '@vue/shared': 3.5.29
6523
6524 + '@vue/compiler-ssr@3.5.30':
6525 + dependencies:
6526 + '@vue/compiler-dom': 3.5.30
6527 + '@vue/shared': 3.5.30
6528 +
6529 '@vue/devtools-api@6.6.4': {}
6530
6531 '@vue/devtools-api@7.7.9':
@@ -6347,11 +6536,11 @@ snapshots:
6536 dependencies:
6537 '@vue/devtools-kit': 8.0.6
6538
6350 - '@vue/devtools-core@8.0.7(vue@3.5.29(typescript@5.9.3))':
6539 + '@vue/devtools-core@8.1.0(vue@3.5.30(typescript@5.9.3))':
6540 dependencies:
6352 - '@vue/devtools-kit': 8.0.7
6353 - '@vue/devtools-shared': 8.0.7
6354 - vue: 3.5.29(typescript@5.9.3)
6541 + '@vue/devtools-kit': 8.1.0
6542 + '@vue/devtools-shared': 8.1.0
6543 + vue: 3.5.30(typescript@5.9.3)
6544
6545 '@vue/devtools-kit@7.7.9':
6546 dependencies:
@@ -6373,9 +6562,9 @@ snapshots:
6562 speakingurl: 14.0.1
6563 superjson: 2.2.6
6564
6376 - '@vue/devtools-kit@8.0.7':
6565 + '@vue/devtools-kit@8.1.0':
6566 dependencies:
6378 - '@vue/devtools-shared': 8.0.7
6567 + '@vue/devtools-shared': 8.1.0
6568 birpc: 2.9.0
6569 hookable: 5.5.3
6570 perfect-debounce: 2.1.0
@@ -6388,93 +6577,95 @@ snapshots:
6577 dependencies:
6578 rfdc: 1.4.1
6579
6391 - '@vue/devtools-shared@8.0.7': {}
6580 + '@vue/devtools-shared@8.1.0': {}
6581
6393 - '@vue/language-core@3.2.5':
6582 + '@vue/language-core@3.2.6':
6583 dependencies:
6584 '@volar/language-core': 2.4.28
6396 - '@vue/compiler-dom': 3.5.28
6397 - '@vue/shared': 3.5.28
6585 + '@vue/compiler-dom': 3.5.29
6586 + '@vue/shared': 3.5.29
6587 alien-signals: 3.1.2
6588 muggle-string: 0.4.1
6589 path-browserify: 1.0.1
6590 picomatch: 4.0.3
6591
6403 - '@vue/reactivity@3.5.29':
6592 + '@vue/reactivity@3.5.30':
6593 dependencies:
6405 - '@vue/shared': 3.5.29
6594 + '@vue/shared': 3.5.30
6595
6407 - '@vue/runtime-core@3.5.29':
6596 + '@vue/runtime-core@3.5.30':
6597 dependencies:
6409 - '@vue/reactivity': 3.5.29
6410 - '@vue/shared': 3.5.29
6598 + '@vue/reactivity': 3.5.30
6599 + '@vue/shared': 3.5.30
6600
6412 - '@vue/runtime-dom@3.5.29':
6601 + '@vue/runtime-dom@3.5.30':
6602 dependencies:
6414 - '@vue/reactivity': 3.5.29
6415 - '@vue/runtime-core': 3.5.29
6416 - '@vue/shared': 3.5.29
6603 + '@vue/reactivity': 3.5.30
6604 + '@vue/runtime-core': 3.5.30
6605 + '@vue/shared': 3.5.30
6606 csstype: 3.2.3
6607
6419 - '@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3))':
6608 + '@vue/server-renderer@3.5.30(vue@3.5.30(typescript@5.9.3))':
6609 dependencies:
6421 - '@vue/compiler-ssr': 3.5.29
6422 - '@vue/shared': 3.5.29
6423 - vue: 3.5.29(typescript@5.9.3)
6610 + '@vue/compiler-ssr': 3.5.30
6611 + '@vue/shared': 3.5.30
6612 + vue: 3.5.30(typescript@5.9.3)
6613
6614 '@vue/shared@3.5.28': {}
6615
6616 '@vue/shared@3.5.29': {}
6617
6618 + '@vue/shared@3.5.30': {}
6619 +
6620 '@vue/test-utils@2.4.6':
6621 dependencies:
6622 js-beautify: 1.15.4
6623 vue-component-type-helpers: 2.2.12
6624
6434 - '@vue/tsconfig@0.9.0(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))':
6625 + '@vue/tsconfig@0.9.0(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))':
6626 optionalDependencies:
6627 typescript: 5.9.3
6437 - vue: 3.5.29(typescript@5.9.3)
6628 + vue: 3.5.30(typescript@5.9.3)
6629
6439 - '@vueuse/core@13.9.0(vue@3.5.29(typescript@5.9.3))':
6630 + '@vueuse/core@13.9.0(vue@3.5.30(typescript@5.9.3))':
6631 dependencies:
6632 '@types/web-bluetooth': 0.0.21
6633 '@vueuse/metadata': 13.9.0
6443 - '@vueuse/shared': 13.9.0(vue@3.5.29(typescript@5.9.3))
6444 - vue: 3.5.29(typescript@5.9.3)
6634 + '@vueuse/shared': 13.9.0(vue@3.5.30(typescript@5.9.3))
6635 + vue: 3.5.30(typescript@5.9.3)
6636
6446 - '@vueuse/core@14.2.1(vue@3.5.29(typescript@5.9.3))':
6637 + '@vueuse/core@14.2.1(vue@3.5.30(typescript@5.9.3))':
6638 dependencies:
6639 '@types/web-bluetooth': 0.0.21
6640 '@vueuse/metadata': 14.2.1
6450 - '@vueuse/shared': 14.2.1(vue@3.5.29(typescript@5.9.3))
6451 - vue: 3.5.29(typescript@5.9.3)
6641 + '@vueuse/shared': 14.2.1(vue@3.5.30(typescript@5.9.3))
6642 + vue: 3.5.30(typescript@5.9.3)
6643
6644 '@vueuse/metadata@13.9.0': {}
6645
6646 '@vueuse/metadata@14.2.1': {}
6647
6457 - '@vueuse/motion@3.0.3(vue@3.5.29(typescript@5.9.3))':
6648 + '@vueuse/motion@3.0.3(vue@3.5.30(typescript@5.9.3))':
6649 dependencies:
6459 - '@vueuse/core': 13.9.0(vue@3.5.29(typescript@5.9.3))
6460 - '@vueuse/shared': 13.9.0(vue@3.5.29(typescript@5.9.3))
6650 + '@vueuse/core': 13.9.0(vue@3.5.30(typescript@5.9.3))
6651 + '@vueuse/shared': 13.9.0(vue@3.5.30(typescript@5.9.3))
6652 defu: 6.1.4
6653 framesync: 6.1.2
6654 popmotion: 11.0.5
6655 style-value-types: 5.1.2
6465 - vue: 3.5.29(typescript@5.9.3)
6656 + vue: 3.5.30(typescript@5.9.3)
6657 optionalDependencies:
6658 '@nuxt/kit': 3.21.1
6659 transitivePeerDependencies:
6660 - magicast
6661
6471 - '@vueuse/shared@13.9.0(vue@3.5.29(typescript@5.9.3))':
6662 + '@vueuse/shared@13.9.0(vue@3.5.30(typescript@5.9.3))':
6663 dependencies:
6473 - vue: 3.5.29(typescript@5.9.3)
6664 + vue: 3.5.30(typescript@5.9.3)
6665
6475 - '@vueuse/shared@14.2.1(vue@3.5.29(typescript@5.9.3))':
6666 + '@vueuse/shared@14.2.1(vue@3.5.30(typescript@5.9.3))':
6667 dependencies:
6477 - vue: 3.5.29(typescript@5.9.3)
6668 + vue: 3.5.30(typescript@5.9.3)
6669
6670 '@yr/monotone-cubic-spline@1.0.3': {}
6671
@@ -6490,8 +6681,6 @@ snapshots:
6681
6682 acorn@8.16.0: {}
6683
6493 - agent-base@7.1.4: {}
6494 -
6684 ajv@6.14.0:
6685 dependencies:
6686 fast-deep-equal: 3.1.3
@@ -6590,7 +6779,7 @@ snapshots:
6779 dependencies:
6780 jackspeak: 4.2.3
6781
6593 - baseline-browser-mapping@2.10.0: {}
6782 + baseline-browser-mapping@2.10.8: {}
6783
6784 bidi-js@1.0.3:
6785 dependencies:
@@ -6626,7 +6815,7 @@ snapshots:
6815
6816 browserslist@4.28.1:
6817 dependencies:
6629 - baseline-browser-mapping: 2.10.0
6818 + baseline-browser-mapping: 2.10.8
6819 caniuse-lite: 1.0.30001770
6820 electron-to-chromium: 1.5.286
6821 node-releases: 2.0.27
@@ -6741,12 +6930,12 @@ snapshots:
6930 codemirror@6.0.2:
6931 dependencies:
6932 '@codemirror/autocomplete': 6.20.0
6744 - '@codemirror/commands': 6.10.2
6933 + '@codemirror/commands': 6.10.3
6934 '@codemirror/language': 6.12.1
6935 '@codemirror/lint': 6.9.5
6936 '@codemirror/search': 6.6.0
6937 '@codemirror/state': 6.5.4
6749 - '@codemirror/view': 6.39.16
6938 + '@codemirror/view': 6.40.0
6939
6940 color-convert@2.0.1:
6941 dependencies:
@@ -6842,9 +7031,9 @@ snapshots:
7031 mdn-data: 2.0.30
7032 source-map-js: 1.2.1
7033
6845 - css-tree@3.1.0:
7034 + css-tree@3.2.1:
7035 dependencies:
6847 - mdn-data: 2.12.2
7036 + mdn-data: 2.27.1
7037 source-map-js: 1.2.1
7038
7039 css-what@6.2.2: {}
@@ -6855,13 +7044,6 @@ snapshots:
7044 dependencies:
7045 css-tree: 2.2.1
7046
6858 - cssstyle@6.0.1:
6859 - dependencies:
6860 - '@asamuzakjp/css-color': 4.1.2
6861 - '@csstools/css-syntax-patches-for-csstree': 1.0.27
6862 - css-tree: 3.1.0
6863 - lru-cache: 11.2.6
6864 -
7047 csstype@3.0.11: {}
7048
7049 csstype@3.2.3: {}
@@ -6869,7 +7051,7 @@ snapshots:
7051 data-urls@7.0.0:
7052 dependencies:
7053 whatwg-mimetype: 5.0.0
6872 - whatwg-url: 16.0.0
7054 + whatwg-url: 16.0.1
7055 transitivePeerDependencies:
7056 - '@noble/hashes'
7057
@@ -6879,7 +7061,7 @@ snapshots:
7061
7062 date-fns@4.1.0: {}
7063
6882 - dayjs@1.11.19: {}
7064 + dayjs@1.11.20: {}
7065
7066 debounce@1.2.1: {}
7067
@@ -7039,7 +7221,7 @@ snapshots:
7221
7222 es-errors@1.3.0: {}
7223
7042 - es-module-lexer@1.7.0: {}
7224 + es-module-lexer@2.0.0: {}
7225
7226 es-object-atoms@1.1.1:
7227 dependencies:
@@ -7089,62 +7271,62 @@ snapshots:
7271
7272 escape-string-regexp@5.0.0: {}
7273
7092 - eslint-compat-utils@0.5.1(eslint@10.0.2(jiti@2.6.1)):
7274 + eslint-compat-utils@0.5.1(eslint@10.0.3(jiti@2.6.1)):
7275 dependencies:
7094 - eslint: 10.0.2(jiti@2.6.1)
7276 + eslint: 10.0.3(jiti@2.6.1)
7277 semver: 7.7.4
7278
7097 - eslint-config-flat-gitignore@2.2.1(eslint@10.0.2(jiti@2.6.1)):
7279 + eslint-config-flat-gitignore@2.2.1(eslint@10.0.3(jiti@2.6.1)):
7280 dependencies:
7099 - '@eslint/compat': 2.0.2(eslint@10.0.2(jiti@2.6.1))
7100 - eslint: 10.0.2(jiti@2.6.1)
7281 + '@eslint/compat': 2.0.2(eslint@10.0.3(jiti@2.6.1))
7282 + eslint: 10.0.3(jiti@2.6.1)
7283
7102 - eslint-flat-config-utils@3.0.1:
7284 + eslint-flat-config-utils@3.0.2:
7285 dependencies:
7104 - '@eslint/config-helpers': 0.5.2
7286 + '@eslint/config-helpers': 0.5.3
7287 pathe: 2.0.3
7288
7107 - eslint-json-compat-utils@0.2.1(eslint@10.0.2(jiti@2.6.1))(jsonc-eslint-parser@3.1.0):
7289 + eslint-json-compat-utils@0.2.1(eslint@10.0.3(jiti@2.6.1))(jsonc-eslint-parser@3.1.0):
7290 dependencies:
7109 - eslint: 10.0.2(jiti@2.6.1)
7291 + eslint: 10.0.3(jiti@2.6.1)
7292 esquery: 1.7.0
7293 jsonc-eslint-parser: 3.1.0
7294
7113 - eslint-merge-processors@2.0.0(eslint@10.0.2(jiti@2.6.1)):
7295 + eslint-merge-processors@2.0.0(eslint@10.0.3(jiti@2.6.1)):
7296 dependencies:
7115 - eslint: 10.0.2(jiti@2.6.1)
7297 + eslint: 10.0.3(jiti@2.6.1)
7298
7117 - eslint-plugin-antfu@3.2.2(eslint@10.0.2(jiti@2.6.1)):
7299 + eslint-plugin-antfu@3.2.2(eslint@10.0.3(jiti@2.6.1)):
7300 dependencies:
7119 - eslint: 10.0.2(jiti@2.6.1)
7301 + eslint: 10.0.3(jiti@2.6.1)
7302
7121 - eslint-plugin-command@3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3))(@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1)):
7303 + eslint-plugin-command@3.5.2(@typescript-eslint/rule-tester@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1)):
7304 dependencies:
7305 '@es-joy/jsdoccomment': 0.84.0
7124 - '@typescript-eslint/rule-tester': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
7125 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
7126 - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
7127 - eslint: 10.0.2(jiti@2.6.1)
7306 + '@typescript-eslint/rule-tester': 8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
7307 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
7308 + '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
7309 + eslint: 10.0.3(jiti@2.6.1)
7310
7129 - eslint-plugin-depend@1.5.0(eslint@10.0.2(jiti@2.6.1)):
7311 + eslint-plugin-depend@1.5.0(eslint@10.0.3(jiti@2.6.1)):
7312 dependencies:
7313 empathic: 2.0.0
7132 - eslint: 10.0.2(jiti@2.6.1)
7314 + eslint: 10.0.3(jiti@2.6.1)
7315 module-replacements: 2.11.0
7316 semver: 7.7.4
7317
7136 - eslint-plugin-es-x@7.8.0(eslint@10.0.2(jiti@2.6.1)):
7318 + eslint-plugin-es-x@7.8.0(eslint@10.0.3(jiti@2.6.1)):
7319 dependencies:
7138 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7320 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
7321 '@eslint-community/regexpp': 4.12.2
7140 - eslint: 10.0.2(jiti@2.6.1)
7141 - eslint-compat-utils: 0.5.1(eslint@10.0.2(jiti@2.6.1))
7322 + eslint: 10.0.3(jiti@2.6.1)
7323 + eslint-compat-utils: 0.5.1(eslint@10.0.3(jiti@2.6.1))
7324
7143 - eslint-plugin-import-lite@0.5.2(eslint@10.0.2(jiti@2.6.1)):
7325 + eslint-plugin-import-lite@0.5.2(eslint@10.0.3(jiti@2.6.1)):
7326 dependencies:
7145 - eslint: 10.0.2(jiti@2.6.1)
7327 + eslint: 10.0.3(jiti@2.6.1)
7328
7147 - eslint-plugin-jsdoc@62.7.1(eslint@10.0.2(jiti@2.6.1)):
7329 + eslint-plugin-jsdoc@62.7.1(eslint@10.0.3(jiti@2.6.1)):
7330 dependencies:
7331 '@es-joy/jsdoccomment': 0.84.0
7332 '@es-joy/resolve.exports': 1.2.0
@@ -7152,7 +7334,7 @@ snapshots:
7334 comment-parser: 1.4.5
7335 debug: 4.4.3
7336 escape-string-regexp: 4.0.0
7155 - eslint: 10.0.2(jiti@2.6.1)
7337 + eslint: 10.0.3(jiti@2.6.1)
7338 espree: 11.1.1
7339 esquery: 1.7.0
7340 html-entities: 2.6.0
@@ -7164,27 +7346,27 @@ snapshots:
7346 transitivePeerDependencies:
7347 - supports-color
7348
7167 - eslint-plugin-jsonc@3.1.1(eslint@10.0.2(jiti@2.6.1)):
7349 + eslint-plugin-jsonc@3.1.1(eslint@10.0.3(jiti@2.6.1)):
7350 dependencies:
7169 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7351 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
7352 '@eslint/core': 1.1.0
7353 '@eslint/plugin-kit': 0.6.0
7354 '@ota-meshi/ast-token-store': 0.3.0
7355 diff-sequences: 29.6.3
7174 - eslint: 10.0.2(jiti@2.6.1)
7175 - eslint-json-compat-utils: 0.2.1(eslint@10.0.2(jiti@2.6.1))(jsonc-eslint-parser@3.1.0)
7356 + eslint: 10.0.3(jiti@2.6.1)
7357 + eslint-json-compat-utils: 0.2.1(eslint@10.0.3(jiti@2.6.1))(jsonc-eslint-parser@3.1.0)
7358 jsonc-eslint-parser: 3.1.0
7359 natural-compare: 1.4.0
7360 synckit: 0.11.12
7361 transitivePeerDependencies:
7362 - '@eslint/json'
7363
7182 - eslint-plugin-n@17.24.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3):
7364 + eslint-plugin-n@17.24.0(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3):
7365 dependencies:
7184 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7366 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
7367 enhanced-resolve: 5.19.0
7186 - eslint: 10.0.2(jiti@2.6.1)
7187 - eslint-plugin-es-x: 7.8.0(eslint@10.0.2(jiti@2.6.1))
7368 + eslint: 10.0.3(jiti@2.6.1)
7369 + eslint-plugin-es-x: 7.8.0(eslint@10.0.3(jiti@2.6.1))
7370 get-tsconfig: 4.13.6
7371 globals: 15.15.0
7372 globrex: 0.1.2
@@ -7196,19 +7378,19 @@ snapshots:
7378
7379 eslint-plugin-no-only-tests@3.3.0: {}
7380
7199 - eslint-plugin-perfectionist@5.6.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3):
7381 + eslint-plugin-perfectionist@5.6.0(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3):
7382 dependencies:
7201 - '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
7202 - eslint: 10.0.2(jiti@2.6.1)
7383 + '@typescript-eslint/utils': 8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
7384 + eslint: 10.0.3(jiti@2.6.1)
7385 natural-orderby: 5.0.0
7386 transitivePeerDependencies:
7387 - supports-color
7388 - typescript
7389
7208 - eslint-plugin-pnpm@1.6.0(eslint@10.0.2(jiti@2.6.1)):
7390 + eslint-plugin-pnpm@1.6.0(eslint@10.0.3(jiti@2.6.1)):
7391 dependencies:
7392 empathic: 2.0.0
7211 - eslint: 10.0.2(jiti@2.6.1)
7393 + eslint: 10.0.3(jiti@2.6.1)
7394 jsonc-eslint-parser: 3.1.0
7395 pathe: 2.0.3
7396 pnpm-workspace-yaml: 1.6.0
@@ -7216,37 +7398,37 @@ snapshots:
7398 yaml: 2.8.2
7399 yaml-eslint-parser: 2.0.0
7400
7219 - eslint-plugin-regexp@3.0.0(eslint@10.0.2(jiti@2.6.1)):
7401 + eslint-plugin-regexp@3.1.0(eslint@10.0.3(jiti@2.6.1)):
7402 dependencies:
7221 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7403 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
7404 '@eslint-community/regexpp': 4.12.2
7405 comment-parser: 1.4.5
7224 - eslint: 10.0.2(jiti@2.6.1)
7406 + eslint: 10.0.3(jiti@2.6.1)
7407 jsdoc-type-pratt-parser: 7.1.1
7408 refa: 0.12.1
7409 regexp-ast-analysis: 0.7.1
7410 scslre: 0.3.0
7411
7230 - eslint-plugin-toml@1.3.1(eslint@10.0.2(jiti@2.6.1)):
7412 + eslint-plugin-toml@1.3.1(eslint@10.0.3(jiti@2.6.1)):
7413 dependencies:
7414 '@eslint/core': 1.1.0
7415 '@eslint/plugin-kit': 0.6.0
7416 '@ota-meshi/ast-token-store': 0.3.0
7417 debug: 4.4.3
7236 - eslint: 10.0.2(jiti@2.6.1)
7418 + eslint: 10.0.3(jiti@2.6.1)
7419 toml-eslint-parser: 1.0.3
7420 transitivePeerDependencies:
7421 - supports-color
7422
7241 - eslint-plugin-unicorn@63.0.0(eslint@10.0.2(jiti@2.6.1)):
7423 + eslint-plugin-unicorn@63.0.0(eslint@10.0.3(jiti@2.6.1)):
7424 dependencies:
7425 '@babel/helper-validator-identifier': 7.28.5
7244 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7426 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
7427 change-case: 5.4.4
7428 ci-info: 4.4.0
7429 clean-regexp: 1.0.0
7430 core-js-compat: 3.48.0
7249 - eslint: 10.0.2(jiti@2.6.1)
7431 + eslint: 10.0.3(jiti@2.6.1)
7432 find-up-simple: 1.0.1
7433 globals: 16.5.0
7434 indent-string: 5.0.0
@@ -7258,27 +7440,27 @@ snapshots:
7440 semver: 7.7.4
7441 strip-indent: 4.1.1
7442
7261 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1)):
7443 + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1)):
7444 dependencies:
7263 - eslint: 10.0.2(jiti@2.6.1)
7445 + eslint: 10.0.3(jiti@2.6.1)
7446 optionalDependencies:
7265 - '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
7447 + '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
7448
7267 - eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.0.2(jiti@2.6.1)))(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.0.2(jiti@2.6.1))):
7449 + eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.0.3(jiti@2.6.1)))(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.0.3(jiti@2.6.1))):
7450 dependencies:
7269 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7270 - eslint: 10.0.2(jiti@2.6.1)
7451 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
7452 + eslint: 10.0.3(jiti@2.6.1)
7453 natural-compare: 1.4.0
7454 nth-check: 2.1.1
7455 postcss-selector-parser: 7.1.1
7456 semver: 7.7.4
7275 - vue-eslint-parser: 10.4.0(eslint@10.0.2(jiti@2.6.1))
7457 + vue-eslint-parser: 10.4.0(eslint@10.0.3(jiti@2.6.1))
7458 xml-name-validator: 4.0.0
7459 optionalDependencies:
7278 - '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.2(jiti@2.6.1))
7279 - '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
7460 + '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.3(jiti@2.6.1))
7461 + '@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
7462
7281 - eslint-plugin-yml@3.3.1(eslint@10.0.2(jiti@2.6.1)):
7463 + eslint-plugin-yml@3.3.1(eslint@10.0.3(jiti@2.6.1)):
7464 dependencies:
7465 '@eslint/core': 1.1.0
7466 '@eslint/plugin-kit': 0.6.0
@@ -7286,16 +7468,16 @@ snapshots:
7468 debug: 4.4.3
7469 diff-sequences: 29.6.3
7470 escape-string-regexp: 5.0.0
7289 - eslint: 10.0.2(jiti@2.6.1)
7471 + eslint: 10.0.3(jiti@2.6.1)
7472 natural-compare: 1.4.0
7473 yaml-eslint-parser: 2.0.0
7474 transitivePeerDependencies:
7475 - supports-color
7476
7295 - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.29)(eslint@10.0.2(jiti@2.6.1)):
7477 + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.30)(eslint@10.0.3(jiti@2.6.1)):
7478 dependencies:
7297 - '@vue/compiler-sfc': 3.5.29
7298 - eslint: 10.0.2(jiti@2.6.1)
7479 + '@vue/compiler-sfc': 3.5.30
7480 + eslint: 10.0.3(jiti@2.6.1)
7481
7482 eslint-scope@9.1.1:
7483 dependencies:
@@ -7304,20 +7486,27 @@ snapshots:
7486 esrecurse: 4.3.0
7487 estraverse: 5.3.0
7488
7489 + eslint-scope@9.1.2:
7490 + dependencies:
7491 + '@types/esrecurse': 4.3.1
7492 + '@types/estree': 1.0.8
7493 + esrecurse: 4.3.0
7494 + estraverse: 5.3.0
7495 +
7496 eslint-visitor-keys@3.4.3: {}
7497
7498 eslint-visitor-keys@4.2.1: {}
7499
7500 eslint-visitor-keys@5.0.1: {}
7501
7313 - eslint@10.0.2(jiti@2.6.1):
7502 + eslint@10.0.3(jiti@2.6.1):
7503 dependencies:
7315 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7504 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@2.6.1))
7505 '@eslint-community/regexpp': 4.12.2
7317 - '@eslint/config-array': 0.23.2
7506 + '@eslint/config-array': 0.23.3
7507 '@eslint/config-helpers': 0.5.2
7319 - '@eslint/core': 1.1.0
7320 - '@eslint/plugin-kit': 0.6.0
7508 + '@eslint/core': 1.1.1
7509 + '@eslint/plugin-kit': 0.6.1
7510 '@humanfs/node': 0.16.7
7511 '@humanwhocodes/module-importer': 1.0.1
7512 '@humanwhocodes/retry': 0.4.3
@@ -7326,7 +7515,7 @@ snapshots:
7515 cross-spawn: 7.0.6
7516 debug: 4.4.3
7517 escape-string-regexp: 4.0.0
7329 - eslint-scope: 9.1.1
7518 + eslint-scope: 9.1.2
7519 eslint-visitor-keys: 5.0.1
7520 espree: 11.1.1
7521 esquery: 1.7.0
@@ -7339,7 +7528,7 @@ snapshots:
7528 imurmurhash: 0.1.4
7529 is-glob: 4.0.3
7530 json-stable-stringify-without-jsonify: 1.0.1
7342 - minimatch: 10.2.1
7531 + minimatch: 10.2.4
7532 natural-compare: 1.4.0
7533 optionator: 0.9.4
7534 optionalDependencies:
@@ -7439,11 +7628,14 @@ snapshots:
7628
7629 fast-levenshtein@2.0.6: {}
7630
7442 - fast-xml-builder@1.0.0: {}
7631 + fast-xml-builder@1.1.4:
7632 + dependencies:
7633 + path-expression-matcher: 1.1.3
7634
7444 - fast-xml-parser@5.4.2:
7635 + fast-xml-parser@5.5.6:
7636 dependencies:
7446 - fast-xml-builder: 1.0.0
7637 + fast-xml-builder: 1.1.4
7638 + path-expression-matcher: 1.1.3
7639 strnum: 2.1.2
7640
7641 fastq@1.20.1:
@@ -7660,7 +7852,7 @@ snapshots:
7852
7853 html-encoding-sniffer@6.0.0:
7854 dependencies:
7663 - '@exodus/bytes': 1.14.1
7855 + '@exodus/bytes': 1.15.0
7856 transitivePeerDependencies:
7857 - '@noble/hashes'
7858
@@ -7668,20 +7860,6 @@ snapshots:
7860
7861 html-void-elements@3.0.0: {}
7862
7671 - http-proxy-agent@7.0.2:
7672 - dependencies:
7673 - agent-base: 7.1.4
7674 - debug: 4.4.3
7675 - transitivePeerDependencies:
7676 - - supports-color
7677 -
7678 - https-proxy-agent@7.0.6:
7679 - dependencies:
7680 - agent-base: 7.1.4
7681 - debug: 4.4.3
7682 - transitivePeerDependencies:
7683 - - supports-color
7684 -
7863 human-signals@1.1.1: {}
7864
7865 human-signals@2.1.0: {}
@@ -7690,7 +7868,7 @@ snapshots:
7868
7869 ignore@7.0.5: {}
7870
7693 - immutable@5.1.4: {}
7871 + immutable@5.1.5: {}
7872
7873 import-fresh@3.3.1:
7874 dependencies:
@@ -7796,7 +7974,7 @@ snapshots:
7974 '@hapi/topo': 6.0.2
7975 '@standard-schema/spec': 1.1.0
7976
7799 - jose@6.2.0: {}
7977 + jose@6.2.2: {}
7978
7979 js-beautify@1.15.4:
7980 dependencies:
@@ -7840,32 +8018,31 @@ snapshots:
8018
8019 jsdoc-type-pratt-parser@7.1.1: {}
8020
7843 - jsdom@28.1.0:
8021 + jsdom@29.0.0:
8022 dependencies:
7845 - '@acemir/cssom': 0.9.31
7846 - '@asamuzakjp/dom-selector': 6.8.1
8023 + '@asamuzakjp/css-color': 5.0.1
8024 + '@asamuzakjp/dom-selector': 7.0.3
8025 '@bramus/specificity': 2.4.2
7848 - '@exodus/bytes': 1.14.1
7849 - cssstyle: 6.0.1
8026 + '@csstools/css-syntax-patches-for-csstree': 1.1.1(css-tree@3.2.1)
8027 + '@exodus/bytes': 1.15.0
8028 + css-tree: 3.2.1
8029 data-urls: 7.0.0
8030 decimal.js: 10.6.0
8031 html-encoding-sniffer: 6.0.0
7853 - http-proxy-agent: 7.0.2
7854 - https-proxy-agent: 7.0.6
8032 is-potential-custom-element-name: 1.0.1
8033 + lru-cache: 11.2.7
8034 parse5: 8.0.0
8035 saxes: 6.0.0
8036 symbol-tree: 3.2.4
7859 - tough-cookie: 6.0.0
7860 - undici: 7.22.0
8037 + tough-cookie: 6.0.1
8038 + undici: 7.24.4
8039 w3c-xmlserializer: 5.0.0
8040 webidl-conversions: 8.0.1
8041 whatwg-mimetype: 5.0.0
7864 - whatwg-url: 16.0.0
8042 + whatwg-url: 16.0.1
8043 xml-name-validator: 5.0.0
8044 transitivePeerDependencies:
8045 - '@noble/hashes'
7868 - - supports-color
8046
8047 jsesc@3.1.0: {}
8048
@@ -7917,54 +8094,54 @@ snapshots:
8094 prelude-ls: 1.2.1
8095 type-check: 0.4.0
8096
7920 - lightningcss-android-arm64@1.31.1:
8097 + lightningcss-android-arm64@1.32.0:
8098 optional: true
8099
7923 - lightningcss-darwin-arm64@1.31.1:
8100 + lightningcss-darwin-arm64@1.32.0:
8101 optional: true
8102
7926 - lightningcss-darwin-x64@1.31.1:
8103 + lightningcss-darwin-x64@1.32.0:
8104 optional: true
8105
7929 - lightningcss-freebsd-x64@1.31.1:
8106 + lightningcss-freebsd-x64@1.32.0:
8107 optional: true
8108
7932 - lightningcss-linux-arm-gnueabihf@1.31.1:
8109 + lightningcss-linux-arm-gnueabihf@1.32.0:
8110 optional: true
8111
7935 - lightningcss-linux-arm64-gnu@1.31.1:
8112 + lightningcss-linux-arm64-gnu@1.32.0:
8113 optional: true
8114
7938 - lightningcss-linux-arm64-musl@1.31.1:
8115 + lightningcss-linux-arm64-musl@1.32.0:
8116 optional: true
8117
7941 - lightningcss-linux-x64-gnu@1.31.1:
8118 + lightningcss-linux-x64-gnu@1.32.0:
8119 optional: true
8120
7944 - lightningcss-linux-x64-musl@1.31.1:
8121 + lightningcss-linux-x64-musl@1.32.0:
8122 optional: true
8123
7947 - lightningcss-win32-arm64-msvc@1.31.1:
8124 + lightningcss-win32-arm64-msvc@1.32.0:
8125 optional: true
8126
7950 - lightningcss-win32-x64-msvc@1.31.1:
8127 + lightningcss-win32-x64-msvc@1.32.0:
8128 optional: true
8129
7953 - lightningcss@1.31.1:
8130 + lightningcss@1.32.0:
8131 dependencies:
8132 detect-libc: 2.1.2
8133 optionalDependencies:
7957 - lightningcss-android-arm64: 1.31.1
7958 - lightningcss-darwin-arm64: 1.31.1
7959 - lightningcss-darwin-x64: 1.31.1
7960 - lightningcss-freebsd-x64: 1.31.1
7961 - lightningcss-linux-arm-gnueabihf: 1.31.1
7962 - lightningcss-linux-arm64-gnu: 1.31.1
7963 - lightningcss-linux-arm64-musl: 1.31.1
7964 - lightningcss-linux-x64-gnu: 1.31.1
7965 - lightningcss-linux-x64-musl: 1.31.1
7966 - lightningcss-win32-arm64-msvc: 1.31.1
7967 - lightningcss-win32-x64-msvc: 1.31.1
8134 + lightningcss-android-arm64: 1.32.0
8135 + lightningcss-darwin-arm64: 1.32.0
8136 + lightningcss-darwin-x64: 1.32.0
8137 + lightningcss-freebsd-x64: 1.32.0
8138 + lightningcss-linux-arm-gnueabihf: 1.32.0
8139 + lightningcss-linux-arm64-gnu: 1.32.0
8140 + lightningcss-linux-arm64-musl: 1.32.0
8141 + lightningcss-linux-x64-gnu: 1.32.0
8142 + lightningcss-linux-x64-musl: 1.32.0
8143 + lightningcss-win32-arm64-msvc: 1.32.0
8144 + lightningcss-win32-x64-msvc: 1.32.0
8145
8146 lines-and-columns@1.2.4: {}
8147
@@ -7994,7 +8171,7 @@ snapshots:
8171
8172 lru-cache@10.4.3: {}
8173
7997 - lru-cache@11.2.6: {}
8174 + lru-cache@11.2.7: {}
8175
8176 lru-cache@5.1.1:
8177 dependencies:
@@ -8158,7 +8335,7 @@ snapshots:
8335
8336 mdn-data@2.0.30: {}
8337
8161 - mdn-data@2.12.2: {}
8338 + mdn-data@2.27.1: {}
8339
8340 mdurl@2.0.0: {}
8341
@@ -8381,10 +8558,6 @@ snapshots:
8558
8559 mimic-function@5.0.1: {}
8560
8384 - minimatch@10.2.1:
8385 - dependencies:
8386 - brace-expansion: 5.0.2
8387 -
8561 minimatch@10.2.4:
8562 dependencies:
8563 brace-expansion: 5.0.2
@@ -8434,11 +8607,10 @@ snapshots:
8607 arrify: 2.0.1
8608 minimatch: 3.1.2
8609
8437 - naive-ui@2.43.2(vue@3.5.29(typescript@5.9.3)):
8610 + naive-ui@2.44.1(vue@3.5.30(typescript@5.9.3)):
8611 dependencies:
8612 '@css-render/plugin-bem': 0.15.14(css-render@0.15.14)
8440 - '@css-render/vue3-ssr': 0.15.14(vue@3.5.29(typescript@5.9.3))
8441 - '@types/katex': 0.16.8
8613 + '@css-render/vue3-ssr': 0.15.14(vue@3.5.30(typescript@5.9.3))
8614 '@types/lodash': 4.17.24
8615 '@types/lodash-es': 4.17.12
8616 async-validator: 4.2.5
@@ -8452,14 +8624,14 @@ snapshots:
8624 lodash-es: 4.17.23
8625 seemly: 0.3.10
8626 treemate: 0.3.11
8455 - vdirs: 0.1.8(vue@3.5.29(typescript@5.9.3))
8456 - vooks: 0.2.12(vue@3.5.29(typescript@5.9.3))
8457 - vue: 3.5.29(typescript@5.9.3)
8458 - vueuc: 0.4.65(vue@3.5.29(typescript@5.9.3))
8627 + vdirs: 0.1.8(vue@3.5.30(typescript@5.9.3))
8628 + vooks: 0.2.12(vue@3.5.30(typescript@5.9.3))
8629 + vue: 3.5.30(typescript@5.9.3)
8630 + vueuc: 0.4.65(vue@3.5.30(typescript@5.9.3))
8631
8632 nanoid@3.3.11: {}
8633
8462 - nanoid@5.1.6: {}
8634 + nanoid@5.1.7: {}
8635
8636 natural-compare@1.4.0: {}
8637
@@ -8612,6 +8784,8 @@ snapshots:
8784
8785 path-exists@4.0.0: {}
8786
8787 + path-expression-matcher@1.1.3: {}
8788 +
8789 path-key@3.1.1: {}
8790
8791 path-parse@1.0.7: {}
@@ -8641,17 +8815,17 @@ snapshots:
8815
8816 pidtree@0.6.0: {}
8817
8644 - pinia-plugin-persistedstate@4.7.1(@nuxt/kit@3.21.1)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))):
8818 + pinia-plugin-persistedstate@4.7.1(@nuxt/kit@3.21.1)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))):
8819 dependencies:
8820 defu: 6.1.4
8821 optionalDependencies:
8822 '@nuxt/kit': 3.21.1
8649 - pinia: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
8823 + pinia: 3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))
8824
8651 - pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)):
8825 + pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)):
8826 dependencies:
8827 '@vue/devtools-api': 7.7.9
8654 - vue: 3.5.29(typescript@5.9.3)
8828 + vue: 3.5.30(typescript@5.9.3)
8829 optionalDependencies:
8830 typescript: 5.9.3
8831
@@ -8695,6 +8869,12 @@ snapshots:
8869 picocolors: 1.1.1
8870 source-map-js: 1.2.1
8871
8872 + postcss@8.5.8:
8873 + dependencies:
8874 + nanoid: 3.3.11
8875 + picocolors: 1.1.1
8876 + source-map-js: 1.2.1
8877 +
8878 prelude-ls@1.2.1: {}
8879
8880 prettier-plugin-tailwindcss@0.7.2(prettier@3.8.1):
@@ -8937,10 +9117,10 @@ snapshots:
9117 dependencies:
9118 tslib: 2.8.1
9119
8940 - sass@1.97.3:
9120 + sass@1.98.0:
9121 dependencies:
9122 chokidar: 4.0.3
8943 - immutable: 5.1.4
9123 + immutable: 5.1.5
9124 source-map-js: 1.2.1
9125 optionalDependencies:
9126 '@parcel/watcher': 2.5.6
@@ -8980,14 +9160,14 @@ snapshots:
9160
9161 shell-quote@1.8.3: {}
9162
8983 - shiki@4.0.1:
9163 + shiki@4.0.2:
9164 dependencies:
8985 - '@shikijs/core': 4.0.1
8986 - '@shikijs/engine-javascript': 4.0.1
8987 - '@shikijs/engine-oniguruma': 4.0.1
8988 - '@shikijs/langs': 4.0.1
8989 - '@shikijs/themes': 4.0.1
8990 - '@shikijs/types': 4.0.1
9165 + '@shikijs/core': 4.0.2
9166 + '@shikijs/engine-javascript': 4.0.2
9167 + '@shikijs/engine-oniguruma': 4.0.2
9168 + '@shikijs/langs': 4.0.2
9169 + '@shikijs/themes': 4.0.2
9170 + '@shikijs/types': 4.0.2
9171 '@shikijs/vscode-textmate': 10.0.2
9172 '@types/hast': 3.0.4
9173
@@ -9047,7 +9227,7 @@ snapshots:
9227 transitivePeerDependencies:
9228 - supports-color
9229
9050 - std-env@3.10.0: {}
9230 + std-env@4.0.0: {}
9231
9232 stream-combiner@0.0.4:
9233 dependencies:
@@ -9115,7 +9295,7 @@ snapshots:
9295
9296 tagged-tag@1.0.0: {}
9297
9118 - tailwindcss@4.2.1: {}
9298 + tailwindcss@4.2.2: {}
9299
9300 tapable@2.3.0: {}
9301
@@ -9135,11 +9315,11 @@ snapshots:
9315 unconfig: 7.5.0
9316 yaml: 2.8.2
9317
9138 - thememirror@2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16):
9318 + thememirror@2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0):
9319 dependencies:
9320 '@codemirror/language': 6.12.1
9141 - '@codemirror/state': 6.5.4
9142 - '@codemirror/view': 6.39.16
9321 + '@codemirror/state': 6.6.0
9322 + '@codemirror/view': 6.40.0
9323
9324 through@2.3.8: {}
9325
@@ -9179,7 +9359,7 @@ snapshots:
9359
9360 totalist@3.0.1: {}
9361
9182 - tough-cookie@6.0.0:
9362 + tough-cookie@6.0.1:
9363 dependencies:
9364 tldts: 7.0.23
9365
@@ -9235,7 +9415,7 @@ snapshots:
9415
9416 unctx@2.5.0:
9417 dependencies:
9238 - acorn: 8.15.0
9418 + acorn: 8.16.0
9419 estree-walker: 3.0.3
9420 magic-string: 0.30.21
9421 unplugin: 2.3.11
@@ -9245,7 +9425,7 @@ snapshots:
9425
9426 undici-types@7.22.0: {}
9427
9248 - undici@7.22.0: {}
9428 + undici@7.24.4: {}
9429
9430 unist-util-is@6.0.1:
9431 dependencies:
@@ -9280,7 +9460,7 @@ snapshots:
9460 unplugin@2.3.11:
9461 dependencies:
9462 '@jridgewell/remapping': 2.3.5
9283 - acorn: 8.15.0
9463 + acorn: 8.16.0
9464 picomatch: 4.0.3
9465 webpack-virtual-modules: 0.6.2
9466 optional: true
@@ -9314,10 +9494,10 @@ snapshots:
9494
9495 validator@13.15.26: {}
9496
9317 - vdirs@0.1.8(vue@3.5.29(typescript@5.9.3)):
9497 + vdirs@0.1.8(vue@3.5.30(typescript@5.9.3)):
9498 dependencies:
9499 evtd: 0.2.4
9320 - vue: 3.5.29(typescript@5.9.3)
9500 + vue: 3.5.30(typescript@5.9.3)
9501
9502 vfile-message@4.0.3:
9503 dependencies:
@@ -9340,17 +9520,17 @@ snapshots:
9520 - rollup
9521 - supports-color
9522
9343 - vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)):
9523 + vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)):
9524 dependencies:
9525 birpc: 2.9.0
9346 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
9347 - vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
9526 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
9527 + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
9528
9349 - vite-hot-client@2.1.0(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)):
9529 + vite-hot-client@2.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)):
9530 dependencies:
9351 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
9531 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
9532
9353 - vite-plugin-inspect@11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)):
9533 + vite-plugin-inspect@11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)):
9534 dependencies:
9535 ansis: 4.2.0
9536 debug: 4.4.3
@@ -9360,28 +9540,28 @@ snapshots:
9540 perfect-debounce: 2.1.0
9541 sirv: 3.0.2
9542 unplugin-utils: 0.3.1
9363 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
9364 - vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
9543 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
9544 + vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
9545 optionalDependencies:
9546 '@nuxt/kit': 3.21.1
9547 transitivePeerDependencies:
9548 - supports-color
9549
9370 - vite-plugin-vue-devtools@8.0.7(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)):
9550 + vite-plugin-vue-devtools@8.1.0(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)):
9551 dependencies:
9372 - '@vue/devtools-core': 8.0.7(vue@3.5.29(typescript@5.9.3))
9373 - '@vue/devtools-kit': 8.0.7
9374 - '@vue/devtools-shared': 8.0.7
9552 + '@vue/devtools-core': 8.1.0(vue@3.5.30(typescript@5.9.3))
9553 + '@vue/devtools-kit': 8.1.0
9554 + '@vue/devtools-shared': 8.1.0
9555 sirv: 3.0.2
9376 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
9377 - vite-plugin-inspect: 11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
9378 - vite-plugin-vue-inspector: 5.3.2(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
9556 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
9557 + vite-plugin-inspect: 11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
9558 + vite-plugin-vue-inspector: 5.3.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
9559 transitivePeerDependencies:
9560 - '@nuxt/kit'
9561 - supports-color
9562 - vue
9563
9384 - vite-plugin-vue-inspector@5.3.2(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)):
9564 + vite-plugin-vue-inspector@5.3.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)):
9565 dependencies:
9566 '@babel/core': 7.29.0
9567 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0)
@@ -9389,22 +9569,22 @@ snapshots:
9569 '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
9570 '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
9571 '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0)
9392 - '@vue/compiler-dom': 3.5.28
9572 + '@vue/compiler-dom': 3.5.29
9573 kolorist: 1.8.0
9574 magic-string: 0.30.21
9395 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
9575 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
9576 transitivePeerDependencies:
9577 - supports-color
9578
9399 - vite-svg-loader@5.1.1(vue@3.5.29(typescript@5.9.3)):
9579 + vite-svg-loader@5.1.1(vue@3.5.30(typescript@5.9.3)):
9580 dependencies:
9581 debug: 4.4.3
9582 svgo: 3.3.3
9403 - vue: 3.5.29(typescript@5.9.3)
9583 + vue: 3.5.30(typescript@5.9.3)
9584 transitivePeerDependencies:
9585 - supports-color
9586
9407 - vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2):
9587 + vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2):
9588 dependencies:
9589 esbuild: 0.27.3
9590 fdir: 6.5.0(picomatch@4.0.3)
@@ -9413,82 +9593,72 @@ snapshots:
9593 rollup: 4.57.1
9594 tinyglobby: 0.2.15
9595 optionalDependencies:
9416 - '@types/node': 25.3.5
9596 + '@types/node': 25.5.0
9597 fsevents: 2.3.3
9598 jiti: 2.6.1
9419 - lightningcss: 1.31.1
9420 - sass: 1.97.3
9599 + lightningcss: 1.32.0
9600 + sass: 1.98.0
9601 yaml: 2.8.2
9602
9423 - vitest@4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2):
9603 + vitest@4.1.0(@types/node@25.5.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)):
9604 dependencies:
9425 - '@vitest/expect': 4.0.18
9426 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2))
9427 - '@vitest/pretty-format': 4.0.18
9428 - '@vitest/runner': 4.0.18
9429 - '@vitest/snapshot': 4.0.18
9430 - '@vitest/spy': 4.0.18
9431 - '@vitest/utils': 4.0.18
9432 - es-module-lexer: 1.7.0
9605 + '@vitest/expect': 4.1.0
9606 + '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2))
9607 + '@vitest/pretty-format': 4.1.0
9608 + '@vitest/runner': 4.1.0
9609 + '@vitest/snapshot': 4.1.0
9610 + '@vitest/spy': 4.1.0
9611 + '@vitest/utils': 4.1.0
9612 + es-module-lexer: 2.0.0
9613 expect-type: 1.3.0
9614 magic-string: 0.30.21
9615 obug: 2.1.1
9616 pathe: 2.0.3
9617 picomatch: 4.0.3
9438 - std-env: 3.10.0
9618 + std-env: 4.0.0
9619 tinybench: 2.9.0
9620 tinyexec: 1.0.2
9621 tinyglobby: 0.2.15
9622 tinyrainbow: 3.0.3
9443 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(yaml@2.8.2)
9623 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(yaml@2.8.2)
9624 why-is-node-running: 2.3.0
9625 optionalDependencies:
9446 - '@types/node': 25.3.5
9447 - jsdom: 28.1.0
9626 + '@types/node': 25.5.0
9627 + jsdom: 29.0.0
9628 transitivePeerDependencies:
9449 - - jiti
9450 - - less
9451 - - lightningcss
9629 - msw
9453 - - sass
9454 - - sass-embedded
9455 - - stylus
9456 - - sugarss
9457 - - terser
9458 - - tsx
9459 - - yaml
9630
9631 void-elements@3.1.0: {}
9632
9463 - vooks@0.2.12(vue@3.5.29(typescript@5.9.3)):
9633 + vooks@0.2.12(vue@3.5.30(typescript@5.9.3)):
9634 dependencies:
9635 evtd: 0.2.4
9466 - vue: 3.5.29(typescript@5.9.3)
9636 + vue: 3.5.30(typescript@5.9.3)
9637
9638 vscode-uri@3.1.0: {}
9639
9470 - vue-advanced-cropper@2.8.9(vue@3.5.29(typescript@5.9.3)):
9640 + vue-advanced-cropper@2.8.9(vue@3.5.30(typescript@5.9.3)):
9641 dependencies:
9642 classnames: 2.5.1
9643 debounce: 1.2.1
9644 easy-bem: 1.1.1
9475 - vue: 3.5.29(typescript@5.9.3)
9645 + vue: 3.5.30(typescript@5.9.3)
9646
9477 - vue-codemirror@6.1.1(codemirror@6.0.2)(vue@3.5.29(typescript@5.9.3)):
9647 + vue-codemirror@6.1.1(codemirror@6.0.2)(vue@3.5.30(typescript@5.9.3)):
9648 dependencies:
9479 - '@codemirror/commands': 6.10.2
9649 + '@codemirror/commands': 6.10.3
9650 '@codemirror/language': 6.12.1
9651 '@codemirror/state': 6.5.4
9482 - '@codemirror/view': 6.39.16
9652 + '@codemirror/view': 6.40.0
9653 codemirror: 6.0.2
9484 - vue: 3.5.29(typescript@5.9.3)
9654 + vue: 3.5.30(typescript@5.9.3)
9655
9656 vue-component-type-helpers@2.2.12: {}
9657
9488 - vue-eslint-parser@10.4.0(eslint@10.0.2(jiti@2.6.1)):
9658 + vue-eslint-parser@10.4.0(eslint@10.0.3(jiti@2.6.1)):
9659 dependencies:
9660 debug: 4.4.3
9491 - eslint: 10.0.2(jiti@2.6.1)
9661 + eslint: 10.0.3(jiti@2.6.1)
9662 eslint-scope: 9.1.1
9663 eslint-visitor-keys: 5.0.1
9664 espree: 11.1.1
@@ -9497,22 +9667,23 @@ snapshots:
9667 transitivePeerDependencies:
9668 - supports-color
9669
9500 - vue-highlight-words@3.0.1(vue@3.5.29(typescript@5.9.3)):
9670 + vue-highlight-words@3.0.1(vue@3.5.30(typescript@5.9.3)):
9671 dependencies:
9672 highlight-words-core: 1.2.3
9503 - vue: 3.5.29(typescript@5.9.3)
9673 + vue: 3.5.30(typescript@5.9.3)
9674
9505 - vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)):
9675 + vue-i18n@11.3.0(vue@3.5.30(typescript@5.9.3)):
9676 dependencies:
9507 - '@intlify/core-base': 11.2.8
9508 - '@intlify/shared': 11.2.8
9677 + '@intlify/core-base': 11.3.0
9678 + '@intlify/devtools-types': 11.3.0
9679 + '@intlify/shared': 11.3.0
9680 '@vue/devtools-api': 6.6.4
9510 - vue: 3.5.29(typescript@5.9.3)
9681 + vue: 3.5.30(typescript@5.9.3)
9682
9512 - vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)):
9683 + vue-router@5.0.3(@vue/compiler-sfc@3.5.30)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)):
9684 dependencies:
9685 '@babel/generator': 7.29.1
9515 - '@vue-macros/common': 3.1.2(vue@3.5.29(typescript@5.9.3))
9686 + '@vue-macros/common': 3.1.2(vue@3.5.30(typescript@5.9.3))
9687 '@vue/devtools-api': 8.0.6
9688 ast-walker-scope: 0.8.3
9689 chokidar: 5.0.0
@@ -9527,56 +9698,56 @@ snapshots:
9698 tinyglobby: 0.2.15
9699 unplugin: 3.0.0
9700 unplugin-utils: 0.3.1
9530 - vue: 3.5.29(typescript@5.9.3)
9701 + vue: 3.5.30(typescript@5.9.3)
9702 yaml: 2.8.2
9703 optionalDependencies:
9533 - '@vue/compiler-sfc': 3.5.29
9534 - pinia: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
9704 + '@vue/compiler-sfc': 3.5.30
9705 + pinia: 3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))
9706
9536 - vue-sjv@0.0.6(vue@3.5.29(typescript@5.9.3)):
9707 + vue-sjv@0.0.6(vue@3.5.30(typescript@5.9.3)):
9708 dependencies:
9538 - vue: 3.5.29(typescript@5.9.3)
9709 + vue: 3.5.30(typescript@5.9.3)
9710
9540 - vue-tsc@3.2.5(typescript@5.9.3):
9711 + vue-tsc@3.2.6(typescript@5.9.3):
9712 dependencies:
9713 '@volar/typescript': 2.4.28
9543 - '@vue/language-core': 3.2.5
9714 + '@vue/language-core': 3.2.6
9715 typescript: 5.9.3
9716
9546 - vue3-apexcharts@1.11.1(apexcharts@5.5.1)(vue@3.5.29(typescript@5.9.3)):
9717 + vue3-apexcharts@1.11.1(apexcharts@5.5.1)(vue@3.5.30(typescript@5.9.3)):
9718 dependencies:
9719 apexcharts: 5.5.1
9549 - vue: 3.5.29(typescript@5.9.3)
9720 + vue: 3.5.30(typescript@5.9.3)
9721
9551 - vue3-marquee@4.2.2(vue@3.5.29(typescript@5.9.3)):
9722 + vue3-marquee@4.2.2(vue@3.5.30(typescript@5.9.3)):
9723 dependencies:
9553 - vue: 3.5.29(typescript@5.9.3)
9724 + vue: 3.5.30(typescript@5.9.3)
9725
9555 - vue@3.5.29(typescript@5.9.3):
9726 + vue@3.5.30(typescript@5.9.3):
9727 dependencies:
9557 - '@vue/compiler-dom': 3.5.29
9558 - '@vue/compiler-sfc': 3.5.29
9559 - '@vue/runtime-dom': 3.5.29
9560 - '@vue/server-renderer': 3.5.29(vue@3.5.29(typescript@5.9.3))
9561 - '@vue/shared': 3.5.29
9728 + '@vue/compiler-dom': 3.5.30
9729 + '@vue/compiler-sfc': 3.5.30
9730 + '@vue/runtime-dom': 3.5.30
9731 + '@vue/server-renderer': 3.5.30(vue@3.5.30(typescript@5.9.3))
9732 + '@vue/shared': 3.5.30
9733 optionalDependencies:
9734 typescript: 5.9.3
9735
9565 - vuedraggable@4.1.0(vue@3.5.29(typescript@5.9.3)):
9736 + vuedraggable@4.1.0(vue@3.5.30(typescript@5.9.3)):
9737 dependencies:
9738 sortablejs: 1.14.0
9568 - vue: 3.5.29(typescript@5.9.3)
9739 + vue: 3.5.30(typescript@5.9.3)
9740
9570 - vueuc@0.4.65(vue@3.5.29(typescript@5.9.3)):
9741 + vueuc@0.4.65(vue@3.5.30(typescript@5.9.3)):
9742 dependencies:
9572 - '@css-render/vue3-ssr': 0.15.14(vue@3.5.29(typescript@5.9.3))
9743 + '@css-render/vue3-ssr': 0.15.14(vue@3.5.30(typescript@5.9.3))
9744 '@juggle/resize-observer': 3.4.0
9745 css-render: 0.15.14
9746 evtd: 0.2.4
9747 seemly: 0.3.10
9577 - vdirs: 0.1.8(vue@3.5.29(typescript@5.9.3))
9578 - vooks: 0.2.12(vue@3.5.29(typescript@5.9.3))
9579 - vue: 3.5.29(typescript@5.9.3)
9748 + vdirs: 0.1.8(vue@3.5.30(typescript@5.9.3))
9749 + vooks: 0.2.12(vue@3.5.30(typescript@5.9.3))
9750 + vue: 3.5.30(typescript@5.9.3)
9751
9752 w3c-keyname@2.2.8: {}
9753
@@ -9600,9 +9771,9 @@ snapshots:
9771
9772 whatwg-mimetype@5.0.0: {}
9773
9603 - whatwg-url@16.0.0:
9774 + whatwg-url@16.0.1:
9775 dependencies:
9605 - '@exodus/bytes': 1.14.1
9776 + '@exodus/bytes': 1.15.0
9777 tr46: 6.0.0
9778 webidl-conversions: 8.0.1
9779 transitivePeerDependencies:
@@ -9658,9 +9829,9 @@ snapshots:
9829
9830 xmlchars@2.2.0: {}
9831
9661 - xmllint-wasm@5.1.0(@types/node@25.3.5):
9832 + xmllint-wasm@5.1.0(@types/node@25.5.0):
9833 dependencies:
9663 - '@types/node': 25.3.5
9834 + '@types/node': 25.5.0
9835
9836 xmllint@0.1.1: {}
9837
frontend/src/api/endpoints/incidentManagement/cases.ts
+6 -3
@@ -119,15 +119,18 @@ export default {
119 deleteCase(caseId: number) {
120 return HttpClient.delete<FlaskBaseResponse>(`/incidents/db_operations/case/${caseId}`)
121 },
122 - exportCases(customerCode?: string) {
122 + exportCases(customerCode?: string, year?: number, month?: number) {
123 let url = `/incidents/report/generate-report-csv`
124
125 if (customerCode) {
126 url = `/incidents/report/generate-report-csv/${customerCode}`
127 }
128
129 - return HttpClient.post<Blob>(url, {
130 - responseType: "blob"
129 + return HttpClient.post<Blob>(url, null, {
130 + responseType: "blob",
131 + params: {
132 + ...(year !== undefined && month !== undefined ? { year, month } : {})
133 + }
134 })
135 },
136 getCaseDataStoreFiles(caseId: number) {
frontend/src/api/endpoints/sca.ts
+5 -5
@@ -1,15 +1,15 @@
1 import type {
2 ScaOverviewQuery,
3 ScaOverviewResponse,
4 + ScaPackageAgentsResponse,
5 + ScaPackageRegistryResponse,
6 + ScaPoliciesIndexResponse,
7 + ScaPolicyContentResponse,
8 SCAReportDeleteResponse,
9 SCAReportGenerateRequest,
10 SCAReportGenerateResponse,
11 SCAReportListResponse,
8 - ScaStatsResponse,
9 - ScaPoliciesIndexResponse,
10 - ScaPolicyContentResponse,
11 - ScaPackageRegistryResponse,
12 - ScaPackageAgentsResponse
12 + ScaStatsResponse
13 } from "@/types/sca.d"
14 import { HttpClient } from "../httpClient"
15 import { createSSEStream } from "../sseClient"
frontend/src/api/endpoints/siem.ts
+51 -3
@@ -1,6 +1,13 @@
1 -import type { FlaskBaseResponse } from "@/types/flask.d"
2 -import type { EventSource } from "@/types/eventSources.d"
1 +import type {
2 + DashboardCategory,
3 + DashboardCategoryWithTemplates,
4 + EnableDashboardPayload,
5 + EnabledDashboard,
6 + PanelDataResponse
7 +} from "@/types/dashboards.d"
8 import type { EventSearchResult, FieldMapping } from "@/types/events.d"
9 +import type { EventSource } from "@/types/eventSources.d"
10 +import type { FlaskBaseResponse } from "@/types/flask.d"
11 import { HttpClient } from "../httpClient"
12
13 export interface EventSourceCreatePayload {
@@ -41,7 +48,14 @@ export default {
48 queryEvents(
49 customerCode: string,
50 sourceName: string,
44 - params: { timerange?: string; page_size?: number; scroll_id?: string; query?: string }
51 + params: {
52 + timerange?: string
53 + page_size?: number
54 + scroll_id?: string
55 + query?: string
56 + time_from?: string
57 + time_to?: string
58 + }
59 ) {
60 return HttpClient.get<
61 FlaskBaseResponse & {
@@ -56,5 +70,39 @@ export default {
70 return HttpClient.get<FlaskBaseResponse & { fields: FieldMapping[]; total: number; index_pattern: string }>(
71 `/siem/events/${customerCode}/${sourceName}/fields`
72 )
73 + },
74 +
75 + // ── Dashboards ──────────────────────────────────────────────
76 + getDashboardCategories() {
77 + return HttpClient.get<FlaskBaseResponse & { categories: DashboardCategory[] }>(`/siem/dashboards/templates`)
78 + },
79 + getDashboardCategory(categoryId: string) {
80 + return HttpClient.get<FlaskBaseResponse & { category: DashboardCategoryWithTemplates }>(
81 + `/siem/dashboards/templates/${categoryId}`
82 + )
83 + },
84 + getEnabledDashboards(customerCode: string) {
85 + return HttpClient.get<FlaskBaseResponse & { enabled_dashboards: EnabledDashboard[] }>(
86 + `/siem/dashboards/enabled/${customerCode}`
87 + )
88 + },
89 + enableDashboard(payload: EnableDashboardPayload) {
90 + return HttpClient.post<FlaskBaseResponse & { enabled_dashboard: EnabledDashboard }>(
91 + `/siem/dashboards/enable`,
92 + payload
93 + )
94 + },
95 + disableDashboard(dashboardId: number) {
96 + return HttpClient.delete<FlaskBaseResponse>(`/siem/dashboards/disable/${dashboardId}`)
97 + },
98 + getPanelData(dashboardId: number, timerange: string, signal?: AbortSignal) {
99 + return HttpClient.post<PanelDataResponse>(
100 + `/siem/dashboards/panel-data`,
101 + {
102 + dashboard_id: dashboardId,
103 + timerange
104 + },
105 + signal ? { signal } : {}
106 + )
107 }
108 }
frontend/src/api/index.ts
+1 -1
@@ -27,8 +27,8 @@ import portainer from "./endpoints/portainer"
27 import reporting from "./endpoints/reporting"
28 import sca from "./endpoints/sca"
29 import scheduler from "./endpoints/scheduler"
30 -import siem from "./endpoints/siem"
30 import shuffle from "./endpoints/shuffle"
31 +import siem from "./endpoints/siem"
32 import sigma from "./endpoints/sigma"
33 import snapshots from "./endpoints/snapshots"
34 import soc from "./endpoints/soc"
frontend/src/app-layouts/common/Navbar/items.tsx
+14 -1
@@ -20,7 +20,7 @@ const ReportCreationIcon = "carbon:report-data"
20 const SchedulerIcon = "material-symbols:autoplay"
21 const CustomerPortalIcon = "streamline-ultimate:coding-apps-website-apps-browser"
22 const ToolsIcon = "carbon:tool-box"
23 -const EventSearchIcon = "carbon:search-locate"
23 +// const EventSearchIcon = "carbon:search-locate"
24
25 // TODO-FE: refactor
26 export default function getItems(): MenuMixedOption[] {
@@ -131,6 +131,19 @@ export default function getItems(): MenuMixedOption[] {
131 ),
132 key: "EventSearch"
133 },
134 + {
135 + label: () =>
136 + h(
137 + RouterLink,
138 + {
139 + to: {
140 + name: "Dashboards"
141 + }
142 + },
143 + { default: () => "Dashboards" }
144 + ),
145 + key: "Dashboards"
146 + },
147 {
148 label: () =>
149 h(
frontend/src/assets/scss/overrides/apexchart-override.scss
+4
@@ -6,6 +6,10 @@
6 color: #000;
7 }
8 }
9 +
10 + foreignObject {
11 + display: none;
12 + }
13 }
14
15 .apexcharts-tooltip {
frontend/src/components/activeResponse/ActiveResponseActions.vue
+2 -2
@@ -34,7 +34,7 @@
34 </template>
35
36 <script setup lang="ts">
37 -import type { Size } from "naive-ui/es/button/src/interface"
37 +import type { ButtonSize } from "naive-ui"
38 import type { SupportedActiveResponse } from "@/types/activeResponse.d"
39 import { NButton, NModal } from "naive-ui"
40 import { computed, ref, watch } from "vue"
@@ -44,7 +44,7 @@ import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
44 const { activeResponse, size, agentId } = defineProps<{
45 activeResponse: SupportedActiveResponse
46 agentId?: string | number
47 - size?: Size
47 + size?: ButtonSize
48 }>()
49
50 const emit = defineEmits<{
frontend/src/components/activeResponse/ActiveResponseWizardButton.vue
+3 -3
@@ -21,15 +21,15 @@
21 </template>
22
23 <script setup lang="ts">
24 -import type { Size, Type } from "naive-ui/es/button/src/interface"
24 +import type { ButtonSize, ButtonType } from "naive-ui"
25 import { NButton, NModal } from "naive-ui"
26 import { ref, watch } from "vue"
27 import Icon from "@/components/common/Icon.vue"
28 import ActiveResponseWizard from "./ActiveResponseWizard.vue"
29
30 defineProps<{
31 - size?: Size
32 - type?: Type
31 + size?: ButtonSize
32 + type?: ButtonType
33 secondary?: boolean
34 }>()
35
frontend/src/components/alerts/AlertActions.vue
+2 -2
@@ -86,7 +86,7 @@
86 </template>
87
88 <script setup lang="ts">
89 -import type { Size } from "naive-ui/es/button/src/interface"
89 +import type { ButtonSize } from "naive-ui"
90 import type { SocAlertField } from "./type.d"
91 import type { Alert, WazuhRuleExclude } from "@/types/alerts.d"
92 import { NButton, NInput, NModal, useMessage } from "naive-ui"
@@ -98,7 +98,7 @@ import AlertWazuhRules from "./AlertWazuhRules.vue"
98
99 const { alert, size, socAlertField } = defineProps<{
100 alert: Alert
101 - size?: Size
101 + size?: ButtonSize
102 socAlertField: SocAlertField
103 }>()
104
frontend/src/components/artifacts/ArtifactRecommendation.vue
+2 -2
@@ -71,7 +71,7 @@
71 </template>
72
73 <script setup lang="ts">
74 -import type { Size } from "naive-ui/es/button/src/interface"
74 +import type { ButtonSize } from "naive-ui"
75 import type { Recommendation } from "@/types/artifacts.d"
76 import type { OsTypesFull } from "@/types/common.d"
77 import _uniqBy from "lodash/uniqBy"
@@ -87,7 +87,7 @@ interface RecommendationStore {
87
88 const { context, size } = defineProps<{
89 context: string | object
90 - size?: Size
90 + size?: ButtonSize
91 }>()
92
93 const AiIcon = "mage:stars-c"
frontend/src/components/cloudSecurityAssessment/CloudSecurityAssessmentButton.vue
+3 -3
@@ -8,14 +8,14 @@
8 </template>
9
10 <script setup lang="ts">
11 -import type { Size, Type } from "naive-ui/es/button/src/interface"
11 +import type { ButtonSize, ButtonType } from "naive-ui"
12 import { NButton } from "naive-ui"
13 import { useRouter } from "vue-router"
14 import Icon from "@/components/common/Icon.vue"
15
16 defineProps<{
17 - size?: Size
18 - type?: Type
17 + size?: ButtonSize
18 + type?: ButtonType
19 secondary?: boolean
20 }>()
21
frontend/src/components/common/cards/CardEntity.vue
+2 -2
@@ -15,8 +15,8 @@
15 content-class="h-full grow"
16 class="flex h-full flex-col"
17 >
18 - <div class="card-entity-wrapper flex flex-col" :class="cardEntityWrapperClass">
19 - <div class="main-box flex flex-col" :class="mainBoxClass">
18 + <div class="card-entity-wrapper flex h-full flex-col" :class="cardEntityWrapperClass">
19 + <div class="main-box flex h-full flex-col" :class="mainBoxClass">
20 <div v-if="$slots.header" class="header-box" :class="headerBoxClass">
21 <slot name="header" />
22 </div>
frontend/src/components/common/cards/CardLink.vue new
+40
@@ -0,0 +1,40 @@
1 +<template>
2 + <n-card size="small" :class="{ 'group cursor-pointer': clickable }" :hoverable>
3 + <div class="flex flex-col gap-2">
4 + <div class="flex items-center justify-between gap-2">
5 + <span
6 + class="text-secondary text-sm transition-colors"
7 + :class="{ 'group-hover:text-primary': clickable }"
8 + >
9 + <slot name="title">{{ title }}</slot>
10 + </span>
11 + <Icon
12 + v-if="clickable"
13 + name="carbon:arrow-up-right"
14 + class="group-hover:text-primary transition-colors"
15 + />
16 + <slot name="header-extra" />
17 + </div>
18 + <slot />
19 + <div class="font-mono text-xl font-semibold">
20 + <slot name="value">{{ value }}</slot>
21 + </div>
22 + <div v-if="subtitle || $slots.subtitle" class="text-secondary text-xs">
23 + <slot name="subtitle">{{ subtitle }}</slot>
24 + </div>
25 + </div>
26 + </n-card>
27 +</template>
28 +
29 +<script setup lang="ts">
30 +import { NCard } from "naive-ui"
31 +import Icon from "@/components/common/Icon.vue"
32 +
33 +defineProps<{
34 + title?: string | number
35 + value?: string | number
36 + subtitle?: string | number
37 + hoverable?: boolean
38 + clickable?: boolean
39 +}>()
40 +</script>
frontend/src/components/common/charts/ChartBar.vue new
+145
@@ -0,0 +1,145 @@
1 +<template>
2 + <div :style="{ height, width: '100%' }">
3 + <apexchart width="100%" :height :options="chartOptions" :series="chartSeries" />
4 + </div>
5 +</template>
6 +
7 +<script setup lang="ts">
8 +import type { ApexOptions } from "apexcharts"
9 +import { computed } from "vue"
10 +import apexchart from "vue3-apexcharts"
11 +import { useThemeStore } from "@/stores/theme"
12 +import { DASHBOARD_CHART_COLORS } from "./chartColors"
13 +import "@/assets/scss/overrides/apexchart-override.scss"
14 +
15 +const props = withDefaults(
16 + defineProps<{
17 + labels?: string[]
18 + data?: number[]
19 + height?: string
20 + monochrome?: boolean
21 + }>(),
22 + {
23 + labels: () => [],
24 + data: () => [],
25 + height: "100%"
26 + }
27 +)
28 +
29 +const emit = defineEmits<{
30 + itemClick: [item: { name: string }]
31 +}>()
32 +
33 +const themeStore = useThemeStore()
34 +
35 +const chartSeries = computed(() => [
36 + {
37 + name: "value",
38 + data: props.labels.map((_, i) => Number(props.data[i] ?? 0))
39 + }
40 +])
41 +
42 +const chartOptions = computed<ApexOptions>(() => {
43 + const style = themeStore.style
44 + const fg = style["fg-default-color"]
45 + const bc = style["border-color"]
46 + const ff = style["font-family"]
47 +
48 + return {
49 + chart: {
50 + type: "bar",
51 + fontFamily: ff,
52 + toolbar: { show: false },
53 + animations: { enabled: true },
54 + events: {
55 + dataPointSelection(_event, _chartContext, config) {
56 + const idx = config.dataPointIndex
57 + const name = props.labels[idx]
58 + if (name) emit("itemClick", { name })
59 + }
60 + }
61 + },
62 + responsive: [
63 + {
64 + breakpoint: 1080,
65 + options: {
66 + yaxis: {
67 + labels: {
68 + style: { colors: fg, fontSize: "10px" },
69 + maxWidth: 100
70 + }
71 + }
72 + }
73 + }
74 + ],
75 + colors: props.monochrome ? [DASHBOARD_CHART_COLORS[0]] : DASHBOARD_CHART_COLORS,
76 + states: {
77 + active: {
78 + allowMultipleDataPointsSelection: false,
79 + filter: {
80 + type: "none"
81 + }
82 + }
83 + },
84 + plotOptions: {
85 + bar: {
86 + expandOnClick: false,
87 + horizontal: true,
88 + distributed: true,
89 + borderRadius: 4,
90 + barHeight: "60%",
91 + dataLabels: {
92 + position: "top"
93 + }
94 + }
95 + },
96 + dataLabels: {
97 + enabled: true,
98 + textAnchor: "start",
99 + offsetX: 5,
100 + style: {
101 + fontSize: "12px",
102 + colors: ["#fff"]
103 + }
104 + },
105 + stroke: { show: false },
106 + xaxis: {
107 + categories: [...props.labels],
108 + labels: {
109 + style: { colors: fg, fontSize: "10px" }
110 + },
111 + axisBorder: { show: false },
112 + axisTicks: { show: false }
113 + },
114 + yaxis: {
115 + labels: {
116 + style: { colors: fg, fontSize: "11px" },
117 + maxWidth: 200,
118 + trim: true
119 + }
120 + },
121 + grid: {
122 + borderColor: bc,
123 + strokeDashArray: 0,
124 + xaxis: { lines: { show: true } },
125 + yaxis: { lines: { show: false } }
126 + },
127 + legend: { show: false },
128 + tooltip: {
129 + theme: themeStore.isThemeDark ? "dark" : "light",
130 + y: {
131 + formatter: (val: number) => String(val)
132 + }
133 + },
134 + noData: {
135 + text: "No data",
136 + align: "center",
137 + style: {
138 + color: fg,
139 + fontSize: "16px",
140 + fontFamily: ff
141 + }
142 + }
143 + }
144 +})
145 +</script>
frontend/src/components/common/charts/ChartColumn.vue new
+144
@@ -0,0 +1,144 @@
1 +<template>
2 + <div :style="{ height, width: '100%' }">
3 + <apexchart width="100%" :height :options="chartOptions" :series="chartSeries" />
4 + </div>
5 +</template>
6 +
7 +<script setup lang="ts">
8 +import type { ApexOptions } from "apexcharts"
9 +import { computed } from "vue"
10 +import apexchart from "vue3-apexcharts"
11 +import { useSettingsStore } from "@/stores/settings"
12 +import { useThemeStore } from "@/stores/theme"
13 +import dayjs from "@/utils/dayjs"
14 +import { DASHBOARD_CHART_COLORS } from "./chartColors"
15 +import "@/assets/scss/overrides/apexchart-override.scss"
16 +
17 +const props = withDefaults(
18 + defineProps<{
19 + labels?: string[]
20 + data?: number[]
21 + height?: string
22 + monochrome?: boolean
23 + labelsDatetime?: boolean
24 + }>(),
25 + {
26 + labels: () => [],
27 + data: () => [],
28 + height: "100%"
29 + }
30 +)
31 +
32 +const emit = defineEmits<{
33 + itemClick: [item: { name: string }]
34 +}>()
35 +
36 +const themeStore = useThemeStore()
37 +const dFormats = useSettingsStore().dateFormat
38 +
39 +const chartSeries = computed(() => [
40 + {
41 + name: "value",
42 + data: props.labels.map((_, i) => Number(props.data[i] ?? 0))
43 + }
44 +])
45 +
46 +const chartOptions = computed<ApexOptions>(() => {
47 + const style = themeStore.style
48 + const fg = style["fg-default-color"]
49 + const bc = style["border-color"]
50 + const ff = style["font-family"]
51 +
52 + return {
53 + chart: {
54 + type: "bar",
55 + fontFamily: ff,
56 + toolbar: { show: false },
57 + animations: { enabled: true },
58 + events: {
59 + dataPointSelection(_event, _chartContext, config) {
60 + const idx = config.dataPointIndex
61 + const name = props.labels[idx]
62 + if (name) emit("itemClick", { name })
63 + }
64 + }
65 + },
66 + responsive: [
67 + {
68 + breakpoint: 1200,
69 + options: {
70 + xaxis: {
71 + labels: {
72 + show: false
73 + }
74 + }
75 + }
76 + }
77 + ],
78 + colors: props.monochrome ? [DASHBOARD_CHART_COLORS[0]] : DASHBOARD_CHART_COLORS,
79 + states: {
80 + active: {
81 + allowMultipleDataPointsSelection: false,
82 + filter: {
83 + type: "none"
84 + }
85 + }
86 + },
87 + plotOptions: {
88 + bar: {
89 + expandOnClick: false,
90 + horizontal: false,
91 + distributed: true,
92 + borderRadius: 4,
93 + borderRadiusApplication: "end",
94 + columnWidth: "60%"
95 + }
96 + },
97 + dataLabels: { enabled: false },
98 + stroke: { show: false },
99 + xaxis: {
100 + categories: Array.from(props.labels, label => {
101 + return props.labelsDatetime
102 + ? [dayjs(label).format(dFormats.date), dayjs(label).format(dFormats.time)]
103 + : label
104 + }),
105 + labels: {
106 + style: { colors: fg, fontSize: "9px" },
107 + rotate: -65,
108 + rotateAlways: true,
109 + hideOverlappingLabels: true,
110 + trim: false
111 + },
112 + axisBorder: { show: false },
113 + axisTicks: { show: false }
114 + },
115 + yaxis: {
116 + labels: {
117 + style: { colors: fg, fontSize: "10px" }
118 + }
119 + },
120 + grid: {
121 + borderColor: bc,
122 + strokeDashArray: 0,
123 + xaxis: { lines: { show: false } },
124 + yaxis: { lines: { show: true } }
125 + },
126 + legend: { show: false },
127 + tooltip: {
128 + theme: themeStore.isThemeDark ? "dark" : "light",
129 + y: {
130 + formatter: (val: number) => String(val)
131 + }
132 + },
133 + noData: {
134 + text: "No data",
135 + align: "center",
136 + style: {
137 + color: fg,
138 + fontSize: "16px",
139 + fontFamily: ff
140 + }
141 + }
142 + }
143 +})
144 +</script>
frontend/src/components/common/charts/ChartPie.vue new
+121
@@ -0,0 +1,121 @@
1 +<template>
2 + <div :style="{ height, width: '100%' }">
3 + <apexchart width="100%" :height :options="chartOptions" :series="chartSeries" />
4 + </div>
5 +</template>
6 +
7 +<script setup lang="ts">
8 +import type { ApexOptions } from "apexcharts"
9 +import { computed } from "vue"
10 +import apexchart from "vue3-apexcharts"
11 +import { useThemeStore } from "@/stores/theme"
12 +import { DASHBOARD_CHART_COLORS } from "./chartColors"
13 +import "@/assets/scss/overrides/apexchart-override.scss"
14 +
15 +const props = withDefaults(
16 + defineProps<{
17 + labels?: string[]
18 + data?: number[]
19 + height?: string
20 + monochrome?: boolean
21 + }>(),
22 + {
23 + labels: () => [],
24 + data: () => [],
25 + height: "100%"
26 + }
27 +)
28 +
29 +const emit = defineEmits<{
30 + itemClick: [item: { name: string }]
31 +}>()
32 +
33 +const themeStore = useThemeStore()
34 +
35 +const chartSeries = computed<number[]>(() => {
36 + if (!props.labels.length) return []
37 + return props.labels.map((_, i) => Number(props.data[i] ?? 0))
38 +})
39 +
40 +const chartOptions = computed<ApexOptions>(() => {
41 + const style = themeStore.style
42 + const fg = style["fg-default-color"]
43 + const ff = style["font-family"]
44 +
45 + return {
46 + chart: {
47 + type: "donut",
48 + fontFamily: ff,
49 + toolbar: { show: false },
50 + animations: { enabled: true },
51 + events: {
52 + dataPointSelection(_event, _chartContext, config) {
53 + const idx = config.dataPointIndex
54 + const name = props.labels[idx]
55 + if (name) emit("itemClick", { name })
56 + }
57 + }
58 + },
59 + labels: [...props.labels],
60 + colors: props.monochrome ? [DASHBOARD_CHART_COLORS[0]] : DASHBOARD_CHART_COLORS,
61 + states: {
62 + active: {
63 + allowMultipleDataPointsSelection: false,
64 + filter: {
65 + type: "none"
66 + }
67 + }
68 + },
69 + plotOptions: {
70 + pie: {
71 + expandOnClick: false,
72 + donut: {
73 + size: "55%",
74 + labels: {
75 + show: true,
76 + value: { color: fg }
77 + }
78 + }
79 + }
80 + },
81 + dataLabels: {
82 + enabled: true
83 + },
84 + stroke: {
85 + show: false
86 + },
87 + legend: {
88 + show: true,
89 + position: "right",
90 + fontSize: "11px",
91 + fontFamily: ff,
92 + labels: { colors: fg },
93 + itemMargin: { vertical: 6, horizontal: 4 },
94 + markers: {
95 + size: 7,
96 + strokeWidth: 0,
97 + offsetX: -5
98 + },
99 + formatter: (seriesName, opts) => {
100 + const series = opts.w.globals.series as number[]
101 + const value = series[opts.seriesIndex] ?? 0
102 + const total = series.reduce((sum, v) => sum + v, 0)
103 + const pct = total > 0 ? (value / total) * 100 : 0
104 + return `${seriesName} - ${value} (${pct.toFixed(1)}%)`
105 + }
106 + },
107 + tooltip: {
108 + theme: themeStore.isThemeDark ? "dark" : "light"
109 + },
110 + noData: {
111 + text: "No data",
112 + align: "center",
113 + style: {
114 + color: fg,
115 + fontSize: "16px",
116 + fontFamily: ff
117 + }
118 + }
119 + }
120 +})
121 +</script>
frontend/src/components/common/charts/chartColors.ts new
+17
@@ -0,0 +1,17 @@
1 +/** Palette shared by dashboard charts (pie segments, etc.). */
2 +export const DASHBOARD_CHART_COLORS = [
3 + "#8a3ffc",
4 + "#33b1ff",
5 + "#007d79",
6 + "#ff7eb6",
7 + "#fa4d56",
8 + "#fff1f1",
9 + "#6fdc8c",
10 + "#4589ff",
11 + "#d12771",
12 + "#d2a106",
13 + "#08bdba",
14 + "#bae6ff",
15 + "#ba4e00",
16 + "#d4bbff"
17 +]
frontend/src/components/customers/CustomerCreationButton.vue
+2 -2
@@ -54,7 +54,7 @@
54 </template>
55
56 <script setup lang="ts">
57 -import type { Size } from "naive-ui/es/button/src/interface"
57 +import type { ButtonSize } from "naive-ui"
58 import { NButton, NDrawer, NDrawerContent } from "naive-ui"
59 import { computed, ref, watch } from "vue"
60 import Icon from "@/components/common/Icon.vue"
@@ -64,7 +64,7 @@ import CustomerForm from "./CustomerForm.vue"
64 const { customersCount, disabled, size } = defineProps<{
65 customersCount?: number
66 disabled?: boolean
67 - size?: Size
67 + size?: ButtonSize
68 }>()
69
70 const emit = defineEmits<{
frontend/src/components/customers/eventSources/CustomerEventSources.vue
+1 -1
@@ -4,7 +4,7 @@
4 <div v-if="showForm">
5 <CustomerEventSourceForm
6 :customer-code
7 - :editing-source="editingSource"
7 + :editing-source
8 @submitted="refreshList()"
9 @close="closeForm()"
10 />
frontend/src/components/customers/integrations/CustomerIntegrationActions.vue
+2 -2
@@ -17,7 +17,7 @@
17 </template>
18
19 <script setup lang="ts">
20 -import type { Size } from "naive-ui/es/button/src/interface"
20 +import type { ButtonSize } from "naive-ui"
21 import type { ApiCommonResponse } from "@/types/common.d"
22 import type { CustomerIntegration } from "@/types/integrations.d"
23 import { NButton, useDialog, useMessage } from "naive-ui"
@@ -29,7 +29,7 @@ import { handleDeleteIntegration } from "./utils"
29 const { integration, hideDeleteButton, size } = defineProps<{
30 integration: CustomerIntegration
31 hideDeleteButton?: boolean
32 - size?: Size
32 + size?: ButtonSize
33 }>()
34
35 const emit = defineEmits<{
frontend/src/components/customers/metadata/CustomerIntegrationMetaButton.vue
+2 -2
@@ -23,7 +23,7 @@
23 </template>
24
25 <script setup lang="ts">
26 -import type { Size } from "naive-ui/es/button/src/interface"
26 +import type { ButtonSize } from "naive-ui"
27 import { NButton, NModal } from "naive-ui"
28 import { defineAsyncComponent, ref } from "vue"
29 import Icon from "@/components/common/Icon.vue"
@@ -31,7 +31,7 @@ import Icon from "@/components/common/Icon.vue"
31 const { integrationName, customerCode, size } = defineProps<{
32 integrationName: string
33 customerCode: string
34 - size?: Size
34 + size?: ButtonSize
35 }>()
36
37 const CustomerIntegrationMetaDetails = defineAsyncComponent(
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorActions.vue
+2 -2
@@ -162,7 +162,7 @@
162 </template>
163
164 <script setup lang="ts">
165 -import type { Size } from "naive-ui/es/button/src/interface"
165 +import type { ButtonSize } from "naive-ui"
166 import type { FortinetModel } from "./provisions/FortinetForm.vue"
167 import type { SentinelOneModel } from "./provisions/SentinelOneForm.vue"
168 import type { SonicWallModel } from "./provisions/SonicwallForm.vue"
@@ -179,7 +179,7 @@ import SonicwallForm from "./provisions/SonicwallForm.vue"
179 const { networkConnector, hideDeleteButton, size } = defineProps<{
180 networkConnector: CustomerNetworkConnector
181 hideDeleteButton?: boolean
182 - size?: Size
182 + size?: ButtonSize
183 }>()
184
185 const emit = defineEmits<{
frontend/src/components/dashboards/DashboardCategoriesSection.vue new
+186
@@ -0,0 +1,186 @@
1 +<template>
2 + <n-card size="small" class="@container">
3 + <template #header>Dashboard Categories</template>
4 + <template #header-extra>
5 + <span class="text-secondary text-sm">{{ categories.length }} available</span>
6 + </template>
7 +
8 + <n-spin :show="loadingCategories">
9 + <div
10 + v-if="categories.length"
11 + class="grid grid-cols-1 gap-3 @xl:grid-cols-2 @3xl:grid-cols-3 @6xl:grid-cols-4"
12 + >
13 + <DashboardCategoryCard
14 + v-for="cat in categories"
15 + :key="cat.id"
16 + :category="cat"
17 + :selected="selectedCategoryId === cat.id"
18 + @select="selectCategory(cat.id)"
19 + />
20 + </div>
21 + <n-empty v-else-if="!loadingCategories" description="No dashboard categories found" />
22 + </n-spin>
23 +
24 + <div v-if="selectedCategory" class="border-border mt-4 flex flex-col gap-4 border-t pt-4">
25 + <div class="flex items-center justify-between gap-2">
26 + <div class="flex grow items-center justify-between">
27 + <div class="flex items-center gap-2">
28 + <div class="flex h-full items-center justify-center" :style="{ color: selectedCategory.color }">
29 + <Icon :name="getDashboardIcon(selectedCategory.icon)" :size="19" />
30 + </div>
31 + <span>{{ selectedCategory.title }}</span>
32 + </div>
33 + <span class="text-sm font-normal opacity-60">
34 + {{ selectedCategory.templates.length }} template{{
35 + selectedCategory.templates.length !== 1 ? "s" : ""
36 + }}
37 + </span>
38 + </div>
39 +
40 + <n-select
41 + v-model:value="selectedEventSourceId"
42 + :options="eventSourceOptions"
43 + placeholder="Select Event Source"
44 + filterable
45 + :loading="loadingEventSources"
46 + :disabled="!selectedCustomerCode"
47 + clearable
48 + size="small"
49 + :consistent-menu-width="false"
50 + class="w-48!"
51 + />
52 + </div>
53 +
54 + <n-spin :show="loadingTemplates">
55 + <div
56 + v-if="selectedCategory.templates.length"
57 + class="grid grid-cols-1 gap-3 @xl:grid-cols-2 @3xl:grid-cols-3 @6xl:grid-cols-4"
58 + >
59 + <DashboardTemplateCard
60 + v-for="tpl in selectedCategory.templates"
61 + :key="tpl.id"
62 + :template="tpl"
63 + :selected-customer-code
64 + :selected-event-source-id
65 + :selected-category-id
66 + :enabled-dashboards
67 + disabled-tooltip-text="Select an event source first"
68 + @refresh-enabled-dashboards="emit('refreshEnabledDashboards')"
69 + />
70 + </div>
71 + <n-empty v-else-if="!loadingTemplates" description="No templates in this category" />
72 + </n-spin>
73 + </div>
74 + </n-card>
75 +</template>
76 +
77 +<script setup lang="ts">
78 +import type { DashboardCategory, DashboardCategoryWithTemplates, EnabledDashboard } from "@/types/dashboards.d"
79 +import type { EventSource } from "@/types/eventSources.d"
80 +import { NCard, NEmpty, NSelect, NSpin, useMessage } from "naive-ui"
81 +import { computed, onBeforeMount, ref, watch } from "vue"
82 +import Api from "@/api"
83 +import Icon from "@/components/common/Icon.vue"
84 +import DashboardCategoryCard from "./DashboardCategoryCard.vue"
85 +import DashboardTemplateCard from "./DashboardTemplateCard.vue"
86 +import { getDashboardIcon } from "./utils"
87 +
88 +const props = defineProps<{
89 + selectedCustomerCode: string | null
90 + eventSourcesList: EventSource[]
91 + loadingEventSources: boolean
92 + enabledDashboards: EnabledDashboard[]
93 +}>()
94 +
95 +const emit = defineEmits<{
96 + refreshEnabledDashboards: []
97 +}>()
98 +
99 +const message = useMessage()
100 +
101 +const loadingCategories = ref(false)
102 +const categories = ref<DashboardCategory[]>([])
103 +const selectedCategoryId = ref<string | null>(null)
104 +const selectedCategory = ref<DashboardCategoryWithTemplates | null>(null)
105 +const loadingTemplates = ref(false)
106 +const selectedEventSourceId = ref<number | null>(null)
107 +
108 +const eventSourceOptions = computed(() =>
109 + props.eventSourcesList
110 + .filter(source => source.enabled)
111 + .map(source => ({
112 + label: `${source.name} (${source.event_type})`,
113 + value: source.id
114 + }))
115 +)
116 +
117 +function getCategories() {
118 + loadingCategories.value = true
119 + Api.siem
120 + .getDashboardCategories()
121 + .then(res => {
122 + if (res.data.success) {
123 + categories.value = res.data?.categories || []
124 + } else {
125 + message.warning(res.data?.message || "An error occurred. Please try again later.")
126 + }
127 + })
128 + .catch(err => {
129 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
130 + })
131 + .finally(() => {
132 + loadingCategories.value = false
133 + })
134 +}
135 +
136 +function selectCategory(categoryId: string) {
137 + if (selectedCategoryId.value === categoryId) {
138 + selectedCategoryId.value = null
139 + selectedCategory.value = null
140 + return
141 + }
142 +
143 + selectedCategoryId.value = categoryId
144 + loadingTemplates.value = true
145 +
146 + Api.siem
147 + .getDashboardCategory(categoryId)
148 + .then(res => {
149 + if (res.data.success) {
150 + selectedCategory.value = res.data.category
151 + } else {
152 + message.warning(res.data?.message || "An error occurred. Please try again later.")
153 + }
154 + })
155 + .catch(err => {
156 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
157 + })
158 + .finally(() => {
159 + loadingTemplates.value = false
160 + })
161 +}
162 +
163 +watch(
164 + () => props.selectedCustomerCode,
165 + () => {
166 + selectedEventSourceId.value = null
167 + }
168 +)
169 +
170 +watch(
171 + () => props.eventSourcesList,
172 + sources => {
173 + if (!selectedEventSourceId.value) return
174 +
175 + const hasSelectedSource = sources.some(source => source.enabled && source.id === selectedEventSourceId.value)
176 +
177 + if (!hasSelectedSource) {
178 + selectedEventSourceId.value = null
179 + }
180 + }
181 +)
182 +
183 +onBeforeMount(() => {
184 + getCategories()
185 +})
186 +</script>
frontend/src/components/dashboards/DashboardCategoryCard.vue new
+52
@@ -0,0 +1,52 @@
1 +<template>
2 + <CardEntity hoverable clickable size="small" :class="{ highlighted: selected }" @click="$emit('select')">
3 + <template #headerMain>
4 + <div class="flex items-center gap-2">
5 + <div class="flex h-full items-center justify-center" :style="{ color: category.color }">
6 + <Icon :name="getDashboardIcon(category.icon)" :size="16" />
7 + </div>
8 + {{ category.title }}
9 + </div>
10 + </template>
11 + <template #default>
12 + <div class="flex flex-col gap-2">
13 + <p class="text-xs">
14 + {{ category.description }}
15 + </p>
16 + <div v-if="category.tags.length" class="text-tertiary flex flex-wrap gap-2 text-xs">
17 + <span v-for="tag in category.tags" :key="tag">#{{ tag }}</span>
18 + </div>
19 + </div>
20 + </template>
21 +
22 + <template #footerMain>
23 + <div class="flex flex-wrap gap-2">
24 + <Badge type="splitted">
25 + <template #label>Vendor</template>
26 + <template #value>{{ category.vendor }}</template>
27 + </Badge>
28 + <Badge type="splitted">
29 + <template #label>Type</template>
30 + <template #value>{{ category.event_type }}</template>
31 + </Badge>
32 + </div>
33 + </template>
34 + </CardEntity>
35 +</template>
36 +
37 +<script setup lang="ts">
38 +import type { DashboardCategory } from "@/types/dashboards.d"
39 +import Badge from "@/components/common/Badge.vue"
40 +import CardEntity from "@/components/common/cards/CardEntity.vue"
41 +import Icon from "@/components/common/Icon.vue"
42 +import { getDashboardIcon } from "./utils"
43 +
44 +defineProps<{
45 + category: DashboardCategory
46 + selected?: boolean
47 +}>()
48 +
49 +defineEmits<{
50 + select: []
51 +}>()
52 +</script>
frontend/src/components/dashboards/DashboardTemplateCard.vue new
+144
@@ -0,0 +1,144 @@
1 +<template>
2 + <CardEntity hoverable size="small">
3 + <template #headerMain>
4 + {{ template.title }}
5 + </template>
6 + <template #default>
7 + <p class="text-xs">
8 + {{ template.description }}
9 + </p>
10 + </template>
11 +
12 + <template #footerMain>
13 + <Badge type="splitted">
14 + <template #label>Panels</template>
15 + <template #value>{{ template.panels.length }}</template>
16 + </Badge>
17 + </template>
18 +
19 + <template #footerExtra>
20 + <n-tooltip v-if="!isEnabled" :disabled="!disabledTooltipText" class="px-2! py-1!">
21 + <template #trigger>
22 + <n-button size="small" type="primary" :disabled="!canEnable" @click="onEnable">
23 + <template #icon>
24 + <Icon :name="disabledTooltipText ? LockedIcon : EnableIcon" />
25 + </template>
26 + Enable
27 + </n-button>
28 + </template>
29 + <div class="text-sm">
30 + {{ disabledTooltipText }}
31 + </div>
32 + </n-tooltip>
33 + <n-button v-else size="small" type="error" quaternary @click="onDisable">
34 + <template #icon>
35 + <Icon :name="DisableIcon" />
36 + </template>
37 + Disable
38 + </n-button>
39 + </template>
40 + </CardEntity>
41 +</template>
42 +
43 +<script setup lang="ts">
44 +import type { DashboardTemplate, EnabledDashboard } from "@/types/dashboards.d"
45 +import { NButton, NTooltip, useDialog, useMessage } from "naive-ui"
46 +import { computed } from "vue"
47 +import Api from "@/api"
48 +import Badge from "@/components/common/Badge.vue"
49 +import CardEntity from "@/components/common/cards/CardEntity.vue"
50 +import Icon from "@/components/common/Icon.vue"
51 +
52 +const props = defineProps<{
53 + template: DashboardTemplate
54 + selectedCustomerCode: string | null
55 + selectedEventSourceId: number | null
56 + selectedCategoryId: string | null
57 + enabledDashboards: EnabledDashboard[]
58 + disabledTooltipText?: string
59 +}>()
60 +
61 +const emit = defineEmits<{
62 + refreshEnabledDashboards: []
63 +}>()
64 +
65 +const message = useMessage()
66 +const dialog = useDialog()
67 +
68 +const EnableIcon = "carbon:add-alt"
69 +const DisableIcon = "carbon:subtract-alt"
70 +const LockedIcon = "carbon:locked"
71 +
72 +const canEnable = computed(() => !!props.selectedCustomerCode && !!props.selectedEventSourceId)
73 +
74 +const isEnabled = computed(() => {
75 + if (!props.selectedCategoryId || !props.selectedEventSourceId) return false
76 +
77 + return props.enabledDashboards.some(
78 + d =>
79 + d.library_card === props.selectedCategoryId &&
80 + d.template_id === props.template.id &&
81 + d.event_source_id === props.selectedEventSourceId
82 + )
83 +})
84 +
85 +function onEnable() {
86 + if (!props.selectedCustomerCode || !props.selectedEventSourceId || !props.selectedCategoryId) {
87 + message.warning("Please select a customer and event source first")
88 + return
89 + }
90 +
91 + Api.siem
92 + .enableDashboard({
93 + customer_code: props.selectedCustomerCode,
94 + event_source_id: props.selectedEventSourceId,
95 + library_card: props.selectedCategoryId,
96 + template_id: props.template.id,
97 + display_name: props.template.title
98 + })
99 + .then(res => {
100 + if (res.data.success) {
101 + message.success(res.data?.message || "Dashboard enabled successfully")
102 + emit("refreshEnabledDashboards")
103 + } else {
104 + message.warning(res.data?.message || "An error occurred. Please try again later.")
105 + }
106 + })
107 + .catch(err => {
108 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
109 + })
110 +}
111 +
112 +function onDisable() {
113 + const match = props.enabledDashboards.find(
114 + d =>
115 + d.library_card === props.selectedCategoryId &&
116 + d.template_id === props.template.id &&
117 + d.event_source_id === props.selectedEventSourceId
118 + )
119 +
120 + if (!match) return
121 +
122 + dialog.warning({
123 + title: "Disable Dashboard",
124 + content: `Are you sure you want to disable "${props.template.title}"?`,
125 + positiveText: "Disable",
126 + negativeText: "Cancel",
127 + onPositiveClick: () => {
128 + Api.siem
129 + .disableDashboard(match.id)
130 + .then(res => {
131 + if (res.data.success) {
132 + message.success(res.data?.message || "Dashboard disabled successfully")
133 + emit("refreshEnabledDashboards")
134 + } else {
135 + message.warning(res.data?.message || "An error occurred. Please try again later.")
136 + }
137 + })
138 + .catch(err => {
139 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
140 + })
141 + }
142 + })
143 +}
144 +</script>
frontend/src/components/dashboards/DashboardViewer.vue new
+202
@@ -0,0 +1,202 @@
1 +<template>
2 + <div class="@container flex flex-col gap-8">
3 + <!-- Header Bar -->
4 + <div class="flex flex-wrap items-end justify-between gap-6">
5 + <div class="flex gap-3">
6 + <!-- TODO-FE: use router by name -->
7 + <n-button quaternary size="small" @click="router.push('/dashboards')">
8 + <template #icon>
9 + <Icon :name="ArrowBackIcon" :size="22" />
10 + </template>
11 + </n-button>
12 + <div class="flex flex-col">
13 + <span class="text-lg font-semibold">{{ dashboardTitle }}</span>
14 + <span class="text-xs opacity-60">{{ dashboardDescription }}</span>
15 + </div>
16 + </div>
17 + <div class="flex grow items-center justify-end gap-2">
18 + <n-radio-group v-model:value="selectedTimerange" size="small">
19 + <n-radio-button v-for="preset in timePresets" :key="preset" :value="preset" :label="preset" />
20 + </n-radio-group>
21 +
22 + <n-button size="small" :loading @click="fetchPanelData">
23 + <template #icon>
24 + <Icon :name="RefreshIcon" :size="16" />
25 + </template>
26 + </n-button>
27 + </div>
28 + </div>
29 +
30 + <!-- Panels Grid -->
31 + <n-spin :show="loading" content-class="grid grid-cols-12 gap-4">
32 + <CardLink
33 + v-for="item in panels"
34 + :key="item.panel.id"
35 + :title="item.panel.title"
36 + class="h-full"
37 + :class="[panelColSpanClass(item.panel.w)]"
38 + :clickable="['stat'].includes(item.panel.type)"
39 + @click="['stat'].includes(item.panel.type) ? openEventSearch(item.panel.lucene || '*') : undefined"
40 + >
41 + <template v-if="['pie', 'bar_h'].includes(item.panel.type)" #header-extra>
42 + <n-tooltip class="py1! px2!">
43 + <template #trigger>
44 + <Icon :name="InfoIcon" :size="16" class="text-secondary cursor-help" />
45 + </template>
46 + <div class="text-sm">Click on a segment to go to the event search page.</div>
47 + </n-tooltip>
48 + </template>
49 +
50 + <div v-if="item.panel.type === 'stat'" class="font-mono text-2xl font-semibold">
51 + {{ formatCompactNumber(item.data?.value) }}
52 + </div>
53 +
54 + <component
55 + :is="chartByType[item.panel.type]"
56 + v-if="item.data && chartByType[item.panel.type]"
57 + :labels="item.data.labels"
58 + :data="item.data.data"
59 + :monochrome="item.panel.type === 'histogram'"
60 + :labels-datetime="item.panel.type === 'histogram'"
61 + :height="`${item.panel.h}px`"
62 + @item-click="onChartItemClick(item.panel, $event.name)"
63 + />
64 +
65 + <span v-if="item.data?.error" class="text-error text-xs">
66 + {{ item.data.error }}
67 + </span>
68 + </CardLink>
69 + </n-spin>
70 +
71 + <n-empty v-if="!loading && !hasData && errorMsg" :description="errorMsg" />
72 + </div>
73 +</template>
74 +
75 +<script setup lang="ts">
76 +import type { Component } from "vue"
77 +import type { ApiError } from "@/types/common"
78 +import type { DashboardPanel, DashboardPanelType, PanelResult } from "@/types/dashboards.d"
79 +import axios from "axios"
80 +import { NButton, NEmpty, NRadioButton, NRadioGroup, NSpin, NTooltip, useMessage } from "naive-ui"
81 +import { computed, ref, watch } from "vue"
82 +import { useRouter } from "vue-router"
83 +import Api from "@/api"
84 +import CardLink from "@/components/common/cards/CardLink.vue"
85 +import ChartBar from "@/components/common/charts/ChartBar.vue"
86 +import ChartColumn from "@/components/common/charts/ChartColumn.vue"
87 +import ChartPie from "@/components/common/charts/ChartPie.vue"
88 +import Icon from "@/components/common/Icon.vue"
89 +import { formatCompactNumber } from "@/utils"
90 +import { panelColSpanClass } from "./utils"
91 +
92 +const { dashboardId } = defineProps<{
93 + dashboardId: number
94 +}>()
95 +
96 +const ArrowBackIcon = "carbon:arrow-left"
97 +const RefreshIcon = "carbon:renew"
98 +
99 +interface DashboardPanelEntry {
100 + panel: DashboardPanel
101 + data: PanelResult | undefined
102 +}
103 +
104 +const chartByType: Record<DashboardPanelType, Component | undefined> = {
105 + stat: undefined,
106 + pie: ChartPie,
107 + bar_h: ChartBar,
108 + histogram: ChartColumn
109 +}
110 +
111 +const router = useRouter()
112 +const InfoIcon = "carbon:information"
113 +const message = useMessage()
114 +
115 +const timePresets = ["1h", "6h", "24h", "7d", "30d"]
116 +
117 +const dashboardTitle = ref("")
118 +const dashboardDescription = ref("")
119 +const customerCode = ref("")
120 +const sourceName = ref("")
121 +const panels = ref<DashboardPanelEntry[]>([])
122 +const loading = ref(false)
123 +const errorMsg = ref("")
124 +const selectedTimerange = ref(timePresets[2])
125 +
126 +const hasData = computed(() => panels.value.some(row => row.data != null))
127 +
128 +let abortController = new AbortController()
129 +
130 +async function fetchPanelData() {
131 + if (abortController) {
132 + abortController.abort()
133 + }
134 +
135 + abortController = new AbortController()
136 +
137 + loading.value = true
138 + errorMsg.value = ""
139 +
140 + try {
141 + const res = await Api.siem.getPanelData(dashboardId, selectedTimerange.value, abortController.signal)
142 +
143 + if (res.data.success) {
144 + dashboardTitle.value = res.data.template.title
145 + dashboardDescription.value = res.data.template.description
146 +
147 + panels.value = res.data.template.panels.map(p => ({
148 + panel: p,
149 + data: res.data.panels[p.id]
150 + }))
151 +
152 + customerCode.value = res.data.customer_code
153 + sourceName.value = res.data.source_name
154 + } else {
155 + errorMsg.value = res.data.message || "Failed to fetch panel data"
156 + message.error(errorMsg.value)
157 + }
158 +
159 + loading.value = false
160 + } catch (error) {
161 + if (!axios.isCancel(error)) {
162 + loading.value = false
163 + errorMsg.value = (error as ApiError).response?.data?.message || "Failed to fetch panel data"
164 + message.error(errorMsg.value)
165 + }
166 + }
167 +}
168 +
169 +function openEventSearch(luceneQuery: string) {
170 + const routeData = router.resolve({
171 + name: "EventSearch",
172 + query: {
173 + customer_code: customerCode.value,
174 + source_name: sourceName.value,
175 + query: luceneQuery
176 + }
177 + })
178 + window.open(routeData.href, "_blank")
179 +}
180 +
181 +function buildDrillDownQuery(panel: DashboardPanel, clickedValue: string): string {
182 + const baseLucene = panel.lucene && panel.lucene !== "*" ? `(${panel.lucene})` : ""
183 + const fieldFilter = panel.field ? `${panel.field}:"${clickedValue}"` : ""
184 + return [baseLucene, fieldFilter].filter(Boolean).join(" AND ")
185 +}
186 +
187 +function onChartItemClick(panel: DashboardPanel, name: string) {
188 + if (panel.type === "histogram") {
189 + return
190 + }
191 + const query = buildDrillDownQuery(panel, name)
192 + if (query) openEventSearch(query)
193 +}
194 +
195 +watch(
196 + selectedTimerange,
197 + () => {
198 + fetchPanelData()
199 + },
200 + { immediate: true }
201 +)
202 +</script>
frontend/src/components/dashboards/DashboardsBrowser.vue new
+132
@@ -0,0 +1,132 @@
1 +<template>
2 + <div class="flex flex-col gap-4">
3 + <!-- Filters Bar -->
4 + <n-form-item label="Customer" :show-feedback="false">
5 + <n-select
6 + v-model:value="selectedCustomerCode"
7 + :options="customersOptions"
8 + placeholder="Select Customer"
9 + filterable
10 + :loading="loadingCustomers"
11 + :consistent-menu-width="false"
12 + clearable
13 + />
14 + </n-form-item>
15 +
16 + <!-- No Event Sources Warning -->
17 + <n-alert v-if="showNoSourcesWarning" title="No Event Sources Configured" type="warning">
18 + An Event Source needs to be defined for this customer before dashboards can be enabled. Go to the customer's
19 + <strong>Event Sources</strong>
20 + tab to configure one.
21 + </n-alert>
22 +
23 + <EnabledDashboardsSection
24 + ref="enabledDashboardsSectionRef"
25 + v-model:enabled-dashboards="enabledDashboards"
26 + :customer-code="selectedCustomerCode"
27 + :visible="!!selectedCustomerCode && !showNoSourcesWarning"
28 + :event-sources-list
29 + />
30 +
31 + <DashboardCategoriesSection
32 + :selected-customer-code
33 + :event-sources-list
34 + :loading-event-sources
35 + :enabled-dashboards
36 + @refresh-enabled-dashboards="refreshEnabledDashboards"
37 + />
38 + </div>
39 +</template>
40 +
41 +<script setup lang="ts">
42 +import type { Customer } from "@/types/customers.d"
43 +import type { EnabledDashboard } from "@/types/dashboards.d"
44 +import type { EventSource } from "@/types/eventSources.d"
45 +import { NAlert, NFormItem, NSelect, useMessage } from "naive-ui"
46 +import { computed, onBeforeMount, ref, useTemplateRef, watch } from "vue"
47 +import Api from "@/api"
48 +import DashboardCategoriesSection from "./DashboardCategoriesSection.vue"
49 +import EnabledDashboardsSection from "./EnabledDashboardsSection.vue"
50 +
51 +const message = useMessage()
52 +
53 +// ── Customer selection ──────────────────────────────────────────
54 +const loadingCustomers = ref(false)
55 +const customersList = ref<Customer[]>([])
56 +const selectedCustomerCode = ref<string | null>(null)
57 +
58 +// ── Enabled dashboards (lista aggiornata da EnabledDashboardsSection via v-model) ──
59 +const enabledDashboards = ref<EnabledDashboard[]>([])
60 +const enabledDashboardsSectionRef = useTemplateRef<InstanceType<typeof EnabledDashboardsSection>>(
61 + "enabledDashboardsSectionRef"
62 +)
63 +
64 +const customersOptions = computed(() =>
65 + customersList.value.map(c => ({ label: `#${c.customer_code} - ${c.customer_name}`, value: c.customer_code }))
66 +)
67 +
68 +function getCustomers() {
69 + loadingCustomers.value = true
70 +
71 + Api.customers
72 + .getCustomers()
73 + .then(res => {
74 + if (res.data.success) {
75 + customersList.value = res.data?.customers || []
76 + } else {
77 + message.warning(res.data?.message || "An error occurred. Please try again later.")
78 + }
79 + })
80 + .catch(err => {
81 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
82 + })
83 + .finally(() => {
84 + loadingCustomers.value = false
85 + })
86 +}
87 +
88 +// ── Event Source selection ───────────────────────────────────────
89 +const loadingEventSources = ref(false)
90 +const eventSourcesList = ref<EventSource[]>([])
91 +
92 +const showNoSourcesWarning = computed(
93 + () => selectedCustomerCode.value && !loadingEventSources.value && !eventSourcesList.value.length
94 +)
95 +
96 +function getEventSources(customerCode: string) {
97 + loadingEventSources.value = true
98 + eventSourcesList.value = []
99 +
100 + Api.siem
101 + .getEventSources(customerCode)
102 + .then(res => {
103 + if (res.data.success) {
104 + eventSourcesList.value = res.data?.event_sources || []
105 + } else {
106 + message.warning(res.data?.message || "An error occurred. Please try again later.")
107 + }
108 + })
109 + .catch(err => {
110 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
111 + })
112 + .finally(() => {
113 + loadingEventSources.value = false
114 + })
115 +}
116 +
117 +function refreshEnabledDashboards() {
118 + enabledDashboardsSectionRef.value?.refreshEnabledDashboards()
119 +}
120 +
121 +watch(selectedCustomerCode, code => {
122 + eventSourcesList.value = []
123 +
124 + if (code) {
125 + getEventSources(code)
126 + }
127 +})
128 +
129 +onBeforeMount(() => {
130 + getCustomers()
131 +})
132 +</script>
frontend/src/components/dashboards/EnabledDashboardsSection.vue new
+176
@@ -0,0 +1,176 @@
1 +<template>
2 + <n-card v-if="visible" ref="wrapperRef" size="small">
3 + <template #header>Enabled Dashboards</template>
4 + <template #header-extra>
5 + <span class="text-secondary text-sm">{{ enabledDashboards.length }} enabled</span>
6 + </template>
7 +
8 + <n-data-table
9 + bordered
10 + :loading="loadingEnabled"
11 + size="small"
12 + :data="enabledDashboards"
13 + :columns="enabledColumns"
14 + :scroll-x="600"
15 + :pagination="false"
16 + class="[&_.n-data-table-th\_\_title]:whitespace-nowrap"
17 + >
18 + <template #empty>
19 + <n-empty description="No enabled dashboards" />
20 + </template>
21 + </n-data-table>
22 + </n-card>
23 +</template>
24 +
25 +<script setup lang="ts">
26 +import type { DataTableColumns } from "naive-ui"
27 +import type { EnabledDashboard } from "@/types/dashboards.d"
28 +import type { EventSource } from "@/types/eventSources.d"
29 +import { useElementSize } from "@vueuse/core"
30 +import { NButton, NCard, NDataTable, NEmpty, useDialog, useMessage } from "naive-ui"
31 +import { computed, h, ref, useTemplateRef, watch } from "vue"
32 +import { useRouter } from "vue-router"
33 +import Api from "@/api"
34 +
35 +const props = defineProps<{
36 + customerCode: string | null
37 + visible: boolean
38 + eventSourcesList: EventSource[]
39 +}>()
40 +
41 +const enabledDashboards = defineModel<EnabledDashboard[]>("enabledDashboards", { default: () => [] })
42 +
43 +const loadingEnabled = ref(false)
44 +
45 +const message = useMessage()
46 +const dialog = useDialog()
47 +const { width: headerWidthRef } = useElementSize(useTemplateRef("wrapperRef"))
48 +const router = useRouter()
49 +const simpleMode = computed(() => headerWidthRef.value < 600)
50 +
51 +function fetchEnabledDashboards(customerCode: string) {
52 + loadingEnabled.value = true
53 +
54 + Api.siem
55 + .getEnabledDashboards(customerCode)
56 + .then(res => {
57 + if (res.data.success) {
58 + enabledDashboards.value = res.data?.enabled_dashboards || []
59 + } else {
60 + message.warning(res.data?.message || "An error occurred. Please try again later.")
61 + }
62 + })
63 + .catch(err => {
64 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
65 + })
66 + .finally(() => {
67 + loadingEnabled.value = false
68 + })
69 +}
70 +
71 +function refreshEnabledDashboards() {
72 + const code = props.customerCode
73 + if (code) {
74 + fetchEnabledDashboards(code)
75 + }
76 +}
77 +
78 +watch(
79 + () => props.customerCode,
80 + code => {
81 + if (!code) {
82 + loadingEnabled.value = false
83 + enabledDashboards.value = []
84 + return
85 + }
86 + fetchEnabledDashboards(code)
87 + },
88 + { immediate: true }
89 +)
90 +
91 +defineExpose({
92 + refreshEnabledDashboards
93 +})
94 +
95 +const enabledColumns = computed<DataTableColumns<EnabledDashboard>>(() => [
96 + { title: "Display Name", key: "display_name", minWidth: 240 },
97 + { title: "Category", key: "library_card", width: 150 },
98 + { title: "Template", key: "template_id", width: 180 },
99 + {
100 + title: "Event Source",
101 + key: "event_source_id",
102 + width: 180,
103 + render(row) {
104 + const source = props.eventSourcesList.find(s => s.id === row.event_source_id)
105 + return source ? `${source.name} (${source.event_type})` : `#${row.event_source_id}`
106 + }
107 + },
108 + {
109 + title: "Created",
110 + key: "created_at",
111 + width: 180,
112 + render(row) {
113 + return new Date(row.created_at).toLocaleString()
114 + }
115 + },
116 + {
117 + title: "",
118 + key: "actions",
119 + width: 160,
120 + fixed: simpleMode.value ? undefined : "right",
121 + render(row) {
122 + return h("div", { class: "flex gap-2" }, [
123 + h(
124 + NButton,
125 + {
126 + size: "small",
127 + type: "primary",
128 + quaternary: true,
129 + onClick: () => {
130 + router.push({ name: "DashboardView", params: { id: String(row.id) } })
131 + }
132 + },
133 + { default: () => "View" }
134 + ),
135 + h(
136 + NButton,
137 + {
138 + size: "small",
139 + type: "error",
140 + quaternary: true,
141 + onClick: () => {
142 + dialog.warning({
143 + title: "Disable Dashboard",
144 + content: `Are you sure you want to disable "${row.display_name}"?`,
145 + positiveText: "Disable",
146 + negativeText: "Cancel",
147 + onPositiveClick: () => {
148 + Api.siem
149 + .disableDashboard(row.id)
150 + .then(res => {
151 + if (res.data.success) {
152 + message.success("Dashboard disabled successfully")
153 + refreshEnabledDashboards()
154 + } else {
155 + message.warning(
156 + res.data?.message || "An error occurred. Please try again later."
157 + )
158 + }
159 + })
160 + .catch(err => {
161 + message.error(
162 + err.response?.data?.message ||
163 + "An error occurred. Please try again later."
164 + )
165 + })
166 + }
167 + })
168 + }
169 + },
170 + { default: () => "Disable" }
171 + )
172 + ])
173 + }
174 + }
175 +])
176 +</script>
frontend/src/components/dashboards/utils.ts new
+37
@@ -0,0 +1,37 @@
1 +export function getDashboardIcon(icon: string) {
2 + switch (icon) {
3 + case "cpu":
4 + case "memory":
5 + return "carbon:chip"
6 + case "network":
7 + return "carbon:load-balancer-network"
8 + case "storage":
9 + return "carbon:vmdk-disk"
10 + case "security":
11 + return "carbon:security"
12 + case "performance":
13 + return "ph:gauge"
14 + default:
15 + return "carbon:dashboard"
16 + }
17 +}
18 +
19 +/** Span responsive su griglia 12 col: `col-span-12` su schermi stretti, poi breakpoint (`@lg`, `@2xl`, `@3xl`, `@5xl`) verso lo span target `w`. */
20 +export const PANEL_COL_SPAN_BY_WIDTH: Record<number, string> = {
21 + 1: "col-span-12 @2xl:col-span-4 @3xl:col-span-2 @5xl:col-span-1",
22 + 2: "col-span-12 @lg:col-span-4 @5xl:col-span-2",
23 + 3: "col-span-12 @lg:col-span-6 @5xl:col-span-3",
24 + 4: "col-span-12 @4xl:col-span-4",
25 + 5: "col-span-12 @3xl:col-span-5",
26 + 6: "col-span-12 @2xl:col-span-6",
27 + 7: "col-span-12 @3xl:col-span-7",
28 + 8: "col-span-12 @2xl:col-span-8",
29 + 9: "col-span-12 @2xl:col-span-9",
30 + 10: "col-span-12 @xl:col-span-10",
31 + 11: "col-span-12 @xl:col-span-11",
32 + 12: "col-span-12"
33 +}
34 +
35 +export function panelColSpanClass(w: number): string {
36 + return PANEL_COL_SPAN_BY_WIDTH[w] ?? "col-span-12"
37 +}
frontend/src/components/events/EventDetailDrawer.vue
+5 -7
@@ -5,7 +5,7 @@
5 <div class="flex flex-col gap-1">
6 <div
7 v-for="[key, value] in sortedFields"
8 - :key="key"
8 + :key
9 class="field-row flex items-start gap-2 rounded px-2 py-1.5 hover:bg-[var(--hover-005-color)]"
10 >
11 <span class="min-w-36 shrink-0 font-mono text-xs font-semibold opacity-70">{{ key }}</span>
@@ -46,19 +46,17 @@ import { NButton, NDrawer, NDrawerContent, NEmpty } from "naive-ui"
46 import { computed } from "vue"
47 import Icon from "@/components/common/Icon.vue"
48
49 -const FilterAddIcon = "carbon:filter"
50 -const FilterRemoveIcon = "carbon:filter-remove"
51 -
52 -const show = defineModel<boolean>("show", { default: false })
53 -
49 const props = defineProps<{
50 event: EventSearchResult | null
51 }>()
57 -
52 defineEmits<{
53 "filter-add": [field: string, value: string]
54 "filter-exclude": [field: string, value: string]
55 }>()
56 +const FilterAddIcon = "carbon:filter"
57 +const FilterRemoveIcon = "carbon:filter-remove"
58 +
59 +const show = defineModel<boolean>("show", { default: false })
60
61 const sortedFields = computed(() => {
62 if (!props.event) return []
frontend/src/components/events/EventSearch.vue
+36 -17
@@ -40,6 +40,15 @@
40 <span class="text-xs opacity-60">Time Range</span>
41 <n-select v-model:value="timerange" :options="timerangeOptions" style="width: 140px" />
42 </div>
43 + <div v-if="timerange === 'custom'" class="flex flex-col gap-1">
44 + <span class="text-xs opacity-60">Custom Range</span>
45 + <n-date-picker
46 + v-model:value="customDateRange"
47 + type="datetimerange"
48 + clearable
49 + style="width: 380px"
50 + />
51 + </div>
52 <div class="flex flex-col gap-1">
53 <span class="text-xs opacity-60">Page Size</span>
54 <n-select v-model:value="pageSize" :options="pageSizeOptions" style="width: 110px" />
@@ -60,7 +69,6 @@
69 <!-- Query Bar with Autocomplete -->
70 <div class="relative">
71 <n-input
63 - ref="queryInputRef"
72 v-model:value="query"
73 placeholder="Lucene query (e.g. agent_name:server01 AND rule_level:>=10)"
74 clearable
@@ -81,8 +89,8 @@
89 <div
90 v-for="(suggestion, index) in filteredSuggestions"
91 :key="suggestion.field"
84 - class="suggestion-item flex cursor-pointer items-center justify-between px-3 py-1.5 text-sm hover:bg-[var(--hover-005-color)]"
85 - :class="{ 'bg-[var(--hover-005-color)]': index === activeSuggestionIndex }"
92 + class="suggestion-item hover:bg-hover-005 flex cursor-pointer items-center justify-between px-3 py-1.5 text-sm"
93 + :class="{ 'bg-hover-005': index === activeSuggestionIndex }"
94 @mousedown.prevent="applySuggestion(suggestion.field)"
95 >
96 <span class="font-mono">{{ suggestion.field }}</span>
@@ -102,13 +110,13 @@
110 </span>
111 </div>
112 <n-data-table
105 - :columns="columns"
113 + :columns
114 :data="events"
115 :bordered="false"
116 :single-line="false"
117 size="small"
118 :row-key="(row: EventSearchResult) => row._id || JSON.stringify(row)"
111 - :row-props="rowProps"
119 + :row-props
120 max-height="calc(100vh - 360px)"
121 virtual-scroll
122 />
@@ -135,10 +143,10 @@
143
144 <script setup lang="ts">
145 import type { DataTableColumns } from "naive-ui"
146 +import type { Customer } from "@/types/customers.d"
147 import type { EventSearchResult, FieldMapping } from "@/types/events.d"
148 import type { EventSource } from "@/types/eventSources.d"
140 -import type { Customer } from "@/types/customers.d"
141 -import { NAlert, NButton, NCard, NDataTable, NEmpty, NInput, NSelect, NSpin, useMessage } from "naive-ui"
149 +import { NAlert, NButton, NCard, NDataTable, NDatePicker, NEmpty, NInput, NSelect, NSpin, useMessage } from "naive-ui"
150 import { computed, h, nextTick, onBeforeMount, ref } from "vue"
151 import { useRoute } from "vue-router"
152 import Api from "@/api"
@@ -149,6 +157,7 @@ const route = useRoute()
157
158 const SearchIcon = "carbon:search"
159 const CodeIcon = "carbon:code"
160 +const FIELD_TOKEN_REGEX = /(?:^|[\s(])([a-z_][\w.]*)$/i
161
162 const message = useMessage()
163
@@ -193,6 +202,9 @@ const showNoSourcesWarning = computed(
202 () => selectedCustomerCode.value && !loadingEventSources.value && eventSourcesList.value.length === 0
203 )
204
205 +// -- Field mappings / autocomplete --
206 +const fieldMappings = ref<FieldMapping[]>([])
207 +
208 function getEventSources(customerCode: string) {
209 loadingEventSources.value = true
210 eventSourcesList.value = []
@@ -239,8 +251,10 @@ const timerangeOptions = [
251 { label: "3 days", value: "3d" },
252 { label: "7 days", value: "7d" },
253 { label: "14 days", value: "14d" },
242 - { label: "30 days", value: "30d" }
254 + { label: "30 days", value: "30d" },
255 + { label: "Custom", value: "custom" }
256 ]
257 +const customDateRange = ref<[number, number] | null>(null)
258
259 const pageSize = ref(50)
260 const pageSizeOptions = [
@@ -252,8 +266,6 @@ const pageSizeOptions = [
266
267 const query = ref("")
268
255 -// -- Field mappings / autocomplete --
256 -const fieldMappings = ref<FieldMapping[]>([])
269 const showSuggestions = ref(false)
270 const activeSuggestionIndex = ref(0)
271
@@ -277,7 +289,7 @@ const currentFieldToken = computed(() => {
289 const cursorPos = query.value.length
290 const before = query.value.substring(0, cursorPos)
291 // Match the last word being typed (field name token before a colon or standalone)
280 - const match = before.match(/(?:^|[\s(])([a-zA-Z_][\w.]*)$/)
292 + const match = before.match(FIELD_TOKEN_REGEX)
293 return match ? match[1] : ""
294 })
295
@@ -296,7 +308,7 @@ function applySuggestion(fieldName: string) {
308 const token = currentFieldToken.value
309 if (token) {
310 const lastIndex = query.value.lastIndexOf(token)
299 - query.value = query.value.substring(0, lastIndex) + fieldName + ":"
311 + query.value = `${query.value.substring(0, lastIndex) + fieldName}:`
312 }
313 showSuggestions.value = false
314 }
@@ -329,12 +341,19 @@ function searchEvents() {
341 hasSearched.value = true
342 resetResults()
343
344 + const params: { timerange?: string; page_size: number; query?: string; time_from?: string; time_to?: string } = {
345 + page_size: pageSize.value,
346 + query: query.value || undefined
347 + }
348 + if (timerange.value === "custom" && customDateRange.value) {
349 + params.time_from = new Date(customDateRange.value[0]).toISOString()
350 + params.time_to = new Date(customDateRange.value[1]).toISOString()
351 + } else {
352 + params.timerange = timerange.value === "custom" ? "24h" : timerange.value
353 + }
354 +
355 Api.siem
333 - .queryEvents(selectedCustomerCode.value, selectedSourceName.value, {
334 - timerange: timerange.value,
335 - page_size: pageSize.value,
336 - query: query.value || undefined
337 - })
356 + .queryEvents(selectedCustomerCode.value, selectedSourceName.value, params)
357 .then(res => {
358 if (res.data.success) {
359 events.value = res.data.events || []
frontend/src/components/githubAudit/GitHubAuditButton.vue
+3 -3
@@ -8,14 +8,14 @@
8 </template>
9
10 <script setup lang="ts">
11 -import type { Size, Type } from "naive-ui/es/button/src/interface"
11 +import type { ButtonSize, ButtonType } from "naive-ui"
12 import { NButton } from "naive-ui"
13 import { useRouter } from "vue-router"
14 import Icon from "@/components/common/Icon.vue"
15
16 defineProps<{
17 - size?: Size
18 - type?: Type
17 + size?: ButtonSize
18 + type?: ButtonType
19 secondary?: boolean
20 }>()
21
frontend/src/components/incidentManagement/alerts/AlertMergeCaseButton.vue
+2 -2
@@ -54,7 +54,7 @@
54 </template>
55
56 <script setup lang="ts">
57 -import type { Size } from "naive-ui/es/button/src/interface"
57 +import type { ButtonSize } from "naive-ui"
58 import type { Ref } from "vue"
59 import type { Alert } from "@/types/incidentManagement/alerts.d"
60 import type { Case } from "@/types/incidentManagement/cases.d"
@@ -65,7 +65,7 @@ import Api from "@/api"
65 import Icon from "@/components/common/Icon.vue"
66 import CaseItem from "../cases/CaseItem.vue"
67
68 -const { alerts, size } = defineProps<{ alerts: Alert[]; size?: Size }>()
68 +const { alerts, size } = defineProps<{ alerts: Alert[]; size?: ButtonSize }>()
69
70 const emit = defineEmits<{
71 (e: "updated", value: Alert): void
frontend/src/components/incidentManagement/cases/CaseCreationButton.vue
+2 -2
@@ -24,14 +24,14 @@
24 </template>
25
26 <script setup lang="ts">
27 -import type { Size } from "naive-ui/es/button/src/interface"
27 +import type { ButtonSize } from "naive-ui"
28 import type { Case } from "@/types/incidentManagement/cases"
29 import { NButton, NModal } from "naive-ui"
30 import { ref, watch } from "vue"
31 import Icon from "@/components/common/Icon.vue"
32 import CaseCreationForm from "./CaseCreationForm.vue"
33
34 -const { showIcon, size } = defineProps<{ showIcon?: boolean; size?: Size }>()
34 +const { showIcon, size } = defineProps<{ showIcon?: boolean; size?: ButtonSize }>()
35
36 const emit = defineEmits<{
37 (e: "submitted", value: Case): void
frontend/src/components/incidentManagement/cases/CaseNotificationButton.vue
+2 -2
@@ -17,14 +17,14 @@
17 </template>
18
19 <script setup lang="ts">
20 -import type { Size } from "naive-ui/es/button/src/interface"
20 +import type { ButtonSize } from "naive-ui"
21 import { NButton, NTooltip, useMessage } from "naive-ui"
22 import { ref } from "vue"
23 import Api from "@/api"
24 import Icon from "@/components/common/Icon.vue"
25
26 const { size, caseId, notificationInvokedNumber } = defineProps<{
27 - size?: Size
27 + size?: ButtonSize
28 caseId: number
29 notificationInvokedNumber: number
30 }>()
frontend/src/components/incidentManagement/cases/CaseReportButton.vue
+2 -3
@@ -52,8 +52,7 @@
52 </template>
53
54 <script setup lang="ts">
55 -import type { FormInst, FormRules, FormValidationError } from "naive-ui"
56 -import type { Size } from "naive-ui/es/button/src/interface"
55 +import type { ButtonSize, FormInst, FormRules, FormValidationError } from "naive-ui"
56 import type { CaseReportPayload } from "@/api/endpoints/incidentManagement/cases"
57 import type { DeepNullable } from "@/types/common"
58 import { saveAs } from "file-saver"
@@ -76,7 +75,7 @@ import { useSettingsStore } from "@/stores/settings"
75 import { formatDate } from "@/utils/format"
76 import CaseReportTemplateSelect from "./CaseReportTemplateSelect.vue"
77
79 -const { size, caseId } = defineProps<{ size?: Size; caseId: number }>()
78 +const { size, caseId } = defineProps<{ size?: ButtonSize; caseId: number }>()
79
80 const ReportIcon = "carbon:report-data"
81 const dFormats = useSettingsStore().dateFormat
frontend/src/components/incidentManagement/cases/CasesExport.vue
+36 -14
@@ -1,35 +1,46 @@
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 v-if="showIcon" #icon>
5 - <Icon :name="DownloadIcon" :size="14" />
6 - </template>
7 - Export
8 - </n-button>
9 - </n-dropdown>
2 + <div class="flex items-center gap-2">
3 + <n-date-picker
4 + v-model:value="selectedMonth"
5 + type="month"
6 + clearable
7 + placeholder="All time"
8 + size="small"
9 + style="width: 150px"
10 + />
11 + <n-dropdown placement="bottom-start" trigger="click" :options="customersOptions" @select="exportCases">
12 + <n-button :size :loading="exporting" secondary @click="load()">
13 + <template v-if="showIcon" #icon>
14 + <Icon :name="DownloadIcon" :size="14" />
15 + </template>
16 + Export
17 + </n-button>
18 + </n-dropdown>
19 + </div>
20 </template>
21
22 <script setup lang="ts">
13 -import type { Size } from "naive-ui/es/button/src/interface"
23 +import type { ButtonSize } from "naive-ui"
24 import type { DropdownMixedOption } from "naive-ui/es/dropdown/src/interface"
25 import type { Ref } from "vue"
26 import type { Customer } from "@/types/customers.d"
27 import { useWindowSize } from "@vueuse/core"
28 import { saveAs } from "file-saver"
19 -import { NButton, NDropdown, useMessage } from "naive-ui"
29 +import { NButton, NDatePicker, NDropdown, useMessage } from "naive-ui"
30 import { computed, h, inject, ref } from "vue"
31 import Api from "@/api"
32 import Icon from "@/components/common/Icon.vue"
33 import { useSettingsStore } from "@/stores/settings"
34 import { formatDate } from "@/utils/format"
35
26 -const { size, showIcon } = defineProps<{ size?: Size; showIcon?: boolean }>()
36 +const { size, showIcon } = defineProps<{ size?: ButtonSize; showIcon?: boolean }>()
37
38 const DownloadIcon = "carbon:cloud-download"
39 const loadingCustomersList = ref(false)
40 const dFormats = useSettingsStore().dateFormat
41 const exporting = ref(false)
42 const message = useMessage()
43 +const selectedMonth = ref<number | null>(null)
44 const { width: winWidth } = useWindowSize()
45 const customersList = inject<Ref<Customer[]>>("customers-list", ref([]))
46
@@ -76,13 +87,24 @@ const customersOptions = computed(() => {
87 function exportCases(key: string) {
88 exporting.value = true
89
90 + let year: number | undefined
91 + let month: number | undefined
92 +
93 + if (selectedMonth.value) {
94 + const date = new Date(selectedMonth.value)
95 + year = date.getFullYear()
96 + month = date.getMonth() + 1
97 + }
98 +
99 + const monthSuffix = year && month ? `_${year}-${String(month).padStart(2, "0")}` : ""
100 +
101 const fileName =
102 key === "--all--"
81 - ? `cases_${formatDate(new Date(), dFormats.datetimesec)}.csv`
82 - : `cases_customer:${key}_${formatDate(new Date(), dFormats.datetimesec)}.csv`
103 + ? `cases${monthSuffix}_${formatDate(new Date(), dFormats.datetimesec)}.csv`
104 + : `cases_customer:${key}${monthSuffix}_${formatDate(new Date(), dFormats.datetimesec)}.csv`
105
106 Api.incidentManagement.cases
85 - .exportCases(key === "--all--" ? undefined : key)
107 + .exportCases(key === "--all--" ? undefined : key, year, month)
108 .then(res => {
109 if (res.data) {
110 saveAs(new Blob([res.data], { type: "text/csv;charset=utf-8" }), fileName)
frontend/src/components/metrics/MetricsChart.vue
+15 -15
@@ -1,5 +1,5 @@
1 <template>
2 - <div ref="chartEl" :style="{ height: height + 'px' }"></div>
2 + <div ref="chartEl" :style="{ height: `${height}px` }"></div>
3 </template>
4
5 <script setup lang="ts">
@@ -12,20 +12,6 @@ import { CanvasRenderer } from "echarts/renderers"
12 import { computed, onBeforeUnmount, onMounted, ref, toRefs, watch } from "vue"
13 import { useThemeStore } from "@/stores/theme"
14
15 -echartsUse([
16 - TitleComponent,
17 - TooltipComponent,
18 - LegendComponent,
19 - GridComponent,
20 - DataZoomComponent,
21 - LineChart,
22 - BarChart,
23 - GaugeChart,
24 - CanvasRenderer
25 -])
26 -
27 -const COLORS = ["#38bdf8", "#22c55e", "#eab308", "#ef4444", "#a855f7", "#f97316", "#06b6d4", "#ec4899"]
28 -
15 const props = withDefaults(
16 defineProps<{
17 title: string
@@ -41,6 +27,20 @@ const props = withDefaults(
27 }
28 )
29
30 +echartsUse([
31 + TitleComponent,
32 + TooltipComponent,
33 + LegendComponent,
34 + GridComponent,
35 + DataZoomComponent,
36 + LineChart,
37 + BarChart,
38 + GaugeChart,
39 + CanvasRenderer
40 +])
41 +
42 +const COLORS = ["#38bdf8", "#22c55e", "#eab308", "#ef4444", "#a855f7", "#f97316", "#06b6d4", "#ec4899"]
43 +
44 const { title, series, height, yAxisName, formatBytes } = toRefs(props)
45
46 const chartEl = ref<HTMLElement | null>(null)
frontend/src/components/metrics/MetricsDashboard.vue
+5 -12
@@ -24,14 +24,7 @@
24 @update:value="refresh"
25 />
26 </div>
27 - <n-button
28 - size="small"
29 - type="primary"
30 - secondary
31 - :loading="loading"
32 - :disabled="!selectedHost"
33 - @click="refresh"
34 - >
27 + <n-button size="small" type="primary" secondary :loading :disabled="!selectedHost" @click="refresh">
28 <template #icon>
29 <Icon :name="RefreshIcon" :size="15" />
30 </template>
@@ -48,7 +41,7 @@
41 </n-card>
42
43 <!-- Loading -->
51 - <n-spin v-else-if="loading && !loaded" class="flex min-h-80 items-center justify-center" :show="true" />
44 + <n-spin v-else-if="loading && !loaded" class="flex min-h-80 items-center justify-center" show />
45
46 <!-- Dashboard -->
47 <div v-else-if="loaded">
@@ -112,7 +105,7 @@
105 :series="memory.mem_used || {}"
106 :height="280"
107 y-axis-name="bytes"
115 - :format-bytes="true"
108 + format-bytes
109 />
110 </n-card>
111 </section>
@@ -149,7 +142,7 @@
142 title="Disk I/O (bytes/s)"
143 :series="disks.disk_io || {}"
144 y-axis-name="B/s"
152 - :format-bytes="true"
145 + format-bytes
146 />
147 </n-card>
148 </div>
@@ -182,7 +175,7 @@
175 :series="network.traffic || {}"
176 :height="280"
177 y-axis-name="B/s"
185 - :format-bytes="true"
178 + format-bytes
179 />
180 </n-card>
181 <n-card>
frontend/src/components/metrics/MetricsGauge.vue
+3 -3
@@ -1,5 +1,5 @@
1 <template>
2 - <div ref="gaugeEl" :style="{ height: height + 'px' }"></div>
2 + <div ref="gaugeEl" :style="{ height: `${height}px` }"></div>
3 </template>
4
5 <script setup lang="ts">
@@ -11,8 +11,6 @@ import { CanvasRenderer } from "echarts/renderers"
11 import { computed, onBeforeUnmount, onMounted, ref, toRefs, watch } from "vue"
12 import { useThemeStore } from "@/stores/theme"
13
14 -echartsUse([TooltipComponent, GridComponent, GaugeChart, CanvasRenderer])
15 -
14 const props = withDefaults(
15 defineProps<{
16 value: number
@@ -25,6 +23,8 @@ const props = withDefaults(
23 }
24 )
25
26 +echartsUse([TooltipComponent, GridComponent, GaugeChart, CanvasRenderer])
27 +
28 const { value, title, height } = toRefs(props)
29
30 const gaugeEl = ref<HTMLElement | null>(null)
frontend/src/components/mitre/AttackSimulator/SimulatorButton.vue
+2 -2
@@ -21,13 +21,13 @@
21 </template>
22
23 <script setup lang="ts">
24 -import type { Size } from "naive-ui/es/button/src/interface"
24 +import type { ButtonSize } from "naive-ui"
25 import { NButton, NModal } from "naive-ui"
26 import { computed, ref } from "vue"
27 import Icon from "@/components/common/Icon.vue"
28 import SimulatorWizard from "./SimulatorWizard.vue"
29
30 -const { size, techniqueId, osList } = defineProps<{ size?: Size; techniqueId: string; osList: string[] }>()
30 +const { size, techniqueId, osList } = defineProps<{ size?: ButtonSize; techniqueId: string; osList: string[] }>()
31
32 const AttackIcon = "mdi:target"
33 const showForm = ref(false)
frontend/src/components/monitoringAlerts/ItemActions.vue
+2 -2
@@ -16,7 +16,7 @@
16 </template>
17
18 <script setup lang="ts">
19 -import type { Size } from "naive-ui/es/button/src/interface"
19 +import type { ButtonSize } from "naive-ui"
20 import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
21 import { NButton, useDialog, useMessage } from "naive-ui"
22 import { computed, ref, watch } from "vue"
@@ -25,7 +25,7 @@ import Icon from "@/components/common/Icon.vue"
25
26 const { alert, size } = defineProps<{
27 alert: MonitoringAlert
28 - size?: Size
28 + size?: ButtonSize
29 }>()
30
31 const emit = defineEmits<{
frontend/src/components/sca/ScaStats.vue
+1 -1
@@ -304,7 +304,7 @@ const topPoliciesByScore = computed(() => {
304 })
305
306 // Convert to array and sort by score
307 - // eslint-disable-next-line e18e/prefer-array-to-sorted
307 +
308 return [...policyMap.values()].sort((a: PolicyAggregate, b: PolicyAggregate) => b.score - a.score).slice(0, 5)
309 })
310
frontend/src/components/scaPolicies/List.vue
+2 -2
@@ -119,7 +119,7 @@
119 <n-pagination
120 v-if="filteredPolicies.length > pageSize"
121 v-model:page="currentPage"
122 - :page-size="pageSize"
122 + :page-size
123 :item-count="filteredPolicies.length"
124 :page-slot="6"
125 />
@@ -130,7 +130,7 @@
130
131 <script setup lang="ts">
132 import type { ScaPolicyItem } from "@/types/sca.d"
133 -import { NBadge, NAlert, NButton, NEmpty, NInput, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
133 +import { NAlert, NBadge, NButton, NEmpty, NInput, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
134 import { computed, onBeforeMount, ref } from "vue"
135 import Api from "@/api"
136 import Icon from "@/components/common/Icon.vue"
frontend/src/components/scaPolicies/PolicyCard.vue
+1 -1
@@ -48,7 +48,7 @@
48 :bordered="false"
49 segmented
50 >
51 - <PolicyCardContent :policy="policy" />
51 + <PolicyCardContent :policy />
52 </n-modal>
53 </div>
54 </template>
frontend/src/components/scaPolicies/PolicyCardContent.vue
+2 -2
@@ -166,9 +166,9 @@
166 </template>
167
168 <script setup lang="ts">
169 -import type { ScaPolicyItem, ScaPackageAgentsResponse } from "@/types/sca.d"
170 -import { computed, ref } from "vue"
169 +import type { ScaPackageAgentsResponse, ScaPolicyItem } from "@/types/sca.d"
170 import { NButton, NEmpty, NSpin, useMessage } from "naive-ui"
171 +import { computed, ref } from "vue"
172 import Api from "@/api"
173 import Badge from "@/components/common/Badge.vue"
174 import CardEntity from "@/components/common/cards/CardEntity.vue"
frontend/src/components/scheduler/JobActions.vue
+2 -2
@@ -44,7 +44,7 @@
44 </template>
45
46 <script setup lang="ts">
47 -import type { Size } from "naive-ui/es/button/src/interface"
47 +import type { ButtonSize } from "naive-ui"
48 import type { UpdateJobPayload } from "@/api/endpoints/scheduler"
49 import type { Job } from "@/types/scheduler.d"
50 import { NButton, NModal, useMessage } from "naive-ui"
@@ -53,7 +53,7 @@ import Api from "@/api"
53 import Icon from "@/components/common/Icon.vue"
54 import JobForm from "./JobForm.vue"
55
56 -const props = defineProps<{ job: Job; size?: Size; inline?: boolean }>()
56 +const props = defineProps<{ job: Job; size?: ButtonSize; inline?: boolean }>()
57 const { job, size } = toRefs(props)
58
59 const StartIcon = "material-symbols:autoplay"
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemActions.vue
+2 -2
@@ -50,7 +50,7 @@
50 </template>
51
52 <script setup lang="ts">
53 -import type { Size } from "naive-ui/es/button/src/interface"
53 +import type { ButtonSize } from "naive-ui"
54 import { NButton, NModal, useDialog, useMessage } from "naive-ui"
55 import { computed, ref, watch } from "vue"
56 import Api from "@/api"
@@ -60,7 +60,7 @@ import SocCaseItem from "@/components/soc/SocCases/SocCaseItem.vue"
60 const { alertId, caseId, size } = defineProps<{
61 alertId?: string | number | null
62 caseId?: string | number | null
63 - size?: Size
63 + size?: ButtonSize
64 }>()
65
66 const emit = defineEmits<{
frontend/src/components/soc/SocCases/SocCaseItemActions.vue
+2 -2
@@ -29,7 +29,7 @@
29 </template>
30
31 <script setup lang="ts">
32 -import type { Size } from "naive-ui/es/button/src/interface"
32 +import type { ButtonSize } from "naive-ui"
33 import type { SocCase, SocCaseExt } from "@/types/soc/case.d"
34 import { NButton, useDialog, useMessage } from "naive-ui"
35 import { computed, ref, watch } from "vue"
@@ -39,7 +39,7 @@ import { StateName } from "@/types/soc/case.d"
39
40 const { caseData, size } = defineProps<{
41 caseData: SocCase | SocCaseExt | null
42 - size?: Size
42 + size?: ButtonSize
43 }>()
44
45 const emit = defineEmits<{
frontend/src/components/stackProvisioning/StackProvisioningButton.vue
+3 -3
@@ -20,15 +20,15 @@
20 </template>
21
22 <script setup lang="ts">
23 -import type { Size, Type } from "naive-ui/es/button/src/interface"
23 +import type { ButtonSize, ButtonType } from "naive-ui"
24 import { NButton, NModal } from "naive-ui"
25 import { ref } from "vue"
26 import Icon from "@/components/common/Icon.vue"
27 import StackProvisioningList from "./StackProvisioningList.vue"
28
29 defineProps<{
30 - size?: Size
31 - type?: Type
30 + size?: ButtonSize
31 + type?: ButtonType
32 secondary?: boolean
33 }>()
34
frontend/src/components/threatIntel/AIAnalystButton.vue
+2 -2
@@ -94,7 +94,7 @@
94 </template>
95
96 <script setup lang="ts">
97 -import type { Size } from "naive-ui/es/button/src/interface"
97 +import type { ButtonSize } from "naive-ui"
98 import type { AiAnalysisResponse } from "@/types/threatIntel.d"
99 import { NButton, NModal, NTabPane, NTabs, useMessage } from "naive-ui"
100 import { defineAsyncComponent, ref, watchEffect } from "vue"
@@ -113,7 +113,7 @@ const {
113 indexId: string
114 alertId: number
115 forceLicenseResponse?: boolean
116 - size?: Size
116 + size?: ButtonSize
117 }>()
118
119 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
frontend/src/components/threatIntel/AIVelociraptorArtifactRecommendationButton.vue
+2 -2
@@ -67,7 +67,7 @@
67 </template>
68
69 <script setup lang="ts">
70 -import type { Size } from "naive-ui/es/button/src/interface"
70 +import type { ButtonSize } from "naive-ui"
71 import type { AiVelociraptorArtifactRecommendationResponse } from "@/types/threatIntel.d"
72 import { NButton, NEmpty, NModal, useMessage } from "naive-ui"
73 import { ref, watchEffect } from "vue"
@@ -89,7 +89,7 @@ const {
89 agentId: string
90 alertId: number
91 forceLicenseResponse?: boolean
92 - size?: Size
92 + size?: ButtonSize
93 }>()
94
95 const LockIcon = "carbon:locked"
frontend/src/components/threatIntel/AIWazuhExclusionRuleButton.vue
+2 -2
@@ -58,7 +58,7 @@
58 </template>
59
60 <script setup lang="ts">
61 -import type { Size } from "naive-ui/es/button/src/interface"
61 +import type { ButtonSize } from "naive-ui"
62 import type { AiWazuhExclusionRuleResponse } from "@/types/threatIntel.d"
63 import { NButton, NEmpty, NModal, useMessage } from "naive-ui"
64 import { defineAsyncComponent, ref, watchEffect } from "vue"
@@ -77,7 +77,7 @@ const {
77 indexId: string
78 alertId: number
79 forceLicenseResponse?: boolean
80 - size?: Size
80 + size?: ButtonSize
81 }>()
82
83 const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSource.vue"))
frontend/src/components/threatIntel/ThreatIntelButton.vue
+3 -3
@@ -31,7 +31,7 @@
31 </template>
32
33 <script setup lang="ts">
34 -import type { Size, Type } from "naive-ui/es/button/src/interface"
34 +import type { ButtonSize, ButtonType } from "naive-ui"
35 import { NButton, NCollapse, NCollapseItem, NDrawer, NDrawerContent } from "naive-ui"
36 import { ref, watch } from "vue"
37 import Icon from "@/components/common/Icon.vue"
@@ -39,8 +39,8 @@ import ThreatIntelForm from "./ThreatIntelForm.vue"
39 import VirusTotalForm from "./VirusTotalForm.vue"
40
41 defineProps<{
42 - size?: Size
43 - type?: Type
42 + size?: ButtonSize
43 + type?: ButtonType
44 secondary?: boolean
45 }>()
46
frontend/src/components/threatIntel/VirusTotalEnrichmentButton.vue
+2 -2
@@ -532,7 +532,7 @@
532 </template>
533
534 <script setup lang="ts">
535 -import type { Size } from "naive-ui/es/button/src/interface"
535 +import type { ButtonSize } from "naive-ui"
536 import type { ItemProps } from "@/components/common/cards/CardStatsBars.vue"
537 import type { VirusTotalData } from "@/types/threatIntel.d"
538 import _pick from "lodash/pick"
@@ -545,7 +545,7 @@ import { formatDate } from "@/utils/format"
545
546 const { iocValue, size } = defineProps<{
547 iocValue: string
548 - size?: Size
548 + size?: ButtonSize
549 }>()
550
551 const Badge = defineAsyncComponent(() => import("@/components/common/Badge.vue"))
frontend/src/components/users/ChangePassword.vue
+3 -4
@@ -53,8 +53,7 @@
53 </template>
54
55 <script setup lang="ts">
56 -import type { FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui"
57 -import type { Size, Type } from "naive-ui/es/button/src/interface"
56 +import type { ButtonSize, ButtonType, FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui"
57 import type { User } from "@/types/user"
58 import { NButton, NDrawer, NDrawerContent, NForm, NFormItem, NInput, NSpin, useMessage } from "naive-ui"
59 import PasswordValidator from "password-validator"
@@ -70,8 +69,8 @@ interface ModelType {
69
70 const { type, size, user } = defineProps<{
71 user?: User
73 - size?: Size
74 - type?: Type
72 + size?: ButtonSize
73 + type?: ButtonType
74 }>()
75
76 const showFormDrawer = ref(false)
frontend/src/components/users/DeleteUser.vue
+3 -3
@@ -8,7 +8,7 @@
8 </template>
9
10 <script setup lang="ts">
11 -import type { Size, Type } from "naive-ui/es/button/src/interface"
11 +import type { ButtonSize, ButtonType } from "naive-ui"
12 import type { User } from "@/types/user"
13 import { NButton, useDialog, useMessage } from "naive-ui"
14 import { computed, h, ref, watch } from "vue"
@@ -21,8 +21,8 @@ const {
21 user
22 } = defineProps<{
23 user?: User
24 - size?: Size
25 - type?: Type
24 + size?: ButtonSize
25 + type?: ButtonType
26 }>()
27
28 const emit = defineEmits<{
frontend/src/components/users/TagRbacSettings.vue
+2 -2
@@ -46,12 +46,12 @@
46 :options="tagOptions"
47 :loading="loadingTags"
48 />
49 - <template #help>
49 + <template #feedback>
50 <span class="text-xs opacity-70">Users with access to this tag will also see untagged alerts</span>
51 </template>
52 </n-form-item>
53
54 - <n-divider v-if="settings.enabled" class="!my-2" />
54 + <n-divider v-if="settings.enabled" class="my-2!" />
55
56 <n-alert v-if="settings.enabled" type="info" title="How Tag RBAC Works">
57 <div class="info-content">
frontend/src/components/webVulnerabilityAssessment/WebVulnerabilityAssessmentButton.vue
+3 -3
@@ -8,14 +8,14 @@
8 </template>
9
10 <script setup lang="ts">
11 -import type { Size, Type } from "naive-ui/es/button/src/interface"
11 +import type { ButtonSize, ButtonType } from "naive-ui"
12 import { NButton } from "naive-ui"
13 import { useRouter } from "vue-router"
14 import Icon from "@/components/common/Icon.vue"
15
16 defineProps<{
17 - size?: Size
18 - type?: Type
17 + size?: ButtonSize
18 + type?: ButtonType
19 secondary?: boolean
20 }>()
21
frontend/src/router/index.ts
+12
@@ -187,6 +187,18 @@ const router = createRouter({
187 component: () => import("@/views/EventSearch.vue"),
188 meta: { title: "Event Search", auth: true, roles: RouteRole.All }
189 },
190 + {
191 + path: "/dashboards",
192 + name: "Dashboards",
193 + component: () => import("@/views/Dashboards.vue"),
194 + meta: { title: "Dashboards", auth: true, roles: RouteRole.All }
195 + },
196 + {
197 + path: "/dashboards/:id",
198 + name: "DashboardView",
199 + component: () => import("@/views/DashboardView.vue"),
200 + meta: { title: "Dashboard Viewer", auth: true, roles: RouteRole.All }
201 + },
202 {
203 path: "/artifacts",
204 name: "Artifacts",
frontend/src/stores/healthcheck.ts
+2 -2
@@ -74,9 +74,9 @@ export const useHealthcheckStore = defineStore("healthcheck", {
74 if (authStore.isLogged) {
75 if (this.uncommittedJournalEntriesThreshold) {
76 this.getGraylogCheck()
77 + // this.getClusterHealth()
78 + this.getHealthchecks()
79 }
78 - // this.getClusterHealth()
79 - this.getHealthchecks()
80 }
81 },
82
frontend/src/tailwind.css
+1
@@ -17,6 +17,7 @@
17 --color-info: var(--info-color);
18 --color-border: var(--border-color);
19 --color-hover: var(--hover-color);
20 + --color-hover-005: var(--hover-005-color);
21
22 --color-extra-1: var(--extra1-color);
23 --color-extra-2: var(--extra2-color);
frontend/src/types/dashboards.d.ts new
+72
@@ -0,0 +1,72 @@
1 +export type DashboardPanelType = "stat" | "pie" | "bar_h" | "histogram"
2 +
3 +export interface DashboardPanel {
4 + id: string
5 + title: string
6 + type: DashboardPanelType
7 + w: number
8 + h: number
9 + lucene: string
10 + field?: string
11 + size?: number
12 +}
13 +
14 +export interface DashboardTemplate {
15 + id: string
16 + title: string
17 + description: string
18 + panels: DashboardPanel[]
19 +}
20 +
21 +export interface DashboardCategory {
22 + id: string
23 + title: string
24 + description: string
25 + vendor: string
26 + product: string
27 + event_type: string
28 + tags: string[]
29 + color: string
30 + icon: string
31 +}
32 +
33 +export interface DashboardCategoryWithTemplates extends DashboardCategory {
34 + templates: DashboardTemplate[]
35 +}
36 +
37 +export interface EnabledDashboard {
38 + id: number
39 + customer_code: string
40 + event_source_id: number
41 + library_card: string
42 + template_id: string
43 + display_name: string
44 + created_at: string
45 +}
46 +
47 +export interface EnableDashboardPayload {
48 + customer_code: string
49 + event_source_id: number
50 + library_card: string
51 + template_id: string
52 + display_name: string
53 +}
54 +
55 +export interface PanelResult {
56 + type: string
57 + value: number | null
58 + labels: string[]
59 + data: number[]
60 + error: string | null
61 +}
62 +
63 +export interface PanelDataResponse {
64 + panels: Record<string, PanelResult>
65 + template: DashboardTemplate
66 + dashboard_id: number
67 + customer_code: string
68 + source_name: string
69 + accent_color: string
70 + success: boolean
71 + message: string
72 +}
frontend/src/utils/index.ts
+7
@@ -173,3 +173,10 @@ export function isDate(val?: SafeAny): boolean {
173
174 return dayjs(strVal, regionalFormats, true).isValid()
175 }
176 +
177 +export function formatCompactNumber(value: number | null | undefined): string {
178 + if (value == null) return "—"
179 + if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`
180 + if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`
181 + return value.toLocaleString()
182 +}
frontend/src/views/DashboardView.vue new
+14
@@ -0,0 +1,14 @@
1 +<template>
2 + <div class="page">
3 + <DashboardViewer :dashboard-id />
4 + </div>
5 +</template>
6 +
7 +<script setup lang="ts">
8 +import { computed } from "vue"
9 +import { useRoute } from "vue-router"
10 +import DashboardViewer from "@/components/dashboards/DashboardViewer.vue"
11 +
12 +const route = useRoute()
13 +const dashboardId = computed(() => Number(route.params.id))
14 +</script>
frontend/src/views/Dashboards.vue new
+9
@@ -0,0 +1,9 @@
1 +<template>
2 + <div class="page">
3 + <DashboardsBrowser />
4 + </div>
5 +</template>
6 +
7 +<script setup lang="ts">
8 +import DashboardsBrowser from "@/components/dashboards/DashboardsBrowser.vue"
9 +</script>