@cryptotaxi247 / CoPilot / commits / f047c0b8

728 prebuilt searches (#750)

* Add Copilot Searches integration with API routes and schemas * Add search execution endpoint and models for Copilot Searches * frontend views and comps * Add AlertAssetSearches component for Copilot Searches functionality * Add Graylog query generation and filtering capabilities to Copilot Searches * Add Graylog alert provisioning functionality to Copilot Searches * Add Graylog query generation and alert provisioning types to Copilot Searches * feat: Enhance RuleCard and RuleCardContent components with Graylog provisioning functionality - Added a tooltip and button for provisioning Graylog alerts in RuleCard.vue. - Introduced a new ProvisionGraylogForm.vue component for handling Graylog alert provisioning. - Updated RuleCardContent.vue to display Graylog query information and integrate provisioning logic. - Enhanced modal handling for executing searches and provisioning alerts. - Improved UI elements and added validation for provisioning form inputs. * chore: update frontend dependencies Update various dependencies and devDependencies in the frontend package, including Vue, Tailwind CSS, Shiki, and CodeMirror. Also bumps the package manager to pnpm 10.30.3. * style: lint * chore: set Cursor as default editor for Vue DevTools * refactor props * style: use container queries for Overview page layout Replace viewport-based media queries with container queries to make the layout responsive relative to its parent container. Relates to #728. * feat: add Tools section to navigation menu Introduce a new "Tools" category in the navbar, including links for security assessments and triggers for the Stack Provisioning, Active Response, and Threat Intel components. Relates to #728. * style: use secondary button style for Overview quick actions Add a `secondary` prop to the action button components and apply it to the triggers on the Overview page to update their visual appearance. Relates to #728. * 741 more docs (#744) (#745) * Docs: add supported tool versions page to getting started section * Docs: add Alerting → Shuffle section and video walkthrough to documentation * Docs: add Graylog Threshold Alerts configuration guide to documentation * feat: add Threshold Alerts section to Graylog user interface navigation * feat: move PlatformBadge to common and extract PlatformIcon component Relocate the PlatformBadge component to the common directory to make it reusable across the application. This change also extracts the icon logic into a standalone PlatformIcon component and updates the OS utility to support case-insensitive matching with an updated icon set. Relates to #728. * style: use container queries for AlertsList layout Replace viewport-based media queries with container queries to make the AlertsList header responsive relative to its parent container. This update also refines the Bulk Delete button's styling and positioning. Relates to #728. * style: refine layout and structure of Platform and Severity badges Adjust the PlatformBadge layout to wrap the icon and text within the value slot for improved alignment. Update the SeverityBadge to use a splitted badge type, displaying the specific severity as a label and "Severity" as the value. Relates to #728. * style: refine AlertAssetSearches layout and rules list UI Update the search filters to prioritize the search input and migrate the rules list to use the CardEntity component within a scrollable area. This change also transitions to the common PlatformBadge component and adjusts container padding for better spacing. Relates to #728. * feat: extract search rule form into a modal component Refactor the prebuilt searches interface by moving the search execution logic and results display into a standalone AlertAssetRuleForm component. This form is now presented within a modal when a rule is selected, improving the layout and separation of concerns. Relates to #728. * style: refine search results UI and hit metadata display Update the search results presentation by using Badge components for hit metadata and improving card spacing and scrollbar styling. Relates to #728. * feat: add pagination and case counts to cases listing endpoints * feat: implement pagination and enhance cases listing API response * chore: update current version to 0.1.46 * feat: enhance alert payload validation and fallback handling for timefield * feat: update syscollector configuration for enhanced system inventory collection * feat: add user, group, service, and browser extension monitoring to syscollector configuration * chore: update jose and vite-svg-loader dependencies Relates to #728. * style: move search filters to popover and implement container queries Refine the CoPilot searches interface by moving the platform, severity, and status filters into a popover to optimize horizontal space. This update also transitions the rules grid to use container queries for better responsiveness and introduces a filter reset function with an active filter indicator badge. Relates to #728. * chore: enable format on save and set Prettier as default formatter Configure VS Code settings to automatically format code using the Prettier extension whenever a file is saved. Relates to #728. * style: format * lint * style: refine RuleCard layout and introduce small badge size Update the RuleCard component layout to improve information density, moving severity and platform indicators and utilizing a new small variant for the Badge component. Also adds a `footerBoxClass` prop to CardEntity for more flexible footer styling. Relates to #728. * feat: add PropsList component for dynamic property display Introduce a new PropsList component that renders a list of properties with customizable formatting and optional date detection. This component enhances the UI by allowing for better presentation of key-value pairs, leveraging a transformer function for value formatting. Relates to #728. * refactor: replace static key-value pairs with PropsList component in RuleCardContent Refactor the RuleCardContent component to utilize the new PropsList component for displaying information and risk assessment fields, enhancing maintainability and readability. Additionally, update the layout of the Graylog Query section for improved UI consistency. Relates to #728. * refactor: update Graylog query display and clean up imports in ProvisionGraylogForm and RuleCardContent Refactor the ProvisionGraylogForm component to replace the NCode component with CodeSource for improved code display. Additionally, clean up unused imports in both ProvisionGraylogForm and RuleCardContent components to enhance code clarity and maintainability. Relates to #728. * chore: update eslint-config and @types/node dependencies in package.json Upgrade @antfu/eslint-config to version 7.7.0 and @types/node to version 25.3.5 to ensure compatibility and access to the latest features and fixes. * lint * refactor: replace inline regex with constants for improved readability Refactor the tokens-tool.js and theme.ts files by replacing inline regular expressions with named constants. This change enhances code clarity and maintainability by making the purpose of the regex patterns more explicit. * refactor: replace inline regex with constants across multiple components for improved readability This commit updates various components by replacing inline regular expressions with named constants. This change enhances code clarity and maintainability, making the purpose of the regex patterns more explicit. Affected components include sseClient, AlertWazuhRules, ArtifactsCollect, and others. * refactor: replace inline regex with constants in various components for improved readability This commit updates multiple components by replacing inline regular expressions with named constants, enhancing code clarity and maintainability. Affected components include ListPercentage, CustomerAgents, ScaStats, SocAlertAssetsItem, AIWazuhExclusionRuleButton, VulnerabilityCardContent, and utils. This change makes the purpose of the regex patterns more explicit. * refactor: optimize sorting logic in ScaStats and VulnerabilityStats components This commit refactors the sorting logic in the ScaStats and VulnerabilityStats components by replacing the use of `toSorted` with a more standard approach using `sort`. This change improves code clarity and adheres to ESLint recommendations, enhancing maintainability. * refactor: replace onMounted with onBeforeMount in multiple components for improved lifecycle management This commit updates several components to use onBeforeMount instead of onMounted for loading data, ensuring that data is fetched before the component is rendered. Affected components include AgentsPage, AlertsPage, AlertsView, CaseDetailsView, CasesPage, CasesView, OverviewPage, and various others. This change enhances performance and user experience by reducing the time to display data. * refactor: remove unused onMounted imports across multiple components for improved code clarity This commit updates various components by removing the unused `onMounted` import from Vue, streamlining the code and enhancing readability. Affected components include AgentDataStoreTab, AgentDataStoreTabCompact, VersionUpdateBanner, ExecuteSearchForm, and others. This change contributes to cleaner and more maintainable code. * refactor * chore: add duplication check script and jscpd dependency to package.json This commit introduces a new script, `dup-check`, to the package.json for checking code duplication using jscpd. Additionally, it adds jscpd as a dependency to facilitate this functionality. This change aims to improve code quality by identifying duplicate code segments. * refactor: extract rule header into a separate component for improved code organization This commit introduces a new component, RuleHeader, to encapsulate the display logic for rule details, including platform and severity badges. The ProvisionGraylogForm component is updated to utilize this new component, enhancing code clarity and maintainability. * refactor: replace AlertAssetRuleForm with ExecuteSearchForm and update related components This commit removes the AlertAssetRuleForm component and integrates its functionality into the new ExecuteSearchForm component. The AlertAssetSearches and RuleCard components are updated to utilize ExecuteSearchForm, enhancing code organization and maintainability. Additionally, minor style adjustments are made to modal components for improved layout. * refactor: update Vue components to use shorthand attributes for improved readability This commit modifies several Vue components to utilize shorthand syntax for boolean attributes, enhancing code clarity and consistency. Affected components include ArtifactCardCompact, ScaTable, SearchDialog, FileType, CustomerCreationButton, CustomerInfo, ProfileSettings, SCAReports, StreamingList, SnapshotRepositories, SocAlertsBookmarks, SocCaseAssetLink, and various threat intelligence components. Additionally, a new ESLint rule is added to enforce the use of true attribute shorthand in Vue components. * chore: restore type-check script in package.json for TypeScript validation * precommit-fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Mar 6, 2026 at 15:08 UTC f047c0b8061e2ac72e5a66a58dd1559115e99783
330 files changed +6833 -2015
.vscode/settings.json
+3 -1
@@ -102,5 +102,7 @@
102 "trigger-class",
103 "tab-class",
104 "content-class"
105 - ]
105 + ],
106 + "editor.formatOnSave": true,
107 + "editor.defaultFormatter": "esbenp.prettier-vscode"
108 }
backend/app/customer_provisioning/templates/linux_agent.conf
+21 -9
@@ -142,15 +142,27 @@
142 <config_path>/etc/osquery/osquery.conf</config_path>
143 <add_labels>yes</add_labels>
144 </wodle>
145 - <wodle name="syscollector">
146 - <disabled>no</disabled>
147 - <interval>24h</interval>
148 - <scan_on_start>yes</scan_on_start>
149 - <packages>yes</packages>
150 - <os>yes</os>
151 - <ports all="no">yes</ports>
152 - <processes>yes</processes>
153 - </wodle>
145 + <!-- System inventory -->
146 + <wodle name="syscollector">
147 + <disabled>no</disabled>
148 + <interval>1h</interval>
149 + <scan_on_start>yes</scan_on_start>
150 + <hardware>yes</hardware>
151 + <os>yes</os>
152 + <network>yes</network>
153 + <packages>yes</packages>
154 + <ports all="yes">yes</ports>
155 + <processes>yes</processes>
156 + <users>yes</users>
157 + <groups>yes</groups>
158 + <services>yes</services>
159 + <browser_extensions>yes</browser_extensions>
160 +
161 + <!-- Database synchronization settings -->
162 + <synchronization>
163 + <max_eps>10</max_eps>
164 + </synchronization>
165 + </wodle>
166 <wodle name="command">
167 <disabled>no</disabled>
168 <tag>open-audit</tag>
backend/app/customer_provisioning/templates/windows_agent.conf
+4
@@ -115,6 +115,10 @@
115 <packages>yes</packages>
116 <ports all="no">yes</ports>
117 <processes>yes</processes>
118 + <users>yes</users>
119 + <groups>yes</groups>
120 + <services>yes</services>
121 + <browser_extensions>yes</browser_extensions>
122 <!-- Database synchronization settings -->
123 <synchronization>
124 <max_eps>10</max_eps>
backend/app/incidents/routes/db_operations.py
+49 -10
@@ -147,6 +147,10 @@ from app.incidents.services.db_operations import alerts_total_by_source
147 from app.incidents.services.db_operations import alerts_total_by_tag
148 from app.incidents.services.db_operations import alerts_total_multiple_filters
149 from app.incidents.services.db_operations import case_alert_unlink
150 +from app.incidents.services.db_operations import case_total_for_user
151 +from app.incidents.services.db_operations import cases_closed_for_user
152 +from app.incidents.services.db_operations import cases_in_progress_for_user
153 +from app.incidents.services.db_operations import cases_open_for_user
154 from app.incidents.services.db_operations import create_alert
155 from app.incidents.services.db_operations import create_alert_context
156 from app.incidents.services.db_operations import create_alert_ioc
@@ -1307,12 +1311,32 @@ async def list_alerts_multiple_filters_endpoint(
1311
1312
1313 @incidents_db_operations_router.get("/cases", response_model=CaseOutResponse)
1310 -async def list_cases_endpoint(current_user: User = Depends(AuthHandler().get_current_user), db: AsyncSession = Depends(get_db)):
1311 - """List cases with automatic customer filtering"""
1314 +async def list_cases_endpoint(
1315 + page: int = Query(1, ge=1),
1316 + page_size: int = Query(25, ge=1),
1317 + order: str = Query("desc", regex="^(asc|desc)$"),
1318 + current_user: User = Depends(AuthHandler().get_current_user),
1319 + db: AsyncSession = Depends(get_db),
1320 +):
1321 + """List cases with automatic customer filtering and pagination"""
1322 logger.info(f"Listing cases for user: {current_user.username} with role_id: {current_user.role_id}")
1323
1314 - cases = await list_cases_for_user(current_user, db)
1315 - return CaseOutResponse(cases=cases, success=True, message="Cases retrieved successfully")
1324 + cases = await list_cases_for_user(current_user, db, page, page_size, order)
1325 +
1326 + total = await case_total_for_user(current_user, db)
1327 + open_cases = await cases_open_for_user(current_user, db)
1328 + in_progress = await cases_in_progress_for_user(current_user, db)
1329 + closed = await cases_closed_for_user(current_user, db)
1330 +
1331 + return CaseOutResponse(
1332 + cases=cases,
1333 + total=total,
1334 + open=open_cases,
1335 + in_progress=in_progress,
1336 + closed=closed,
1337 + success=True,
1338 + message="Cases retrieved successfully",
1339 + )
1340
1341
1342 @incidents_db_operations_router.put("/case/status", response_model=CaseOutResponse)
@@ -1525,10 +1549,13 @@ async def delete_case_endpoint(
1549 @incidents_db_operations_router.get("/case/status/{status}", response_model=CaseOutResponse)
1550 async def list_cases_by_status_endpoint(
1551 status: AlertStatus,
1552 + page: int = Query(1, ge=1),
1553 + page_size: int = Query(25, ge=1),
1554 + order: str = Query("desc", regex="^(asc|desc)$"),
1555 current_user: User = Depends(AuthHandler().get_current_user),
1556 db: AsyncSession = Depends(get_db),
1557 ):
1531 - """List cases by status with customer access filtering"""
1558 + """List cases by status with customer access filtering and pagination"""
1559 if status not in AlertStatus:
1560 raise HTTPException(status_code=400, detail="Invalid status")
1561
@@ -1539,14 +1566,26 @@ async def list_cases_by_status_endpoint(
1566
1567 if "*" in accessible_customers:
1568 # Admin/analyst - no filtering needed
1542 - cases = await list_cases_by_status(status.value, db)
1569 + cases = await list_cases_by_status(status.value, db, page=page, page_size=page_size, order=order)
1570 else:
1544 - # Customer user - we need to filter, but there's no direct function for this
1545 - # We'll need to get all cases for the user and then filter by status
1546 - all_user_cases = await list_cases_for_user(current_user, db)
1571 + # Customer user - get paginated cases and filter by status
1572 + all_user_cases = await list_cases_for_user(current_user, db, page, page_size, order)
1573 cases = [case for case in all_user_cases if case.case_status == status.value]
1574
1549 - return CaseOutResponse(cases=cases, success=True, message="Cases retrieved successfully")
1575 + total = await case_total_for_user(current_user, db)
1576 + open_cases = await cases_open_for_user(current_user, db)
1577 + in_progress = await cases_in_progress_for_user(current_user, db)
1578 + closed = await cases_closed_for_user(current_user, db)
1579 +
1580 + return CaseOutResponse(
1581 + cases=cases,
1582 + total=total,
1583 + open=open_cases,
1584 + in_progress=in_progress,
1585 + closed=closed,
1586 + success=True,
1587 + message="Cases retrieved successfully",
1588 + )
1589
1590
1591 @incidents_db_operations_router.get("/case/assigned-to/{assigned_to}", response_model=CaseOutResponse)
backend/app/incidents/schema/db_operations.py
+4
@@ -483,6 +483,10 @@ class CaseOut(BaseModel):
483
484 class CaseOutResponse(BaseModel):
485 cases: List[CaseOut]
486 + total: Optional[int] = None
487 + open: Optional[int] = None
488 + in_progress: Optional[int] = None
489 + closed: Optional[int] = None
490 success: bool
491 message: str
492
backend/app/incidents/services/db_operations.py
+67 -4
@@ -1604,7 +1604,10 @@ async def list_cases(db: AsyncSession) -> List[CaseOut]:
1604 return cases_out
1605
1606
1607 -async def list_cases_by_status(status: str, db: AsyncSession) -> List[CaseOut]:
1607 +async def list_cases_by_status(status: str, db: AsyncSession, page: int = 1, page_size: int = 25, order: str = "desc") -> List[CaseOut]:
1608 + offset = (page - 1) * page_size
1609 + order_by = asc(Case.id) if order == "asc" else desc(Case.id)
1610 +
1611 result = await db.execute(
1612 select(Case)
1613 .where(Case.case_status == status)
@@ -1615,7 +1618,10 @@ async def list_cases_by_status(status: str, db: AsyncSession) -> List[CaseOut]:
1618 selectinload(Case.alerts).selectinload(CaseAlertLink.alert).selectinload(Alert.cases).selectinload(CaseAlertLink.case),
1619 selectinload(Case.alerts).selectinload(CaseAlertLink.alert).selectinload(Alert.iocs).selectinload(AlertToIoC.ioc),
1620 selectinload(Case.comments),
1618 - ),
1621 + )
1622 + .order_by(order_by)
1623 + .offset(offset)
1624 + .limit(page_size),
1625 )
1626 cases = result.scalars().all()
1627 cases_out = []
@@ -2483,11 +2489,65 @@ async def list_alerts_for_user(
2489 return alerts_out
2490
2491
2492 +async def case_total_for_user(user: User, session: AsyncSession) -> int:
2493 + """Get total cases count with customer filtering"""
2494 + base_query = select(func.count(Case.id))
2495 +
2496 + accessible_customers = await customer_access_handler.get_user_accessible_customers(user, session)
2497 + if "*" not in accessible_customers:
2498 + base_query = base_query.where(Case.customer_code.in_(accessible_customers))
2499 +
2500 + result = await session.execute(base_query)
2501 + return result.scalar_one()
2502 +
2503 +
2504 +async def cases_open_for_user(user: User, session: AsyncSession) -> int:
2505 + """Get open cases count with customer filtering"""
2506 + base_query = select(func.count(Case.id)).where(Case.case_status == "OPEN")
2507 +
2508 + accessible_customers = await customer_access_handler.get_user_accessible_customers(user, session)
2509 + if "*" not in accessible_customers:
2510 + base_query = base_query.where(Case.customer_code.in_(accessible_customers))
2511 +
2512 + result = await session.execute(base_query)
2513 + return result.scalar_one()
2514 +
2515 +
2516 +async def cases_in_progress_for_user(user: User, session: AsyncSession) -> int:
2517 + """Get in-progress cases count with customer filtering"""
2518 + base_query = select(func.count(Case.id)).where(Case.case_status == "IN_PROGRESS")
2519 +
2520 + accessible_customers = await customer_access_handler.get_user_accessible_customers(user, session)
2521 + if "*" not in accessible_customers:
2522 + base_query = base_query.where(Case.customer_code.in_(accessible_customers))
2523 +
2524 + result = await session.execute(base_query)
2525 + return result.scalar_one()
2526 +
2527 +
2528 +async def cases_closed_for_user(user: User, session: AsyncSession) -> int:
2529 + """Get closed cases count with customer filtering"""
2530 + base_query = select(func.count(Case.id)).where(Case.case_status == "CLOSED")
2531 +
2532 + accessible_customers = await customer_access_handler.get_user_accessible_customers(user, session)
2533 + if "*" not in accessible_customers:
2534 + base_query = base_query.where(Case.customer_code.in_(accessible_customers))
2535 +
2536 + result = await session.execute(base_query)
2537 + return result.scalar_one()
2538 +
2539 +
2540 async def list_cases_for_user(
2541 user: User,
2542 session: AsyncSession,
2543 + page: int = 1,
2544 + page_size: int = 25,
2545 + order: str = "desc",
2546 ) -> List[CaseOut]:
2490 - """List cases filtered by user's customer access"""
2547 + """List cases filtered by user's customer access with pagination"""
2548 +
2549 + offset = (page - 1) * page_size
2550 + order_by = asc(Case.id) if order == "asc" else desc(Case.id)
2551
2552 base_query = select(Case).options(
2553 selectinload(Case.alerts).selectinload(CaseAlertLink.alert).selectinload(Alert.comments),
@@ -2501,7 +2561,10 @@ async def list_cases_for_user(
2561 # Apply customer filtering
2562 filtered_query = await customer_access_handler.filter_query_by_customer_access(user, session, base_query, Case.customer_code)
2563
2504 - result = await session.execute(filtered_query)
2564 + # Apply ordering and pagination
2565 + final_query = filtered_query.order_by(order_by).offset(offset).limit(page_size)
2566 +
2567 + result = await session.execute(final_query)
2568 cases = result.scalars().all()
2569
2570 # Convert to CaseOut objects (using same logic as list_cases)
backend/app/incidents/services/incident_alert.py
+24 -9
@@ -564,22 +564,37 @@ async def build_alert_payload(
564 # Resolve the actual asset name from the payload using multiple possible fields
565 asset_name_value = await resolve_asset_name_from_payload(field_names.asset_name, alert_payload)
566
567 - # Validate that the required fields exist in the alert_payload
568 - for field_name in [field_names.timefield_name, field_names.alert_title_name]:
569 - if field_name not in alert_payload:
570 - raise HTTPException(
571 - status_code=400,
572 - detail=f"Field name {field_name} not found in alert payload",
573 - )
567 + # Validate alert_title_name exists
568 + if field_names.alert_title_name not in alert_payload:
569 + raise HTTPException(
570 + status_code=400,
571 + detail=f"Field name {field_names.alert_title_name} not found in alert payload",
572 + )
573 +
574 + # Resolve timefield with fallback to 'timestamp'
575 + timefield_value = None
576 + if field_names.timefield_name in alert_payload:
577 + timefield_value = alert_payload[field_names.timefield_name]
578 + elif "timestamp" in alert_payload:
579 + logger.warning(
580 + f"Configured time field '{field_names.timefield_name}' not found in alert payload. " f"Falling back to 'timestamp' field.",
581 + )
582 + timefield_value = alert_payload["timestamp"]
583 + else:
584 + logger.warning(
585 + f"Neither configured time field '{field_names.timefield_name}' nor 'timestamp' found in alert payload. "
586 + f"Using current UTC time as fallback.",
587 + )
588 + timefield_value = datetime.utcnow().isoformat()
589
590 # Clean the alert title to remove BOM and normalize encoding
576 - raw_alert_title = alert_payload[field_names.alert_title_name] if field_names.alert_title_name in alert_payload else None
591 + raw_alert_title = alert_payload.get(field_names.alert_title_name)
592 cleaned_alert_title = clean_alert_title(raw_alert_title) if raw_alert_title else None
593
594 return CreatedAlertPayload(
595 alert_context_payload=await build_alert_context_payload(alert_payload, field_names),
596 asset_payload=asset_name_value,
582 - timefield_payload=alert_payload[field_names.timefield_name] if field_names.timefield_name in alert_payload else None,
597 + timefield_payload=timefield_value,
598 alert_title_payload=cleaned_alert_title,
599 ioc_payload=await build_ioc_payload(alert_payload, field_names),
600 source=syslog_type,
backend/app/integrations/copilot_searches/routes/copilot_searches.py new
+502
@@ -0,0 +1,502 @@
1 +from typing import Optional
2 +
3 +from fastapi import APIRouter
4 +from fastapi import HTTPException
5 +from fastapi import Query
6 +
7 +from app.connectors.graylog.routes.events import get_all_event_definitions
8 +from app.connectors.graylog.schema.events import GraylogEventDefinitionsResponse
9 +from app.integrations.copilot_searches.schema.copilot_searches import (
10 + ExecuteGraylogQueryRequest,
11 +)
12 +from app.integrations.copilot_searches.schema.copilot_searches import (
13 + ExecuteSearchRequest,
14 +)
15 +from app.integrations.copilot_searches.schema.copilot_searches import (
16 + ExecuteSearchResponse,
17 +)
18 +from app.integrations.copilot_searches.schema.copilot_searches import (
19 + GraylogQueryResponse,
20 +)
21 +from app.integrations.copilot_searches.schema.copilot_searches import PlatformFilter
22 +from app.integrations.copilot_searches.schema.copilot_searches import (
23 + ProvisionGraylogAlertRequest,
24 +)
25 +from app.integrations.copilot_searches.schema.copilot_searches import (
26 + ProvisionGraylogAlertResponse,
27 +)
28 +from app.integrations.copilot_searches.schema.copilot_searches import RefreshResponse
29 +from app.integrations.copilot_searches.schema.copilot_searches import RuleDetailResponse
30 +from app.integrations.copilot_searches.schema.copilot_searches import RuleListResponse
31 +from app.integrations.copilot_searches.schema.copilot_searches import RuleSeverity
32 +from app.integrations.copilot_searches.schema.copilot_searches import RuleStatsResponse
33 +from app.integrations.copilot_searches.schema.copilot_searches import RuleStatus
34 +from app.integrations.copilot_searches.services.copilot_searches import (
35 + execute_rule_search,
36 +)
37 +from app.integrations.copilot_searches.services.copilot_searches import (
38 + generate_graylog_query,
39 +)
40 +from app.integrations.copilot_searches.services.copilot_searches import get_rule_by_id
41 +from app.integrations.copilot_searches.services.copilot_searches import get_rule_by_name
42 +from app.integrations.copilot_searches.services.copilot_searches import get_rules_list
43 +from app.integrations.copilot_searches.services.copilot_searches import get_rules_stats
44 +from app.integrations.copilot_searches.services.copilot_searches import (
45 + provision_graylog_alert_from_rule,
46 +)
47 +from app.integrations.copilot_searches.services.copilot_searches import (
48 + refresh_rules_cache,
49 +)
50 +
51 +copilot_searches_router = APIRouter()
52 +
53 +
54 +async def check_if_event_definition_exists(event_definition_title: str) -> bool:
55 + """
56 + Check if an event definition with the given title already exists in Graylog.
57 +
58 + Args:
59 + event_definition_title: The title to check
60 +
61 + Returns:
62 + True if the event definition already exists
63 +
64 + Raises:
65 + HTTPException: If failed to check or if already exists
66 + """
67 + event_definitions_response = await get_all_event_definitions()
68 + if not event_definitions_response.success:
69 + raise HTTPException(
70 + status_code=500,
71 + detail="Failed to collect event definitions from Graylog",
72 + )
73 +
74 + event_definitions_response = GraylogEventDefinitionsResponse(
75 + **event_definitions_response.dict(),
76 + )
77 +
78 + existing_titles = [ed.title for ed in event_definitions_response.event_definitions]
79 +
80 + if event_definition_title in existing_titles:
81 + raise HTTPException(
82 + status_code=400,
83 + detail=f"Event definition '{event_definition_title}' already exists in Graylog",
84 + )
85 +
86 + return False
87 +
88 +
89 +@copilot_searches_router.get(
90 + "",
91 + response_model=RuleListResponse,
92 + description="List all detection rules with optional filtering",
93 +)
94 +async def list_rules(
95 + platform: PlatformFilter = Query(
96 + PlatformFilter.ALL,
97 + description="Filter by platform (linux, windows, all)",
98 + ),
99 + status: Optional[RuleStatus] = Query(
100 + None,
101 + description="Filter by rule status",
102 + ),
103 + severity: Optional[RuleSeverity] = Query(
104 + None,
105 + description="Filter by severity level",
106 + ),
107 + mitre_id: Optional[str] = Query(
108 + None,
109 + description="Filter by MITRE ATT&CK technique ID (e.g., T1136)",
110 + ),
111 + search: Optional[str] = Query(
112 + None,
113 + description="Search in rule name and description",
114 + ),
115 + has_graylog: Optional[bool] = Query(
116 + None,
117 + description="Filter for rules with Graylog queries",
118 + ),
119 + skip: int = Query(0, ge=0, description="Number of rules to skip"),
120 + limit: int = Query(100, ge=1, le=500, description="Maximum rules to return"),
121 +):
122 + """
123 + List all detection rules with optional filtering.
124 +
125 + Supports filtering by:
126 + - **platform**: linux, windows, or all
127 + - **status**: production, experimental, deprecated
128 + - **severity**: low, medium, high, critical
129 + - **mitre_id**: MITRE ATT&CK technique ID
130 + - **search**: Text search in name/description
131 + - **has_graylog**: Filter for rules with Graylog queries
132 + """
133 + result = await get_rules_list(
134 + platform=platform,
135 + status=status,
136 + severity=severity,
137 + mitre_id=mitre_id,
138 + search=search,
139 + has_graylog=has_graylog,
140 + skip=skip,
141 + limit=limit,
142 + )
143 +
144 + return RuleListResponse(**result)
145 +
146 +
147 +@copilot_searches_router.get(
148 + "/linux",
149 + response_model=RuleListResponse,
150 + description="List all Linux detection rules",
151 +)
152 +async def list_linux_rules(
153 + status: Optional[RuleStatus] = Query(None),
154 + severity: Optional[RuleSeverity] = Query(None),
155 + mitre_id: Optional[str] = Query(None),
156 + search: Optional[str] = Query(None),
157 + has_graylog: Optional[bool] = Query(None),
158 + skip: int = Query(0, ge=0),
159 + limit: int = Query(100, ge=1, le=500),
160 +):
161 + """List all Linux detection rules."""
162 + result = await get_rules_list(
163 + platform=PlatformFilter.LINUX,
164 + status=status,
165 + severity=severity,
166 + mitre_id=mitre_id,
167 + search=search,
168 + has_graylog=has_graylog,
169 + skip=skip,
170 + limit=limit,
171 + )
172 +
173 + return RuleListResponse(**result)
174 +
175 +
176 +@copilot_searches_router.get(
177 + "/windows",
178 + response_model=RuleListResponse,
179 + description="List all Windows detection rules",
180 +)
181 +async def list_windows_rules(
182 + status: Optional[RuleStatus] = Query(None),
183 + severity: Optional[RuleSeverity] = Query(None),
184 + mitre_id: Optional[str] = Query(None),
185 + search: Optional[str] = Query(None),
186 + has_graylog: Optional[bool] = Query(None),
187 + skip: int = Query(0, ge=0),
188 + limit: int = Query(100, ge=1, le=500),
189 +):
190 + """List all Windows detection rules."""
191 + result = await get_rules_list(
192 + platform=PlatformFilter.WINDOWS,
193 + status=status,
194 + severity=severity,
195 + mitre_id=mitre_id,
196 + search=search,
197 + has_graylog=has_graylog,
198 + skip=skip,
199 + limit=limit,
200 + )
201 +
202 + return RuleListResponse(**result)
203 +
204 +
205 +@copilot_searches_router.get(
206 + "/stats",
207 + response_model=RuleStatsResponse,
208 + description="Get statistics about loaded detection rules",
209 +)
210 +async def get_rule_stats():
211 + """Get statistics about loaded detection rules."""
212 + result = await get_rules_stats()
213 +
214 + return RuleStatsResponse(**result)
215 +
216 +
217 +@copilot_searches_router.get(
218 + "/id/{rule_id}",
219 + response_model=RuleDetailResponse,
220 + description="Get full details of a specific rule by its ID",
221 +)
222 +async def get_rule_by_id_endpoint(rule_id: str):
223 + """
224 + Get full details of a specific rule by its ID.
225 +
226 + Returns the complete rule including the search query, Graylog query, and raw YAML.
227 + """
228 + rule = await get_rule_by_id(rule_id)
229 +
230 + if rule is None:
231 + raise HTTPException(
232 + status_code=404,
233 + detail=f"Rule with ID '{rule_id}' not found",
234 + )
235 +
236 + return RuleDetailResponse(rule=rule)
237 +
238 +
239 +@copilot_searches_router.get(
240 + "/name/{rule_name:path}",
241 + response_model=RuleDetailResponse,
242 + description="Get full details of a specific rule by its name",
243 +)
244 +async def get_rule_by_name_endpoint(rule_name: str):
245 + """
246 + Get full details of a specific rule by its name.
247 +
248 + Supports fuzzy matching - spaces can be underscores or hyphens.
249 +
250 + Examples:
251 + - `/name/Linux Auditd Add User`
252 + - `/name/linux_auditd_add_user`
253 + - `/name/linux-auditd-add-user`
254 + """
255 + rule = await get_rule_by_name(rule_name)
256 +
257 + if rule is None:
258 + raise HTTPException(
259 + status_code=404,
260 + detail=f"Rule with name '{rule_name}' not found",
261 + )
262 +
263 + return RuleDetailResponse(rule=rule)
264 +
265 +
266 +@copilot_searches_router.get(
267 + "/mitre/{technique_id}",
268 + response_model=RuleListResponse,
269 + description="Get all rules that detect a specific MITRE ATT&CK technique",
270 +)
271 +async def get_rules_by_mitre(
272 + technique_id: str,
273 + platform: PlatformFilter = Query(PlatformFilter.ALL),
274 +):
275 + """
276 + Get all rules that detect a specific MITRE ATT&CK technique.
277 +
278 + Examples:
279 + - `/mitre/T1136` - Account creation
280 + - `/mitre/T1003` - Credential dumping
281 + - `/mitre/T1068` - Privilege escalation
282 + """
283 + result = await get_rules_list(
284 + platform=platform,
285 + mitre_id=technique_id,
286 + )
287 +
288 + return RuleListResponse(**result)
289 +
290 +
291 +@copilot_searches_router.post(
292 + "/refresh",
293 + response_model=RefreshResponse,
294 + description="Manually refresh the rules cache from GitHub",
295 +)
296 +async def refresh_rules():
297 + """
298 + Manually refresh the rules cache from GitHub.
299 +
300 + This fetches the latest rules from the repository and updates the cache.
301 + """
302 + try:
303 + result = await refresh_rules_cache()
304 + return RefreshResponse(**result)
305 + except Exception as e:
306 + raise HTTPException(
307 + status_code=503,
308 + detail=f"Failed to refresh rules: {str(e)}",
309 + )
310 +
311 +
312 +@copilot_searches_router.post(
313 + "/execute",
314 + response_model=ExecuteSearchResponse,
315 + description="Execute a detection rule search against the Wazuh indexer",
316 +)
317 +async def execute_search(request: ExecuteSearchRequest):
318 + """
319 + Execute a detection rule search against the Wazuh indexer.
320 +
321 + This endpoint takes a rule ID and parameters, substitutes the parameters
322 + into the rule's search query, and executes it against the specified index.
323 +
324 + **Required Parameters:**
325 + - **rule_id**: The ID of the rule to execute
326 + - **index_pattern**: The Elasticsearch index pattern to search
327 +
328 + **Optional Parameters:**
329 + - **parameters**: Dictionary of parameter values to substitute
330 + - **size**: Override the default result size
331 +
332 + **Example Request:**
333 + ```json
334 + {
335 + "rule_id": "linux-auditd-add-user-001",
336 + "index_pattern": "wazuh-alerts-*",
337 + "parameters": {
338 + "AGENT_NAME": "my-server",
339 + "CUSTOMER_CODE": "lab",
340 + "START_TIME": "now-24h",
341 + "END_TIME": "now"
342 + },
343 + "size": 50
344 + }
345 + ```
346 + """
347 + try:
348 + result = await execute_rule_search(request)
349 + return result
350 + except ValueError as e:
351 + raise HTTPException(
352 + status_code=400,
353 + detail=str(e),
354 + )
355 + except Exception as e:
356 + raise HTTPException(
357 + status_code=500,
358 + detail=f"Search execution failed: {str(e)}",
359 + )
360 +
361 +
362 +@copilot_searches_router.post(
363 + "/graylog",
364 + response_model=GraylogQueryResponse,
365 + description="Generate a Graylog query from a rule with parameter substitution",
366 +)
367 +async def generate_graylog_query_endpoint(request: ExecuteGraylogQueryRequest):
368 + """
369 + Generate a Graylog query string from a rule with parameter substitution.
370 +
371 + This endpoint takes a rule ID and parameters, substitutes the parameters
372 + into the rule's Graylog query template, and returns the ready-to-use query.
373 +
374 + **Required Parameters:**
375 + - **rule_id**: The ID of the rule to use
376 +
377 + **Optional Parameters:**
378 + - **parameters**: Dictionary of parameter values to substitute
379 +
380 + **Example Request:**
381 + ```json
382 + {
383 + "rule_id": "linux-auditd-add-user-001",
384 + "parameters": {
385 + "AGENT_NAME": "my-server",
386 + "CUSTOMER_CODE": "lab"
387 + }
388 + }
389 + ```
390 +
391 + **Example Response:**
392 + ```json
393 + {
394 + "success": true,
395 + "rule_id": "linux-auditd-add-user-001",
396 + "rule_name": "Linux Auditd Add User",
397 + "graylog_query": "(full_log:/.*useradd.*/ OR full_log:/.*adduser.*/) AND agent_name:my-server",
398 + "original_query": "(full_log:/.*useradd.*/ OR full_log:/.*adduser.*/) AND agent_name:${AGENT_NAME}"
399 + }
400 + ```
401 + """
402 + try:
403 + result = await generate_graylog_query(request)
404 + return result
405 + except ValueError as e:
406 + raise HTTPException(
407 + status_code=400,
408 + detail=str(e),
409 + )
410 + except Exception as e:
411 + raise HTTPException(
412 + status_code=500,
413 + detail=f"Graylog query generation failed: {str(e)}",
414 + )
415 +
416 +
417 +@copilot_searches_router.post(
418 + "/provision/graylog",
419 + response_model=ProvisionGraylogAlertResponse,
420 + description="Provision a Graylog event definition from a CoPilot Search rule",
421 +)
422 +async def provision_graylog_alert(request: ProvisionGraylogAlertRequest):
423 + """
424 + Provision a Graylog event definition from a CoPilot Search rule.
425 +
426 + This endpoint takes a rule ID with a Graylog query and creates a Graylog
427 + event definition that will alert when the query matches. The alert will
428 + include standard field specifications for integration with CoPilot's
429 + incident management workflow.
430 +
431 + **Required Parameters:**
432 + - **rule_id**: The ID of the rule to provision (must have a Graylog query)
433 +
434 + **Optional Parameters:**
435 + - **search_within_seconds**: Time window to search (default: 300 = 5 minutes)
436 + - **execute_every_seconds**: Execution interval (default: 300 = 5 minutes)
437 + - **streams**: List of Graylog stream IDs to limit the search
438 + - **custom_title**: Custom alert title (default: uses rule name)
439 + - **priority**: Alert priority 1-3 (default: 2 or derived from rule severity)
440 + - **event_limit**: Max events per execution (default: 1000)
441 +
442 + **Example Request:**
443 + ```json
444 + {
445 + "rule_id": "linux-auditd-ssh-config-keys-deletion-001",
446 + "search_within_seconds": 300,
447 + "execute_every_seconds": 300,
448 + "custom_title": "SSH Key Deletion Alert",
449 + "priority": 3
450 + }
451 + ```
452 +
453 + **Example Response:**
454 + ```json
455 + {
456 + "success": true,
457 + "message": "Graylog alert 'SSH Key Deletion Alert' provisioned successfully",
458 + "rule_id": "linux-auditd-ssh-config-keys-deletion-001",
459 + "rule_name": "Linux Auditd SSH Config Keys Deletion",
460 + "alert_title": "SSH Key Deletion Alert",
461 + "graylog_query": "(full_log:/.*\\/etc\\/ssh\\/.*/ OR ...) AND ..."
462 + }
463 + ```
464 + """
465 + try:
466 + # Get the rule to determine the alert title for duplicate check
467 + rule = await get_rule_by_id(request.rule_id)
468 + if rule is None:
469 + raise HTTPException(
470 + status_code=404,
471 + detail=f"Rule with ID '{request.rule_id}' not found",
472 + )
473 +
474 + # Check if rule has Graylog query
475 + if rule.graylog is None:
476 + raise HTTPException(
477 + status_code=400,
478 + detail=f"Rule '{request.rule_id}' does not contain a Graylog query",
479 + )
480 +
481 + # Determine the alert title
482 + alert_title = request.custom_title if request.custom_title else rule.name.upper().replace(" ", " - ")
483 +
484 + # Check if event definition already exists
485 + await check_if_event_definition_exists(alert_title)
486 +
487 + # Provision the alert
488 + result = await provision_graylog_alert_from_rule(request)
489 + return result
490 +
491 + except HTTPException:
492 + raise
493 + except ValueError as e:
494 + raise HTTPException(
495 + status_code=400,
496 + detail=str(e),
497 + )
498 + except Exception as e:
499 + raise HTTPException(
500 + status_code=500,
501 + detail=f"Failed to provision Graylog alert: {str(e)}",
502 + )
backend/app/integrations/copilot_searches/schema/copilot_searches.py new
+302
@@ -0,0 +1,302 @@
1 +from datetime import datetime
2 +from enum import Enum
3 +from typing import Any
4 +from typing import Optional
5 +
6 +from pydantic import BaseModel
7 +from pydantic import Field
8 +
9 +
10 +class PlatformFilter(str, Enum):
11 + """Supported platform filters."""
12 +
13 + ALL = "all"
14 + LINUX = "linux"
15 + WINDOWS = "windows"
16 +
17 +
18 +class RuleStatus(str, Enum):
19 + """Rule status types."""
20 +
21 + PRODUCTION = "production"
22 + EXPERIMENTAL = "experimental"
23 + DEPRECATED = "deprecated"
24 +
25 +
26 +class RuleSeverity(str, Enum):
27 + """Rule severity levels."""
28 +
29 + LOW = "low"
30 + MEDIUM = "medium"
31 + HIGH = "high"
32 + CRITICAL = "critical"
33 +
34 +
35 +class ParameterSchema(BaseModel):
36 + """Parameter definition schema."""
37 +
38 + name: str
39 + description: str
40 + type: str
41 + required: bool = False
42 + default: Optional[Any] = None
43 + example: Optional[Any] = None
44 +
45 +
46 +class GraylogQuery(BaseModel):
47 + """Graylog query definition."""
48 +
49 + query: str = Field(..., description="The Graylog search query string")
50 +
51 +
52 +class RuleSummary(BaseModel):
53 + """Lightweight rule summary for list endpoints."""
54 +
55 + id: str
56 + name: str
57 + version: int
58 + status: str
59 + type: str
60 + description: str
61 + author: str
62 + date: str
63 + severity: str
64 + risk_score: int
65 + platform: str
66 + mitre_attack_id: list[str] = []
67 + analytic_story: list[str] = []
68 + cve: list[str] = []
69 + file_path: str
70 + has_graylog_query: bool = False
71 +
72 +
73 +class RuleDetail(BaseModel):
74 + """Full rule details including search query."""
75 +
76 + id: str
77 + name: str
78 + version: int
79 + schema_version: str
80 + status: str
81 + type: str
82 + description: str
83 + author: str
84 + date: str
85 + data_source: list[str]
86 + search: dict
87 + parameters: list[ParameterSchema]
88 + how_to_implement: str
89 + known_false_positives: str
90 + references: list[str]
91 + response: dict
92 + tags: dict
93 + file_path: str
94 + raw_yaml: str
95 + graylog: Optional[GraylogQuery] = None
96 +
97 +
98 +class RuleListResponse(BaseModel):
99 + """Response model for rule listing."""
100 +
101 + total: int
102 + filtered: int
103 + platform: str
104 + rules: list[RuleSummary]
105 + success: bool = True
106 + message: str = "Rules fetched successfully"
107 +
108 +
109 +class RuleStatsResponse(BaseModel):
110 + """Statistics about loaded rules."""
111 +
112 + total_rules: int
113 + by_platform: dict[str, int]
114 + by_status: dict[str, int]
115 + by_severity: dict[str, int]
116 + by_mitre_tactic: dict[str, int]
117 + rules_with_graylog: int
118 + last_refreshed: Optional[datetime]
119 + cache_ttl_minutes: int
120 + success: bool = True
121 + message: str = "Statistics fetched successfully"
122 +
123 +
124 +class RefreshResponse(BaseModel):
125 + """Response model for cache refresh."""
126 +
127 + success: bool
128 + message: str
129 + rules_loaded: int
130 + timestamp: datetime
131 +
132 +
133 +class RuleDetailResponse(BaseModel):
134 + """Response model for single rule detail."""
135 +
136 + success: bool = True
137 + message: str = "Rule fetched successfully"
138 + rule: RuleDetail
139 +
140 +
141 +# =============================================================================
142 +# Search Execution Models
143 +# =============================================================================
144 +
145 +
146 +class ExecuteSearchRequest(BaseModel):
147 + """Request model for executing a rule search."""
148 +
149 + rule_id: str = Field(..., description="The ID of the rule to execute")
150 + index_pattern: str = Field(
151 + ...,
152 + description="The index pattern to search (e.g., 'wazuh-alerts-*')",
153 + example="wazuh-alerts-*",
154 + )
155 + parameters: dict[str, Any] = Field(
156 + default_factory=dict,
157 + description="Parameter values to substitute in the query",
158 + example={
159 + "AGENT_NAME": "my-server",
160 + "CUSTOMER_CODE": "lab",
161 + "START_TIME": "now-24h",
162 + "END_TIME": "now",
163 + },
164 + )
165 + size: Optional[int] = Field(
166 + default=None,
167 + description="Override the default result size from the rule",
168 + ge=1,
169 + le=10000,
170 + )
171 +
172 +
173 +class SearchHit(BaseModel):
174 + """A single search result hit."""
175 +
176 + index: str = Field(..., description="The index the document was found in")
177 + id: str = Field(..., description="The document ID")
178 + score: Optional[float] = Field(None, description="The relevance score")
179 + source: dict[str, Any] = Field(..., description="The document source")
180 +
181 +
182 +class ExecuteSearchResponse(BaseModel):
183 + """Response model for search execution."""
184 +
185 + success: bool = True
186 + message: str = "Search executed successfully"
187 + rule_id: str
188 + rule_name: str
189 + total_hits: int
190 + returned_hits: int
191 + took_ms: int
192 + hits: list[SearchHit]
193 + query_executed: dict = Field(
194 + ...,
195 + description="The actual query that was executed (for debugging)",
196 + )
197 +
198 +
199 +class SearchValidationError(BaseModel):
200 + """Validation error details."""
201 +
202 + parameter: str
203 + message: str
204 +
205 +
206 +class ExecuteSearchErrorResponse(BaseModel):
207 + """Error response for search execution."""
208 +
209 + success: bool = False
210 + message: str
211 + rule_id: Optional[str] = None
212 + validation_errors: list[SearchValidationError] = []
213 +
214 +
215 +# =============================================================================
216 +# Graylog Query Execution Models
217 +# =============================================================================
218 +
219 +
220 +class ExecuteGraylogQueryRequest(BaseModel):
221 + """Request model for executing a Graylog query from a rule."""
222 +
223 + rule_id: str = Field(..., description="The ID of the rule to execute")
224 + parameters: dict[str, Any] = Field(
225 + default_factory=dict,
226 + description="Parameter values to substitute in the query",
227 + example={
228 + "AGENT_NAME": "my-server",
229 + "CUSTOMER_CODE": "lab",
230 + },
231 + )
232 +
233 +
234 +class GraylogQueryResponse(BaseModel):
235 + """Response model for Graylog query generation."""
236 +
237 + success: bool = True
238 + message: str = "Graylog query generated successfully"
239 + rule_id: str
240 + rule_name: str
241 + graylog_query: str = Field(
242 + ...,
243 + description="The Graylog query string with parameters substituted",
244 + )
245 + original_query: str = Field(
246 + ...,
247 + description="The original query template from the rule",
248 + )
249 +
250 +
251 +# =============================================================================
252 +# Graylog Alert Provisioning Models
253 +# =============================================================================
254 +
255 +
256 +class ProvisionGraylogAlertRequest(BaseModel):
257 + """Request model for provisioning a Graylog alert from a CoPilot Search rule."""
258 +
259 + rule_id: str = Field(..., description="The ID of the rule to provision as a Graylog alert")
260 + search_within_seconds: int = Field(
261 + default=300,
262 + description="Time window to search within (in seconds). Default is 300 (5 minutes).",
263 + ge=60,
264 + le=86400,
265 + )
266 + execute_every_seconds: int = Field(
267 + default=300,
268 + description="How often to execute the search (in seconds). Default is 300 (5 minutes).",
269 + ge=60,
270 + le=86400,
271 + )
272 + streams: list[str] = Field(
273 + default_factory=list,
274 + description="Optional list of Graylog stream IDs to limit the search to",
275 + )
276 + custom_title: Optional[str] = Field(
277 + default=None,
278 + description="Optional custom title for the alert. If not provided, uses the rule name.",
279 + )
280 + priority: int = Field(
281 + default=2,
282 + description="Alert priority (1=Low, 2=Normal, 3=High)",
283 + ge=1,
284 + le=3,
285 + )
286 + event_limit: int = Field(
287 + default=1000,
288 + description="Maximum number of events to process per execution",
289 + ge=1,
290 + le=10000,
291 + )
292 +
293 +
294 +class ProvisionGraylogAlertResponse(BaseModel):
295 + """Response model for Graylog alert provisioning."""
296 +
297 + success: bool = True
298 + message: str
299 + rule_id: str
300 + rule_name: str
301 + alert_title: str
302 + graylog_query: str
backend/app/integrations/copilot_searches/services/copilot_searches.py new
+1074
@@ -0,0 +1,1074 @@
1 +import asyncio
2 +import copy
3 +import json
4 +import re
5 +from datetime import datetime
6 +from datetime import timedelta
7 +from typing import Any
8 +from typing import Optional
9 +
10 +import httpx
11 +import yaml
12 +from loguru import logger
13 +
14 +from app.connectors.wazuh_indexer.utils.universal import (
15 + create_wazuh_indexer_client_async,
16 +)
17 +from app.integrations.copilot_searches.schema.copilot_searches import (
18 + ExecuteGraylogQueryRequest,
19 +)
20 +from app.integrations.copilot_searches.schema.copilot_searches import (
21 + ExecuteSearchRequest,
22 +)
23 +from app.integrations.copilot_searches.schema.copilot_searches import (
24 + ExecuteSearchResponse,
25 +)
26 +from app.integrations.copilot_searches.schema.copilot_searches import GraylogQuery
27 +from app.integrations.copilot_searches.schema.copilot_searches import (
28 + GraylogQueryResponse,
29 +)
30 +from app.integrations.copilot_searches.schema.copilot_searches import ParameterSchema
31 +from app.integrations.copilot_searches.schema.copilot_searches import PlatformFilter
32 +from app.integrations.copilot_searches.schema.copilot_searches import (
33 + ProvisionGraylogAlertRequest,
34 +)
35 +from app.integrations.copilot_searches.schema.copilot_searches import (
36 + ProvisionGraylogAlertResponse,
37 +)
38 +from app.integrations.copilot_searches.schema.copilot_searches import RuleDetail
39 +from app.integrations.copilot_searches.schema.copilot_searches import RuleSeverity
40 +from app.integrations.copilot_searches.schema.copilot_searches import RuleStatus
41 +from app.integrations.copilot_searches.schema.copilot_searches import RuleSummary
42 +from app.integrations.copilot_searches.schema.copilot_searches import SearchHit
43 +from app.integrations.copilot_searches.schema.copilot_searches import (
44 + SearchValidationError,
45 +)
46 +from app.integrations.monitoring_alert.schema.provision import (
47 + GraylogAlertProvisionConfig,
48 +)
49 +from app.integrations.monitoring_alert.schema.provision import (
50 + GraylogAlertProvisionFieldSpecItem,
51 +)
52 +from app.integrations.monitoring_alert.schema.provision import (
53 + GraylogAlertProvisionModel,
54 +)
55 +from app.integrations.monitoring_alert.schema.provision import (
56 + GraylogAlertProvisionNotificationSettings,
57 +)
58 +from app.integrations.monitoring_alert.schema.provision import (
59 + GraylogAlertProvisionProvider,
60 +)
61 +from app.integrations.monitoring_alert.services.provision import (
62 + provision_alert_definition,
63 +)
64 +
65 +# =============================================================================
66 +# Configuration
67 +# =============================================================================
68 +
69 +GITHUB_REPO = "socfortress/CoPilot-Search-Queries"
70 +GITHUB_BRANCH = "main"
71 +GITHUB_API_BASE = "https://api.github.com"
72 +GITHUB_RAW_BASE = "https://raw.githubusercontent.com"
73 +
74 +# Cache settings
75 +CACHE_TTL_MINUTES = 30
76 +
77 +
78 +# =============================================================================
79 +# Rules Cache
80 +# =============================================================================
81 +
82 +
83 +class RulesCache:
84 + """
85 + In-memory cache for detection rules fetched from GitHub.
86 +
87 + Handles fetching, parsing, and caching YAML rules from the repository.
88 + """
89 +
90 + def __init__(self):
91 + self._rules: dict[str, dict] = {} # id -> rule data
92 + self._rules_by_name: dict[str, str] = {} # normalized name -> id
93 + self._last_refresh: Optional[datetime] = None
94 + self._lock = asyncio.Lock()
95 +
96 + @property
97 + def is_stale(self) -> bool:
98 + """Check if cache needs refresh."""
99 + if self._last_refresh is None:
100 + return True
101 + age = datetime.utcnow() - self._last_refresh
102 + return age > timedelta(minutes=CACHE_TTL_MINUTES)
103 +
104 + @property
105 + def cache_age_minutes(self) -> Optional[float]:
106 + """Get cache age in minutes."""
107 + if self._last_refresh is None:
108 + return None
109 + age = datetime.utcnow() - self._last_refresh
110 + return age.total_seconds() / 60
111 +
112 + @property
113 + def last_refresh(self) -> Optional[datetime]:
114 + """Get last refresh timestamp."""
115 + return self._last_refresh
116 +
117 + @property
118 + def rules_count(self) -> int:
119 + """Get number of cached rules."""
120 + return len(self._rules)
121 +
122 + async def ensure_loaded(self):
123 + """Ensure rules are loaded, refreshing if stale."""
124 + if self.is_stale:
125 + await self.refresh()
126 +
127 + async def refresh(self) -> int:
128 + """
129 + Refresh rules cache from GitHub repository.
130 +
131 + Returns:
132 + Number of rules loaded
133 + """
134 + async with self._lock:
135 + logger.info("Refreshing rules cache from GitHub...")
136 +
137 + rules = await self._fetch_all_rules()
138 +
139 + self._rules = {}
140 + self._rules_by_name = {}
141 +
142 + for rule in rules:
143 + rule_id = rule.get("id", "")
144 + rule_name = rule.get("name", "")
145 +
146 + self._rules[rule_id] = rule
147 +
148 + # Index by normalized name for lookup
149 + normalized_name = self._normalize_name(rule_name)
150 + self._rules_by_name[normalized_name] = rule_id
151 +
152 + self._last_refresh = datetime.utcnow()
153 + logger.info(f"Loaded {len(self._rules)} rules from GitHub")
154 +
155 + return len(self._rules)
156 +
157 + async def _fetch_all_rules(self) -> list[dict]:
158 + """Fetch all YAML rules from GitHub repository."""
159 + rules = []
160 +
161 + async with httpx.AsyncClient(timeout=30.0) as client:
162 + # Get the directory tree for detections
163 + tree_url = f"{GITHUB_API_BASE}/repos/{GITHUB_REPO}/git/trees/{GITHUB_BRANCH}" f"?recursive=1"
164 +
165 + response = await client.get(tree_url)
166 + response.raise_for_status()
167 +
168 + tree_data = response.json()
169 +
170 + # Filter for YAML files in detections directory
171 + yaml_files = [
172 + item
173 + for item in tree_data.get("tree", [])
174 + if item["path"].startswith("detections/") and item["path"].endswith(".yaml") and item["type"] == "blob"
175 + ]
176 +
177 + logger.info(f"Found {len(yaml_files)} YAML files in repository")
178 +
179 + # Fetch each YAML file
180 + tasks = [self._fetch_yaml_file(client, file_info["path"]) for file_info in yaml_files]
181 +
182 + results = await asyncio.gather(*tasks, return_exceptions=True)
183 +
184 + for result in results:
185 + if isinstance(result, Exception):
186 + logger.warning(f"Failed to fetch rule: {result}")
187 + elif result is not None:
188 + rules.append(result)
189 +
190 + return rules
191 +
192 + async def _fetch_yaml_file(
193 + self,
194 + client: httpx.AsyncClient,
195 + file_path: str,
196 + ) -> Optional[dict]:
197 + """Fetch and parse a single YAML file from GitHub."""
198 + try:
199 + raw_url = f"{GITHUB_RAW_BASE}/{GITHUB_REPO}/{GITHUB_BRANCH}/{file_path}"
200 +
201 + response = await client.get(raw_url)
202 + response.raise_for_status()
203 +
204 + raw_yaml = response.text
205 + rule_data = yaml.safe_load(raw_yaml)
206 +
207 + if not isinstance(rule_data, dict):
208 + logger.warning(f"Invalid YAML structure in {file_path}")
209 + return None
210 +
211 + # Add metadata
212 + rule_data["_file_path"] = file_path
213 + rule_data["_raw_yaml"] = raw_yaml
214 + rule_data["_platform"] = self._detect_platform(file_path, rule_data)
215 + rule_data["_has_graylog"] = "graylog" in rule_data and bool(rule_data.get("graylog", {}).get("query"))
216 +
217 + return rule_data
218 +
219 + except Exception as e:
220 + logger.warning(f"Error fetching {file_path}: {e}")
221 + return None
222 +
223 + def _detect_platform(self, file_path: str, rule_data: dict) -> str:
224 + """Detect the platform (Linux/Windows) for a rule."""
225 + path_lower = file_path.lower()
226 +
227 + # Check path first
228 + if "/linux/" in path_lower:
229 + return "linux"
230 + if "/windows/" in path_lower:
231 + return "windows"
232 +
233 + # Check tags
234 + asset_type = rule_data.get("tags", {}).get("asset_type", "").lower()
235 + if "linux" in asset_type:
236 + return "linux"
237 + if "windows" in asset_type:
238 + return "windows"
239 +
240 + # Check rule name
241 + name_lower = rule_data.get("name", "").lower()
242 + if "linux" in name_lower:
243 + return "linux"
244 + if "windows" in name_lower or "powershell" in name_lower:
245 + return "windows"
246 +
247 + return "unknown"
248 +
249 + def _normalize_name(self, name: str) -> str:
250 + """Normalize rule name for lookup."""
251 + return name.lower().strip().replace(" ", "_").replace("-", "_")
252 +
253 + def get_all_rules(self) -> list[dict]:
254 + """Get all cached rules."""
255 + return list(self._rules.values())
256 +
257 + def get_rule_by_id(self, rule_id: str) -> Optional[dict]:
258 + """Get a rule by its ID."""
259 + return self._rules.get(rule_id)
260 +
261 + def get_rule_by_name(self, name: str) -> Optional[dict]:
262 + """Get a rule by its name (fuzzy match)."""
263 + normalized = self._normalize_name(name)
264 +
265 + # Exact match
266 + if normalized in self._rules_by_name:
267 + rule_id = self._rules_by_name[normalized]
268 + return self._rules.get(rule_id)
269 +
270 + # Partial match
271 + for stored_name, rule_id in self._rules_by_name.items():
272 + if normalized in stored_name or stored_name in normalized:
273 + return self._rules.get(rule_id)
274 +
275 + return None
276 +
277 + def filter_rules(
278 + self,
279 + platform: PlatformFilter = PlatformFilter.ALL,
280 + status: Optional[RuleStatus] = None,
281 + severity: Optional[RuleSeverity] = None,
282 + mitre_id: Optional[str] = None,
283 + search: Optional[str] = None,
284 + has_graylog: Optional[bool] = None,
285 + ) -> list[dict]:
286 + """Filter rules based on criteria."""
287 + results = []
288 +
289 + for rule in self._rules.values():
290 + # Platform filter
291 + if platform != PlatformFilter.ALL:
292 + rule_platform = rule.get("_platform", "unknown")
293 + if rule_platform != platform.value:
294 + continue
295 +
296 + # Status filter
297 + if status is not None:
298 + rule_status = rule.get("status", "").lower()
299 + if rule_status != status.value:
300 + continue
301 +
302 + # Severity filter
303 + if severity is not None:
304 + rule_severity = rule.get("response", {}).get("severity", "").lower()
305 + if rule_severity != severity.value:
306 + continue
307 +
308 + # MITRE ATT&CK filter
309 + if mitre_id is not None:
310 + mitre_ids = rule.get("tags", {}).get("mitre_attack_id", [])
311 + if not any(mitre_id.upper() in m.upper() for m in mitre_ids):
312 + continue
313 +
314 + # Text search (name, description)
315 + if search is not None:
316 + search_lower = search.lower()
317 + name = rule.get("name", "").lower()
318 + description = rule.get("description", "").lower()
319 + if search_lower not in name and search_lower not in description:
320 + continue
321 +
322 + # Graylog query filter
323 + if has_graylog is not None:
324 + rule_has_graylog = rule.get("_has_graylog", False)
325 + if rule_has_graylog != has_graylog:
326 + continue
327 +
328 + results.append(rule)
329 +
330 + return results
331 +
332 + def get_stats(self) -> dict:
333 + """Get statistics about cached rules."""
334 + stats = {
335 + "total_rules": len(self._rules),
336 + "by_platform": {},
337 + "by_status": {},
338 + "by_severity": {},
339 + "by_mitre_tactic": {},
340 + "rules_with_graylog": 0,
341 + }
342 +
343 + for rule in self._rules.values():
344 + # By platform
345 + platform = rule.get("_platform", "unknown")
346 + stats["by_platform"][platform] = stats["by_platform"].get(platform, 0) + 1
347 +
348 + # By status
349 + status = rule.get("status", "unknown")
350 + stats["by_status"][status] = stats["by_status"].get(status, 0) + 1
351 +
352 + # By severity
353 + severity = rule.get("response", {}).get("severity", "unknown")
354 + stats["by_severity"][severity] = stats["by_severity"].get(severity, 0) + 1
355 +
356 + # By MITRE tactic (extract tactic from technique ID)
357 + mitre_ids = rule.get("tags", {}).get("mitre_attack_id", [])
358 + for mitre_id in mitre_ids:
359 + # Extract base technique (e.g., T1136 from T1136.001)
360 + base_technique = mitre_id.split(".")[0] if "." in mitre_id else mitre_id
361 + stats["by_mitre_tactic"][base_technique] = stats["by_mitre_tactic"].get(base_technique, 0) + 1
362 +
363 + # Count rules with Graylog queries
364 + if rule.get("_has_graylog", False):
365 + stats["rules_with_graylog"] += 1
366 +
367 + return stats
368 +
369 +
370 +# =============================================================================
371 +# Helper Functions
372 +# =============================================================================
373 +
374 +
375 +def rule_to_summary(rule: dict) -> RuleSummary:
376 + """Convert a raw rule dict to a RuleSummary model."""
377 + tags = rule.get("tags", {})
378 + response = rule.get("response", {})
379 +
380 + return RuleSummary(
381 + id=rule.get("id", ""),
382 + name=rule.get("name", ""),
383 + version=rule.get("version", 1),
384 + status=rule.get("status", "unknown"),
385 + type=rule.get("type", "unknown"),
386 + description=rule.get("description", ""),
387 + author=rule.get("author", ""),
388 + date=rule.get("date", ""),
389 + severity=response.get("severity", "medium"),
390 + risk_score=response.get("risk_score", 0),
391 + platform=rule.get("_platform", "unknown"),
392 + mitre_attack_id=tags.get("mitre_attack_id", []),
393 + analytic_story=tags.get("analytic_story", []),
394 + cve=tags.get("cve", []),
395 + file_path=rule.get("_file_path", ""),
396 + has_graylog_query=rule.get("_has_graylog", False),
397 + )
398 +
399 +
400 +def rule_to_detail(rule: dict) -> RuleDetail:
401 + """Convert a raw rule dict to a RuleDetail model."""
402 + # Parse parameters
403 + params = []
404 + for name, param_data in rule.get("parameters", {}).items():
405 + params.append(
406 + ParameterSchema(
407 + name=name,
408 + description=param_data.get("description", ""),
409 + type=param_data.get("type", "string"),
410 + required=param_data.get("required", False),
411 + default=param_data.get("default"),
412 + example=param_data.get("example"),
413 + ),
414 + )
415 +
416 + # Parse Graylog query if present
417 + graylog = None
418 + graylog_data = rule.get("graylog")
419 + if graylog_data and isinstance(graylog_data, dict) and graylog_data.get("query"):
420 + graylog = GraylogQuery(query=graylog_data.get("query", ""))
421 +
422 + return RuleDetail(
423 + id=rule.get("id", ""),
424 + name=rule.get("name", ""),
425 + version=rule.get("version", 1),
426 + schema_version=rule.get("schema_version", "1.0"),
427 + status=rule.get("status", "unknown"),
428 + type=rule.get("type", "unknown"),
429 + description=rule.get("description", ""),
430 + author=rule.get("author", ""),
431 + date=rule.get("date", ""),
432 + data_source=rule.get("data_source", []),
433 + search=rule.get("search", {}),
434 + parameters=params,
435 + how_to_implement=rule.get("how_to_implement", ""),
436 + known_false_positives=rule.get("known_false_positives", ""),
437 + references=rule.get("references", []),
438 + response=rule.get("response", {}),
439 + tags=rule.get("tags", {}),
440 + file_path=rule.get("_file_path", ""),
441 + raw_yaml=rule.get("_raw_yaml", ""),
442 + graylog=graylog,
443 + )
444 +
445 +
446 +def _convert_seconds_to_milliseconds(seconds: int) -> int:
447 + """Convert seconds to milliseconds."""
448 + return seconds * 1000
449 +
450 +
451 +def _get_alert_source_from_rule(rule: dict) -> str:
452 + """
453 + Determine the alert source based on rule metadata.
454 +
455 + Args:
456 + rule: The rule dictionary
457 +
458 + Returns:
459 + Alert source string (e.g., "WAZUH", "LINUX_AUDITD", etc.)
460 + """
461 + # Check data_source field
462 + data_sources = rule.get("data_source", [])
463 + if data_sources:
464 + # Use first data source, normalized
465 + source = data_sources[0].upper().replace(" ", "_").replace("-", "_")
466 + return source
467 +
468 + # Check platform
469 + platform = rule.get("_platform", "unknown")
470 + if platform == "linux":
471 + return "LINUX"
472 + if platform == "windows":
473 + return "WINDOWS"
474 +
475 + return "COPILOT_SEARCH"
476 +
477 +
478 +def _get_priority_from_severity(severity: str) -> int:
479 + """
480 + Map rule severity to Graylog priority.
481 +
482 + Args:
483 + severity: Rule severity (low, medium, high, critical)
484 +
485 + Returns:
486 + Graylog priority (1=Low, 2=Normal, 3=High)
487 + """
488 + severity_map = {
489 + "low": 1,
490 + "medium": 2,
491 + "high": 3,
492 + "critical": 3,
493 + }
494 + return severity_map.get(severity.lower(), 2)
495 +
496 +
497 +# =============================================================================
498 +# Global Cache Instance
499 +# =============================================================================
500 +
501 +rules_cache = RulesCache()
502 +
503 +
504 +# =============================================================================
505 +# Service Functions
506 +# =============================================================================
507 +
508 +
509 +async def get_rules_list(
510 + platform: PlatformFilter = PlatformFilter.ALL,
511 + status: Optional[RuleStatus] = None,
512 + severity: Optional[RuleSeverity] = None,
513 + mitre_id: Optional[str] = None,
514 + search: Optional[str] = None,
515 + has_graylog: Optional[bool] = None,
516 + skip: int = 0,
517 + limit: int = 100,
518 +) -> dict:
519 + """
520 + Get filtered list of detection rules.
521 +
522 + Args:
523 + platform: Filter by platform (linux, windows, all)
524 + status: Filter by rule status
525 + severity: Filter by severity level
526 + mitre_id: Filter by MITRE ATT&CK technique ID
527 + search: Text search in name/description
528 + has_graylog: Filter for rules with Graylog queries
529 + skip: Number of rules to skip
530 + limit: Maximum rules to return
531 +
532 + Returns:
533 + Dictionary with total, filtered count, platform, and rules list
534 + """
535 + await rules_cache.ensure_loaded()
536 +
537 + # Filter rules
538 + filtered_rules = rules_cache.filter_rules(
539 + platform=platform,
540 + status=status,
541 + severity=severity,
542 + mitre_id=mitre_id,
543 + search=search,
544 + has_graylog=has_graylog,
545 + )
546 +
547 + # Sort by name
548 + filtered_rules.sort(key=lambda r: r.get("name", "").lower())
549 +
550 + # Paginate
551 + total_filtered = len(filtered_rules)
552 + paginated = filtered_rules[skip : skip + limit]
553 +
554 + # Convert to summaries
555 + summaries = [rule_to_summary(rule) for rule in paginated]
556 +
557 + return {
558 + "total": rules_cache.rules_count,
559 + "filtered": total_filtered,
560 + "platform": platform.value,
561 + "rules": summaries,
562 + }
563 +
564 +
565 +async def get_rule_by_id(rule_id: str) -> Optional[RuleDetail]:
566 + """
567 + Get full details of a rule by its ID.
568 +
569 + Args:
570 + rule_id: The rule ID
571 +
572 + Returns:
573 + RuleDetail or None if not found
574 + """
575 + await rules_cache.ensure_loaded()
576 +
577 + rule = rules_cache.get_rule_by_id(rule_id)
578 +
579 + if rule is None:
580 + return None
581 +
582 + return rule_to_detail(rule)
583 +
584 +
585 +async def get_rule_by_name(rule_name: str) -> Optional[RuleDetail]:
586 + """
587 + Get full details of a rule by its name (fuzzy match).
588 +
589 + Args:
590 + rule_name: The rule name (supports fuzzy matching)
591 +
592 + Returns:
593 + RuleDetail or None if not found
594 + """
595 + await rules_cache.ensure_loaded()
596 +
597 + rule = rules_cache.get_rule_by_name(rule_name)
598 +
599 + if rule is None:
600 + return None
601 +
602 + return rule_to_detail(rule)
603 +
604 +
605 +async def get_rules_stats() -> dict:
606 + """
607 + Get statistics about loaded detection rules.
608 +
609 + Returns:
610 + Dictionary with rule statistics
611 + """
612 + await rules_cache.ensure_loaded()
613 +
614 + stats = rules_cache.get_stats()
615 +
616 + return {
617 + "total_rules": stats["total_rules"],
618 + "by_platform": stats["by_platform"],
619 + "by_status": stats["by_status"],
620 + "by_severity": stats["by_severity"],
621 + "by_mitre_tactic": stats["by_mitre_tactic"],
622 + "rules_with_graylog": stats["rules_with_graylog"],
623 + "last_refreshed": rules_cache.last_refresh,
624 + "cache_ttl_minutes": CACHE_TTL_MINUTES,
625 + }
626 +
627 +
628 +async def refresh_rules_cache() -> dict:
629 + """
630 + Manually refresh the rules cache from GitHub.
631 +
632 + Returns:
633 + Dictionary with refresh results
634 + """
635 + rules_loaded = await rules_cache.refresh()
636 +
637 + return {
638 + "success": True,
639 + "message": "Rules cache refreshed successfully",
640 + "rules_loaded": rules_loaded,
641 + "timestamp": datetime.utcnow(),
642 + }
643 +
644 +
645 +async def get_cache_health() -> dict:
646 + """
647 + Get health/status information about the rules cache.
648 +
649 + Returns:
650 + Dictionary with cache health information
651 + """
652 + return {
653 + "status": "healthy" if not rules_cache.is_stale else "stale",
654 + "rules_loaded": rules_cache.rules_count,
655 + "cache_age_minutes": rules_cache.cache_age_minutes,
656 + "github_repo": GITHUB_REPO,
657 + }
658 +
659 +
660 +# =============================================================================
661 +# Search Execution Functions
662 +# =============================================================================
663 +
664 +
665 +def _substitute_parameters(obj: Any, parameters: dict[str, Any]) -> Any:
666 + """
667 + Recursively substitute ${PARAM_NAME} placeholders in a query object.
668 +
669 + Args:
670 + obj: The object to substitute parameters in (dict, list, or str)
671 + parameters: Dictionary of parameter names to values
672 +
673 + Returns:
674 + The object with parameters substituted
675 + """
676 + if isinstance(obj, str):
677 + # Find all ${PARAM_NAME} patterns and replace them
678 + pattern = r"\$\{([^}]+)\}"
679 +
680 + def replacer(match):
681 + param_name = match.group(1)
682 + if param_name in parameters:
683 + value = parameters[param_name]
684 + # If the entire string is just the placeholder, return the value directly
685 + # This preserves types (int, bool, etc.)
686 + if match.group(0) == obj:
687 + return value
688 + # Otherwise, convert to string for embedding
689 + return str(value)
690 + # Return original if parameter not found
691 + return match.group(0)
692 +
693 + # Check if entire string is a single placeholder
694 + full_match = re.fullmatch(pattern, obj)
695 + if full_match:
696 + param_name = full_match.group(1)
697 + if param_name in parameters:
698 + return parameters[param_name]
699 +
700 + # Otherwise do string substitution
701 + return re.sub(pattern, replacer, obj)
702 +
703 + elif isinstance(obj, dict):
704 + return {key: _substitute_parameters(value, parameters) for key, value in obj.items()}
705 +
706 + elif isinstance(obj, list):
707 + return [_substitute_parameters(item, parameters) for item in obj]
708 +
709 + else:
710 + return obj
711 +
712 +
713 +def _validate_parameters(
714 + rule: dict,
715 + provided_params: dict[str, Any],
716 +) -> tuple[dict[str, Any], list[SearchValidationError]]:
717 + """
718 + Validate and merge provided parameters with defaults.
719 +
720 + Args:
721 + rule: The rule definition
722 + provided_params: Parameters provided by the user
723 +
724 + Returns:
725 + Tuple of (merged_params, validation_errors)
726 + """
727 + errors: list[SearchValidationError] = []
728 + merged_params: dict[str, Any] = {}
729 +
730 + rule_params = rule.get("parameters", {})
731 +
732 + for param_name, param_def in rule_params.items():
733 + is_required = param_def.get("required", False)
734 + default_value = param_def.get("default")
735 +
736 + if param_name in provided_params:
737 + # User provided the parameter
738 + merged_params[param_name] = provided_params[param_name]
739 + elif default_value is not None:
740 + # Use default value
741 + merged_params[param_name] = default_value
742 + elif is_required:
743 + # Required parameter missing
744 + errors.append(
745 + SearchValidationError(
746 + parameter=param_name,
747 + message=f"Required parameter '{param_name}' is missing. {param_def.get('description', '')}",
748 + ),
749 + )
750 +
751 + return merged_params, errors
752 +
753 +
754 +async def execute_rule_search(
755 + request: ExecuteSearchRequest,
756 +) -> ExecuteSearchResponse:
757 + """
758 + Execute a search against the Wazuh indexer using a rule definition.
759 +
760 + Args:
761 + request: The search execution request
762 +
763 + Returns:
764 + ExecuteSearchResponse with search results
765 +
766 + Raises:
767 + ValueError: If the rule is not found or validation fails
768 + """
769 + await rules_cache.ensure_loaded()
770 +
771 + # Get the rule
772 + rule = rules_cache.get_rule_by_id(request.rule_id)
773 + if rule is None:
774 + raise ValueError(f"Rule with ID '{request.rule_id}' not found")
775 +
776 + # Add INDEX_PATTERN to provided parameters
777 + all_params = {**request.parameters, "INDEX_PATTERN": request.index_pattern}
778 +
779 + # Validate parameters
780 + merged_params, validation_errors = _validate_parameters(rule, all_params)
781 +
782 + if validation_errors:
783 + error_messages = [f"{e.parameter}: {e.message}" for e in validation_errors]
784 + raise ValueError(f"Parameter validation failed: {'; '.join(error_messages)}")
785 +
786 + # Get the search definition from the rule
787 + search_def = rule.get("search", {})
788 + if not search_def:
789 + raise ValueError(f"Rule '{request.rule_id}' does not contain a search definition")
790 +
791 + # Build the query with parameter substitution
792 + query = search_def.get("query", {})
793 + substituted_query = _substitute_parameters(copy.deepcopy(query), merged_params)
794 +
795 + # Build the full search body
796 + search_body: dict[str, Any] = {
797 + "query": substituted_query,
798 + }
799 +
800 + # Add size (from request override, rule definition, or default)
801 + if request.size is not None:
802 + search_body["size"] = request.size
803 + elif "size" in search_def:
804 + search_body["size"] = search_def["size"]
805 + else:
806 + search_body["size"] = 100
807 +
808 + # Add sort if defined
809 + if "sort" in search_def:
810 + search_body["sort"] = _substitute_parameters(
811 + copy.deepcopy(search_def["sort"]),
812 + merged_params,
813 + )
814 +
815 + # Add _source if defined
816 + if "_source" in search_def:
817 + search_body["_source"] = search_def["_source"]
818 +
819 + logger.info(f"Executing search for rule '{request.rule_id}' on index '{request.index_pattern}'")
820 + logger.debug(f"Search body: {json.dumps(search_body, indent=2)}")
821 +
822 + # Create the async Elasticsearch client
823 + es_client = await create_wazuh_indexer_client_async()
824 +
825 + try:
826 + # Execute the search
827 + response = await es_client.search(
828 + index=request.index_pattern,
829 + body=search_body,
830 + )
831 +
832 + # Parse the response
833 + hits_data = response.get("hits", {})
834 + total_hits = hits_data.get("total", {})
835 + if isinstance(total_hits, dict):
836 + total_count = total_hits.get("value", 0)
837 + else:
838 + total_count = total_hits
839 +
840 + hits = []
841 + for hit in hits_data.get("hits", []):
842 + hits.append(
843 + SearchHit(
844 + index=hit.get("_index", ""),
845 + id=hit.get("_id", ""),
846 + score=hit.get("_score"),
847 + source=hit.get("_source", {}),
848 + ),
849 + )
850 +
851 + return ExecuteSearchResponse(
852 + success=True,
853 + message="Search executed successfully",
854 + rule_id=request.rule_id,
855 + rule_name=rule.get("name", ""),
856 + total_hits=total_count,
857 + returned_hits=len(hits),
858 + took_ms=response.get("took", 0),
859 + hits=hits,
860 + query_executed=search_body,
861 + )
862 +
863 + except Exception as e:
864 + logger.error(f"Search execution failed: {e}")
865 + raise ValueError(f"Search execution failed: {str(e)}")
866 +
867 + finally:
868 + # Close the client
869 + await es_client.close()
870 +
871 +
872 +# =============================================================================
873 +# Graylog Query Functions
874 +# =============================================================================
875 +
876 +
877 +async def generate_graylog_query(
878 + request: ExecuteGraylogQueryRequest,
879 +) -> GraylogQueryResponse:
880 + """
881 + Generate a Graylog query string from a rule with parameter substitution.
882 +
883 + Args:
884 + request: The Graylog query request
885 +
886 + Returns:
887 + GraylogQueryResponse with the substituted query
888 +
889 + Raises:
890 + ValueError: If the rule is not found or has no Graylog query
891 + """
892 + await rules_cache.ensure_loaded()
893 +
894 + # Get the rule
895 + rule = rules_cache.get_rule_by_id(request.rule_id)
896 + if rule is None:
897 + raise ValueError(f"Rule with ID '{request.rule_id}' not found")
898 +
899 + # Check if rule has Graylog query
900 + graylog_data = rule.get("graylog")
901 + if not graylog_data or not isinstance(graylog_data, dict):
902 + raise ValueError(f"Rule '{request.rule_id}' does not contain a Graylog query")
903 +
904 + original_query = graylog_data.get("query", "")
905 + if not original_query:
906 + raise ValueError(f"Rule '{request.rule_id}' has an empty Graylog query")
907 +
908 + # Validate parameters
909 + merged_params, validation_errors = _validate_parameters(rule, request.parameters)
910 +
911 + if validation_errors:
912 + error_messages = [f"{e.parameter}: {e.message}" for e in validation_errors]
913 + raise ValueError(f"Parameter validation failed: {'; '.join(error_messages)}")
914 +
915 + # Substitute parameters in the Graylog query
916 + substituted_query = _substitute_parameters(original_query, merged_params)
917 +
918 + logger.info(f"Generated Graylog query for rule '{request.rule_id}'")
919 +
920 + return GraylogQueryResponse(
921 + success=True,
922 + message="Graylog query generated successfully",
923 + rule_id=request.rule_id,
924 + rule_name=rule.get("name", ""),
925 + graylog_query=substituted_query,
926 + original_query=original_query,
927 + )
928 +
929 +
930 +# =============================================================================
931 +# Graylog Alert Provisioning Functions
932 +# =============================================================================
933 +
934 +
935 +async def provision_graylog_alert_from_rule(
936 + request: ProvisionGraylogAlertRequest,
937 +) -> ProvisionGraylogAlertResponse:
938 + """
939 + Provision a Graylog event definition from a CoPilot Search rule.
940 +
941 + This takes a rule with a Graylog query and creates a Graylog event definition
942 + that will alert when the query matches.
943 +
944 + Args:
945 + request: The provisioning request
946 +
947 + Returns:
948 + ProvisionGraylogAlertResponse with the result
949 +
950 + Raises:
951 + ValueError: If the rule is not found or has no Graylog query
952 + """
953 + await rules_cache.ensure_loaded()
954 +
955 + # Get the rule
956 + rule = rules_cache.get_rule_by_id(request.rule_id)
957 + if rule is None:
958 + raise ValueError(f"Rule with ID '{request.rule_id}' not found")
959 +
960 + # Check if rule has Graylog query
961 + graylog_data = rule.get("graylog")
962 + if not graylog_data or not isinstance(graylog_data, dict):
963 + raise ValueError(f"Rule '{request.rule_id}' does not contain a Graylog query")
964 +
965 + graylog_query = graylog_data.get("query", "")
966 + if not graylog_query:
967 + raise ValueError(f"Rule '{request.rule_id}' has an empty Graylog query")
968 +
969 + # Get rule metadata
970 + rule_name = rule.get("name", request.rule_id)
971 + rule_description = rule.get("description", "")
972 + rule_severity = rule.get("response", {}).get("severity", "medium")
973 + alert_source = _get_alert_source_from_rule(rule)
974 +
975 + # Determine the alert title
976 + alert_title = request.custom_title if request.custom_title else rule_name.upper().replace(" ", " - ")
977 +
978 + # Determine priority from rule severity or request
979 + priority = request.priority if request.priority != 2 else _get_priority_from_severity(rule_severity)
980 +
981 + logger.info(f"Provisioning Graylog alert for rule '{request.rule_id}' with title '{alert_title}'")
982 +
983 + # Build the Graylog event definition model
984 + alert_model = GraylogAlertProvisionModel(
985 + title=alert_title,
986 + description=rule_description,
987 + priority=priority,
988 + config=GraylogAlertProvisionConfig(
989 + type="aggregation-v1",
990 + query=graylog_query,
991 + query_parameters=[],
992 + streams=request.streams,
993 + group_by=[],
994 + series=[],
995 + conditions={
996 + "expression": None,
997 + },
998 + search_within_ms=_convert_seconds_to_milliseconds(request.search_within_seconds),
999 + execute_every_ms=_convert_seconds_to_milliseconds(request.execute_every_seconds),
1000 + event_limit=request.event_limit,
1001 + ),
1002 + field_spec={
1003 + "ALERT_ID": GraylogAlertProvisionFieldSpecItem(
1004 + data_type="string",
1005 + providers=[
1006 + GraylogAlertProvisionProvider(
1007 + type="template-v1",
1008 + template="${source._id}",
1009 + require_values=True,
1010 + ),
1011 + ],
1012 + ),
1013 + "CUSTOMER_CODE": GraylogAlertProvisionFieldSpecItem(
1014 + data_type="string",
1015 + providers=[
1016 + GraylogAlertProvisionProvider(
1017 + type="template-v1",
1018 + template="${source.agent_labels_customer}",
1019 + require_values=True,
1020 + ),
1021 + ],
1022 + ),
1023 + "ALERT_SOURCE": GraylogAlertProvisionFieldSpecItem(
1024 + data_type="string",
1025 + providers=[
1026 + GraylogAlertProvisionProvider(
1027 + type="template-v1",
1028 + template=alert_source,
1029 + require_values=True,
1030 + ),
1031 + ],
1032 + ),
1033 + "COPILOT_ALERT_ID": GraylogAlertProvisionFieldSpecItem(
1034 + data_type="string",
1035 + providers=[
1036 + GraylogAlertProvisionProvider(
1037 + type="template-v1",
1038 + template="NONE",
1039 + require_values=True,
1040 + ),
1041 + ],
1042 + ),
1043 + "RULE_ID": GraylogAlertProvisionFieldSpecItem(
1044 + data_type="string",
1045 + providers=[
1046 + GraylogAlertProvisionProvider(
1047 + type="template-v1",
1048 + template=request.rule_id,
1049 + require_values=True,
1050 + ),
1051 + ],
1052 + ),
1053 + },
1054 + key_spec=[],
1055 + notification_settings=GraylogAlertProvisionNotificationSettings(
1056 + grace_period_ms=0,
1057 + backlog_size=None,
1058 + ),
1059 + alert=True,
1060 + )
1061 +
1062 + # Provision the alert definition
1063 + await provision_alert_definition(alert_model)
1064 +
1065 + logger.info(f"Successfully provisioned Graylog alert '{alert_title}' for rule '{request.rule_id}'")
1066 +
1067 + return ProvisionGraylogAlertResponse(
1068 + success=True,
1069 + message=f"Graylog alert '{alert_title}' provisioned successfully",
1070 + rule_id=request.rule_id,
1071 + rule_name=rule_name,
1072 + alert_title=alert_title,
1073 + graylog_query=graylog_query,
1074 + )
backend/app/routers/copilot_searches.py new
+15
@@ -0,0 +1,15 @@
1 +from fastapi import APIRouter
2 +
3 +from app.integrations.copilot_searches.routes.copilot_searches import (
4 + copilot_searches_router,
5 +)
6 +
7 +# Instantiate the APIRouter
8 +router = APIRouter()
9 +
10 +# Include the Copilot Searches related routes
11 +router.include_router(
12 + copilot_searches_router,
13 + prefix="/copilot_searches",
14 + tags=["Copilot Searches"],
15 +)
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.45"
10 +CURRENT_VERSION = "0.1.46"
11 VERSION_CHECK_URL = "https://api.github.com/repos/socfortress/CoPilot/releases/latest"
12
13
backend/copilot.py
+2
@@ -40,6 +40,7 @@ from app.routers import cato
40 from app.routers import connectors
41 from app.routers import copilot_action
42 from app.routers import copilot_mcp
43 +from app.routers import copilot_searches
44 from app.routers import cortex
45 from app.routers import crowdstrike
46 from app.routers import customer_portal
@@ -157,6 +158,7 @@ api_router.include_router(office365.router)
158 api_router.include_router(copilot_action.router)
159 api_router.include_router(copilot_mcp.router)
160 api_router.include_router(mimecast.router)
161 +api_router.include_router(copilot_searches.router)
162 api_router.include_router(scheduler.router)
163 api_router.include_router(monitoring_alert.router)
164 api_router.include_router(sap_siem.router)
customer_portal/src/views/AgentsPage.vue
+2 -2
@@ -640,7 +640,7 @@
640 </template>
641
642 <script setup lang="ts">
643 -import { ref, computed, onMounted } from "vue"
643 +import { ref, computed, onMounted, onBeforeMount } from "vue"
644 import { useRouter } from "vue-router"
645 import { usePortalSettingsStore } from "@/stores/portalSettings"
646 import AgentsAPI, { type Agent } from "@/api/agents"
@@ -882,7 +882,7 @@ const logout = () => {
882 router.push("/login")
883 }
884
885 -onMounted(() => {
885 +onBeforeMount(() => {
886 loadAgents()
887 })
888 </script>
customer_portal/src/views/AlertsPage.vue
+2 -2
@@ -669,7 +669,7 @@
669 </template>
670
671 <script setup lang="ts">
672 -import { ref, onMounted, computed } from "vue"
672 +import { ref, onMounted, computed, onBeforeMount } from "vue"
673 import { usePortalSettingsStore } from "@/stores/portalSettings"
674 import AlertsAPI, { type Alert, type AlertsResponse } from "@/api/alerts"
675
@@ -840,7 +840,7 @@ const nextPage = () => {
840 }
841
842 // Lifecycle
843 -onMounted(() => {
843 +onBeforeMount(() => {
844 loadAlerts()
845 })
846 </script>
customer_portal/src/views/AlertsView.vue
+2 -2
@@ -186,7 +186,7 @@
186 </template>
187
188 <script setup lang="ts">
189 -import { ref, onMounted, computed } from "vue"
189 +import { ref, onMounted, computed, onBeforeMount } from "vue"
190 import { useRouter } from "vue-router"
191 import { useAuthStore } from "@/stores/auth"
192 import { httpClient } from "@/utils/httpClient"
@@ -247,7 +247,7 @@ const logout = () => {
247 router.push("/login")
248 }
249
250 -onMounted(() => {
250 +onBeforeMount(() => {
251 fetchAlerts()
252 })
253 </script>
customer_portal/src/views/CaseDetailsView.vue
+2 -2
@@ -161,7 +161,7 @@
161 </template>
162
163 <script setup lang="ts">
164 -import { ref, onMounted, computed } from "vue"
164 +import { ref, onMounted, computed, onBeforeMount } from "vue"
165 import { useRouter, useRoute } from "vue-router"
166 import { useAuthStore } from "@/stores/auth"
167 import { CasesAPI, type Case, type CaseComment } from "@/api/cases"
@@ -226,7 +226,7 @@ const logout = () => {
226 router.push("/login")
227 }
228
229 -onMounted(() => {
229 +onBeforeMount(() => {
230 fetchCaseDetails()
231 })
232 </script>
customer_portal/src/views/CasesPage.vue
+2 -2
@@ -884,7 +884,7 @@
884 </template>
885
886 <script setup lang="ts">
887 -import { ref, onMounted, computed } from "vue"
887 +import { ref, onMounted, computed, onBeforeMount } from "vue"
888 import { usePortalSettingsStore } from "@/stores/portalSettings"
889 import CasesAPI, { type Case, type CasesResponse } from "@/api/cases"
890 import CaseDataStoreAPI, { type CaseDataStoreFile } from "@/api/caseDataStore"
@@ -1132,7 +1132,7 @@ const formatDate = (dateString: string) => {
1132 }
1133
1134 // Lifecycle
1135 -onMounted(() => {
1135 +onBeforeMount(() => {
1136 loadCases()
1137 })
1138 </script>
customer_portal/src/views/CasesView.vue
+2 -2
@@ -202,7 +202,7 @@
202 </template>
203
204 <script setup lang="ts">
205 -import { ref, onMounted, computed } from "vue"
205 +import { ref, onMounted, computed, onBeforeMount } from "vue"
206 import { useRouter } from "vue-router"
207 import { useAuthStore } from "@/stores/auth"
208 import { httpClient } from "@/utils/httpClient"
@@ -274,7 +274,7 @@ const logout = () => {
274 router.push("/login")
275 }
276
277 -onMounted(() => {
277 +onBeforeMount(() => {
278 fetchCases()
279 })
280 </script>
customer_portal/src/views/OverviewPage.vue
+17 -13
@@ -47,12 +47,12 @@
47 Welcome,
48 <span class="font-medium">{{ username }}</span>
49 </div>
50 - <button
51 - @click="openChangePasswordModal"
52 - class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
53 - >
54 - Change Password
55 - </button>
50 + <button
51 + @click="openChangePasswordModal"
52 + class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
53 + >
54 + Change Password
55 + </button>
56 <button
57 @click="logout"
58 class="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700"
@@ -517,12 +517,16 @@
517 </div>
518 </div>
519 </main>
520 - <ChangePasswordModal :is-open="showChangePasswordModal" @close="closeChangePasswordModal" @success="onPasswordChanged" />
520 + <ChangePasswordModal
521 + :is-open="showChangePasswordModal"
522 + @close="closeChangePasswordModal"
523 + @success="onPasswordChanged"
524 + />
525 </div>
526 </template>
527
528 <script setup lang="ts">
525 -import { ref, onMounted, computed } from "vue"
529 +import { ref, onMounted, computed, onBeforeMount } from "vue"
530 import { useRouter } from "vue-router"
531 import { usePortalSettingsStore } from "@/stores/portalSettings"
532 import AlertsAPI, { type Alert } from "@/api/alerts"
@@ -731,16 +735,16 @@ const goToAgents = () => {
735 }
736
737 const openChangePasswordModal = () => {
734 - showChangePasswordModal.value = true
738 + showChangePasswordModal.value = true
739 }
740
741 const closeChangePasswordModal = () => {
738 - showChangePasswordModal.value = false
742 + showChangePasswordModal.value = false
743 }
744
745 const onPasswordChanged = () => {
742 - // Optionally show a success message or perform other actions
743 - console.log("Password changed successfully")
746 + // Optionally show a success message or perform other actions
747 + console.log("Password changed successfully")
748 }
749
750 const logout = () => {
@@ -749,7 +753,7 @@ const logout = () => {
753 router.push("/login")
754 }
755
752 -onMounted(() => {
756 +onBeforeMount(() => {
757 fetchDashboardData()
758 })
759 </script>
frontend/.gitignore
+1
@@ -13,6 +13,7 @@ coverage
13
14 cypress/videos/
15 cypress/screenshots/
16 +report/
17
18 src/unplugin.components.d.ts
19 cypress/screenshots/
frontend/eslint.config.mjs
+54 -44
@@ -1,49 +1,59 @@
1 import antfu from "@antfu/eslint-config"
2
3 -export default antfu({
4 - vue: true,
5 - typescript: true,
6 - javascript: true,
7 - jsx: true,
3 +export default antfu(
4 + {
5 + vue: true,
6 + typescript: true,
7 + javascript: true,
8 + jsx: true,
9
9 - stylistic: {
10 - indent: "tab",
11 - quotes: "double"
10 + stylistic: {
11 + indent: "tab",
12 + quotes: "double"
13 + },
14 + rules: {
15 + "e18e/ban-dependencies": ["error", { allowed: ["axios", "lodash", "depcheck", "fs-extra"] }],
16 + "antfu/if-newline": "off",
17 + "style/operator-linebreak": "off",
18 + "style/arrow-parens": "off",
19 + "style/brace-style": "off",
20 + "style/indent-binary-ops": "off",
21 + "style/indent": "off",
22 + "style/member-delimiter-style": "off",
23 + "style/quotes": "off",
24 + "style/quote-props": "off",
25 + "style/eol-last": "off",
26 + "style/no-mixed-spaces-and-tabs": "off",
27 + "style/comma-dangle": [
28 + "error",
29 + {
30 + arrays: "never",
31 + objects: "never",
32 + imports: "never",
33 + exports: "never",
34 + functions: "never"
35 + }
36 + ]
37 + }
38 },
13 - rules: {
14 - "antfu/if-newline": "off",
15 - "style/operator-linebreak": "off",
16 - "style/arrow-parens": "off",
17 - "style/brace-style": "off",
18 - "style/indent-binary-ops": "off",
19 - "style/indent": "off",
20 - "style/member-delimiter-style": "off",
21 - "style/quotes": "off",
22 - "style/quote-props": "off",
23 - "style/eol-last": "off",
24 - "style/no-mixed-spaces-and-tabs": "off",
25 - "style/comma-dangle": [
26 - "error",
27 - {
28 - arrays: "never",
29 - objects: "never",
30 - imports: "never",
31 - exports: "never",
32 - functions: "never"
33 - }
34 - ],
35 - "vue/block-order": [
36 - "error",
37 - {
38 - order: ["template", "script", "style"]
39 - }
40 - ],
41 - "vue/component-name-in-template-casing": "off",
42 - "vue/custom-event-name-casing": "off",
43 - "vue/html-self-closing": "off",
44 - "vue/singleline-html-element-content-newline": "off",
45 - "vue/comma-dangle": "off",
46 - "vue/quote-props": "off",
47 - "vue/one-component-per-file": "off"
39 + {
40 + files: ["**/*.vue"],
41 + rules: {
42 + "vue/block-order": [
43 + "error",
44 + {
45 + order: ["template", "script", "style"]
46 + }
47 + ],
48 + "vue/component-name-in-template-casing": "off",
49 + "vue/custom-event-name-casing": "off",
50 + "vue/html-self-closing": "off",
51 + "vue/singleline-html-element-content-newline": "off",
52 + "vue/comma-dangle": "off",
53 + "vue/quote-props": "off",
54 + "vue/one-component-per-file": "off",
55 + "vue/prefer-true-attribute-shorthand": "error",
56 + "vue/v-bind-style": ["error", "shorthand", { sameNameShorthand: "always" }]
57 + }
58 }
49 -})
59 +)
frontend/package.json
+30 -28
@@ -3,7 +3,7 @@
3 "type": "module",
4 "version": "1.0.0",
5 "private": true,
6 - "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
6 + "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
7 "engines": {
8 "node": ">=18.0.0"
9 },
@@ -18,6 +18,7 @@
18 "preview": "vite build && echo '' && vite preview --port 4173 --host",
19 "preview:only": "vite preview --port 4173 --host",
20 "test:unit": "vitest run",
21 + "dup-check": "jscpd --ignore \"**/node_modules/**,**/dist/**,**/.nuxt/**,**/*.svg\" --min-tokens 100 --reporters console,html,json .",
22 "type-check": "vue-tsc --build --force",
23 "lint": "eslint . --fix",
24 "format": "prettier --write src/",
@@ -36,32 +37,32 @@
37 "dependencies": {
38 "@ajoelp/json-to-formdata": "^1.5.0",
39 "@codemirror/commands": "^6.10.2",
39 - "@codemirror/lang-javascript": "^6.2.4",
40 + "@codemirror/lang-javascript": "^6.2.5",
41 "@codemirror/lang-xml": "^6.1.0",
41 - "@codemirror/lint": "^6.9.4",
42 + "@codemirror/lint": "^6.9.5",
43 "@codemirror/theme-one-dark": "^6.1.3",
43 - "@codemirror/view": "^6.39.14",
44 + "@codemirror/view": "^6.39.16",
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",
49 - "@shikijs/markdown-it": "^3.22.0",
50 + "@shikijs/markdown-it": "^4.0.1",
51 "@singulio/app-auth-search": "^0.0.3",
52 "@types/codemirror": "^5.60.17",
53 "@vueuse/core": "^14.2.1",
54 "@vueuse/motion": "^3.0.3",
54 - "axios": "^1.13.5",
55 + "axios": "^1.13.6",
56 "bytes": "^3.1.2",
57 "codemirror": "~6.0.2",
58 "colord": "^2.9.3",
59 "dayjs": "^1.11.19",
60 "detect-touch-device": "^1.1.6",
61 "echarts": "^6.0.0",
61 - "fast-xml-parser": "^5.3.6",
62 + "fast-xml-parser": "^5.4.2",
63 "file-saver": "^2.0.5",
64 "html-entities": "^2.6.0",
64 - "jose": "^6.1.3",
65 + "jose": "^6.2.0",
66 "js-md5": "^0.8.3",
67 "lodash": "^4.17.23",
68 "mitt": "^3.0.1",
@@ -71,17 +72,17 @@
72 "pinia": "^3.0.4",
73 "pinia-plugin-persistedstate": "^4.7.1",
74 "secure-ls": "^2.0.0",
74 - "shiki": "^3.22.0",
75 + "shiki": "^4.0.1",
76 "thememirror": "^2.0.1",
77 "validator": "^13.15.26",
77 - "vue": "^3.5.28",
78 + "vue": "^3.5.29",
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-router": "^5.0.2",
83 + "vue-router": "^5.0.3",
84 "vue-sjv": "^0.0.6",
84 - "vue3-apexcharts": "^1.10.0",
85 + "vue3-apexcharts": "^1.11.1",
86 "vue3-marquee": "^4.2.2",
87 "vuedraggable": "^4.1.0",
88 "xmllint": "^0.1.1",
@@ -93,45 +94,46 @@
94 "vueuc": "^0.4.64"
95 },
96 "devDependencies": {
96 - "@antfu/eslint-config": "~7.4.3",
97 - "@clack/prompts": "^1.0.1",
97 + "@antfu/eslint-config": "~7.7.0",
98 + "@clack/prompts": "^1.1.0",
99 "@iconify/vue": "^5.0.0",
99 - "@tailwindcss/vite": "^4.1.18",
100 + "@tailwindcss/vite": "^4.2.1",
101 "@tsconfig/node20": "^20.1.9",
102 "@types/bytes": "^3.1.5",
103 "@types/file-saver": "^2.0.7",
104 "@types/fs-extra": "^11.0.4",
104 - "@types/jsdom": "^27.0.0",
105 - "@types/lodash": "^4.17.23",
105 + "@types/jsdom": "^28.0.0",
106 + "@types/lodash": "^4.17.24",
107 "@types/markdown-it": "^14.1.2",
107 - "@types/node": "^25.2.3",
108 + "@types/node": "^25.3.5",
109 "@types/validator": "^13.15.10",
110 "@vitejs/plugin-vue": "^6.0.4",
111 "@vitejs/plugin-vue-jsx": "^5.1.4",
112 "@vue/test-utils": "^2.4.6",
112 - "@vue/tsconfig": "~0.8.1",
113 - "baseline-browser-mapping": "^2.9.19",
113 + "@vue/tsconfig": "~0.9.0",
114 + "baseline-browser-mapping": "^2.10.0",
115 "depcheck": "^1.4.7",
115 - "eslint": "~10.0.0",
116 + "eslint": "~10.0.2",
117 "flourite": "^1.3.0",
117 - "fs-extra": "^11.3.3",
118 + "fs-extra": "^11.3.4",
119 + "jscpd": "^4.0.8",
120 "jsdom": "^28.1.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",
123 - "start-server-and-test": "^2.1.3",
124 - "tailwindcss": "^4.1.18",
125 - "taze": "^19.9.2",
125 + "start-server-and-test": "^2.1.5",
126 + "tailwindcss": "^4.2.1",
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",
131 - "vite-plugin-vue-devtools": "^8.0.6",
132 - "vite-svg-loader": "^5.1.0",
133 + "vite-plugin-vue-devtools": "^8.0.7",
134 + "vite-svg-loader": "^5.1.1",
135 "vitest": "^4.0.18",
134 - "vue-tsc": "^3.2.4"
136 + "vue-tsc": "^3.2.5"
137 },
138 "pnpm": {
139 "peerDependencyRules": {
frontend/pnpm-lock.yaml
+1487 -937
@@ -15,23 +15,23 @@ importers:
15 specifier: ^6.10.2
16 version: 6.10.2
17 '@codemirror/lang-javascript':
18 - specifier: ^6.2.4
19 - version: 6.2.4
18 + specifier: ^6.2.5
19 + version: 6.2.5
20 '@codemirror/lang-xml':
21 specifier: ^6.1.0
22 version: 6.1.0
23 '@codemirror/lint':
24 - specifier: ^6.9.4
25 - version: 6.9.4
24 + specifier: ^6.9.5
25 + version: 6.9.5
26 '@codemirror/theme-one-dark':
27 specifier: ^6.1.3
28 version: 6.1.3
29 '@codemirror/view':
30 - specifier: ^6.39.14
31 - version: 6.39.14
30 + specifier: ^6.39.16
31 + version: 6.39.16
32 '@f3ve/vue-markdown-it':
33 specifier: ^0.2.3
34 - version: 0.2.3(vue@3.5.28(typescript@5.9.3))
34 + version: 0.2.3(vue@3.5.29(typescript@5.9.3))
35 '@fontsource/jetbrains-mono':
36 specifier: ^5.2.8
37 version: 5.2.8
@@ -45,8 +45,8 @@ importers:
45 specifier: ^2.0.1
46 version: 2.0.1
47 '@shikijs/markdown-it':
48 - specifier: ^3.22.0
49 - version: 3.22.0
48 + specifier: ^4.0.1
49 + version: 4.0.1
50 '@singulio/app-auth-search':
51 specifier: ^0.0.3
52 version: 0.0.3
@@ -55,13 +55,13 @@ importers:
55 version: 5.60.17
56 '@vueuse/core':
57 specifier: ^14.2.1
58 - version: 14.2.1(vue@3.5.28(typescript@5.9.3))
58 + version: 14.2.1(vue@3.5.29(typescript@5.9.3))
59 '@vueuse/motion':
60 specifier: ^3.0.3
61 - version: 3.0.3(vue@3.5.28(typescript@5.9.3))
61 + version: 3.0.3(vue@3.5.29(typescript@5.9.3))
62 axios:
63 - specifier: ^1.13.5
64 - version: 1.13.5(debug@4.4.3)
63 + specifier: ^1.13.6
64 + version: 1.13.6(debug@4.4.3)
65 bytes:
66 specifier: ^3.1.2
67 version: 3.1.2
@@ -81,8 +81,8 @@ importers:
81 specifier: ^6.0.0
82 version: 6.0.0
83 fast-xml-parser:
84 - specifier: ^5.3.6
85 - version: 5.3.6
84 + specifier: ^5.4.2
85 + version: 5.4.2
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.1.3
94 - version: 6.1.3
93 + specifier: ^6.2.0
94 + version: 6.2.0
95 js-md5:
96 specifier: ^0.8.3
97 version: 0.8.3
@@ -103,7 +103,7 @@ importers:
103 version: 3.0.1
104 naive-ui:
105 specifier: ^2.43.2
106 - version: 2.43.2(vue@3.5.28(typescript@5.9.3))
106 + version: 2.43.2(vue@3.5.29(typescript@5.9.3))
107 nanoid:
108 specifier: ^5.1.6
109 version: 5.1.6
@@ -112,71 +112,71 @@ importers:
112 version: 5.3.0
113 pinia:
114 specifier: ^3.0.4
115 - version: 3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))
115 + version: 3.0.4(typescript@5.9.3)(vue@3.5.29(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.28(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.29(typescript@5.9.3)))
119 secure-ls:
120 specifier: ^2.0.0
121 version: 2.0.0
122 shiki:
123 - specifier: ^3.22.0
124 - version: 3.22.0
123 + specifier: ^4.0.1
124 + version: 4.0.1
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.14)
127 + version: 2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)
128 validator:
129 specifier: ^13.15.26
130 version: 13.15.26
131 vue:
132 - specifier: ^3.5.28
133 - version: 3.5.28(typescript@5.9.3)
132 + specifier: ^3.5.29
133 + version: 3.5.29(typescript@5.9.3)
134 vue-advanced-cropper:
135 specifier: ^2.8.9
136 - version: 2.8.9(vue@3.5.28(typescript@5.9.3))
136 + version: 2.8.9(vue@3.5.29(typescript@5.9.3))
137 vue-codemirror:
138 specifier: ^6.1.1
139 - version: 6.1.1(codemirror@6.0.2)(vue@3.5.28(typescript@5.9.3))
139 + version: 6.1.1(codemirror@6.0.2)(vue@3.5.29(typescript@5.9.3))
140 vue-highlight-words:
141 specifier: ^3.0.1
142 - version: 3.0.1(vue@3.5.28(typescript@5.9.3))
142 + version: 3.0.1(vue@3.5.29(typescript@5.9.3))
143 vue-i18n:
144 specifier: ^11.2.8
145 - version: 11.2.8(vue@3.5.28(typescript@5.9.3))
145 + version: 11.2.8(vue@3.5.29(typescript@5.9.3))
146 vue-router:
147 - specifier: ^5.0.2
148 - version: 5.0.2(@vue/compiler-sfc@3.5.28)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(vue@3.5.28(typescript@5.9.3))
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))
149 vue-sjv:
150 specifier: ^0.0.6
151 - version: 0.0.6(vue@3.5.28(typescript@5.9.3))
151 + version: 0.0.6(vue@3.5.29(typescript@5.9.3))
152 vue3-apexcharts:
153 - specifier: ^1.10.0
154 - version: 1.10.0(apexcharts@5.5.1)(vue@3.5.28(typescript@5.9.3))
153 + specifier: ^1.11.1
154 + version: 1.11.1(apexcharts@5.5.1)(vue@3.5.29(typescript@5.9.3))
155 vue3-marquee:
156 specifier: ^4.2.2
157 - version: 4.2.2(vue@3.5.28(typescript@5.9.3))
157 + version: 4.2.2(vue@3.5.29(typescript@5.9.3))
158 vuedraggable:
159 specifier: ^4.1.0
160 - version: 4.1.0(vue@3.5.28(typescript@5.9.3))
160 + version: 4.1.0(vue@3.5.29(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.2.3)
166 + version: 5.1.0(@types/node@25.3.5)
167 devDependencies:
168 '@antfu/eslint-config':
169 - specifier: ~7.4.3
170 - version: 7.4.3(@vue/compiler-sfc@3.5.28)(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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))
171 '@clack/prompts':
172 - specifier: ^1.0.1
173 - version: 1.0.1
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.28(typescript@5.9.3))
176 + version: 5.0.0(vue@3.5.29(typescript@5.9.3))
177 '@tailwindcss/vite':
178 - specifier: ^4.1.18
179 - version: 4.1.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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))
180 '@tsconfig/node20':
181 specifier: ^20.1.9
182 version: 20.1.9
@@ -190,47 +190,50 @@ importers:
190 specifier: ^11.0.4
191 version: 11.0.4
192 '@types/jsdom':
193 - specifier: ^27.0.0
194 - version: 27.0.0
193 + specifier: ^28.0.0
194 + version: 28.0.0
195 '@types/lodash':
196 - specifier: ^4.17.23
197 - version: 4.17.23
196 + specifier: ^4.17.24
197 + version: 4.17.24
198 '@types/markdown-it':
199 specifier: ^14.1.2
200 version: 14.1.2
201 '@types/node':
202 - specifier: ^25.2.3
203 - version: 25.2.3
202 + specifier: ^25.3.5
203 + version: 25.3.5
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.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
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))
210 '@vitejs/plugin-vue-jsx':
211 specifier: ^5.1.4
212 - version: 5.1.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
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))
213 '@vue/test-utils':
214 specifier: ^2.4.6
215 version: 2.4.6
216 '@vue/tsconfig':
217 - specifier: ~0.8.1
218 - version: 0.8.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))
217 + specifier: ~0.9.0
218 + version: 0.9.0(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
219 baseline-browser-mapping:
220 - specifier: ^2.9.19
221 - version: 2.9.19
220 + specifier: ^2.10.0
221 + version: 2.10.0
222 depcheck:
223 specifier: ^1.4.7
224 version: 1.4.7
225 eslint:
226 - specifier: ~10.0.0
227 - version: 10.0.0(jiti@2.6.1)
226 + specifier: ~10.0.2
227 + version: 10.0.2(jiti@2.6.1)
228 flourite:
229 specifier: ^1.3.0
230 version: 1.3.0
231 fs-extra:
232 - specifier: ^11.3.3
233 - version: 11.3.3
232 + specifier: ^11.3.4
233 + version: 11.3.4
234 + jscpd:
235 + specifier: ^4.0.8
236 + version: 4.0.8
237 jsdom:
238 specifier: ^28.1.0
239 version: 28.1.0
@@ -247,14 +250,14 @@ importers:
250 specifier: ^1.97.3
251 version: 1.97.3
252 start-server-and-test:
250 - specifier: ^2.1.3
251 - version: 2.1.3
253 + specifier: ^2.1.5
254 + version: 2.1.5
255 tailwindcss:
253 - specifier: ^4.1.18
254 - version: 4.1.18
256 + specifier: ^4.2.1
257 + version: 4.2.1
258 taze:
256 - specifier: ^19.9.2
257 - version: 19.9.2
259 + specifier: ^19.10.0
260 + version: 19.10.0
261 type-fest:
262 specifier: ^5.4.4
263 version: 5.4.4
@@ -263,25 +266,25 @@ importers:
266 version: 5.9.3
267 vite:
268 specifier: ^7.3.1
266 - version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
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)
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
272 - version: 11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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))
276 vite-plugin-vue-devtools:
274 - specifier: ^8.0.6
275 - version: 8.0.6(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
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))
279 vite-svg-loader:
277 - specifier: ^5.1.0
278 - version: 5.1.0(vue@3.5.28(typescript@5.9.3))
280 + specifier: ^5.1.1
281 + version: 5.1.1(vue@3.5.29(typescript@5.9.3))
282 vitest:
283 specifier: ^4.0.18
281 - version: 4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
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)
285 vue-tsc:
283 - specifier: ^3.2.4
284 - version: 3.2.4(typescript@5.9.3)
286 + specifier: ^3.2.5
287 + version: 3.2.5(typescript@5.9.3)
288 optionalDependencies:
289 '@rollup/rollup-linux-x64-gnu':
290 specifier: ^4.46.2
@@ -291,7 +294,7 @@ importers:
294 version: 0.3.11
295 vueuc:
296 specifier: ^0.4.64
294 - version: 0.4.65(vue@3.5.28(typescript@5.9.3))
297 + version: 0.4.65(vue@3.5.29(typescript@5.9.3))
298
299 packages:
300
@@ -357,8 +360,8 @@ packages:
360 resolution: {integrity: sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==}
361 engines: {node: '>= 14.0.0'}
362
360 - '@antfu/eslint-config@7.4.3':
361 - resolution: {integrity: sha512-qHOG2408wBz21x191n0Mm9r2q/PqMYul+YE+DUBqZMtJYUn+bd+Dh3g8LIkyItMJR+Xs9f9TKXJehZLRYxJlHg==}
363 + '@antfu/eslint-config@7.7.0':
364 + resolution: {integrity: sha512-lkxb84o8z4v1+me51XlrHHF6zvOZfvTu6Y11t6h6v17JSMl9yoNHwC0Sqp/NfMTHie/LGgjyXOupXpQCXxfs1Q==}
365 hasBin: true
366 peerDependencies:
367 '@angular-eslint/eslint-plugin': ^21.1.0
@@ -424,8 +427,8 @@ packages:
427 '@antfu/install-pkg@1.1.0':
428 resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
429
427 - '@antfu/ni@27.0.1':
428 - resolution: {integrity: sha512-I6SOlwJ0MN73ECYcr7VJHpqSseyd7bpshx6JAaD0zNowS4kSWzFsqg8ikQT7DnCLiD4AZ+FaQJQ8WAk0Qi89Vw==}
430 + '@antfu/ni@28.2.0':
431 + resolution: {integrity: sha512-+pnatqUMTpi1g/VxbaTsX9UxibTp5oWCMbfUAQPV91UL9lTIMmlU2uvG8bDETDJ0kJdsZT8zLBctKLJOeL5jmg==}
432 engines: {node: '>=20'}
433 hasBin: true
434
@@ -582,11 +585,11 @@ packages:
585 resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
586 hasBin: true
587
585 - '@clack/core@1.0.1':
586 - resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==}
588 + '@clack/core@1.1.0':
589 + resolution: {integrity: sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==}
590
588 - '@clack/prompts@1.0.1':
589 - resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==}
591 + '@clack/prompts@1.1.0':
592 + resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==}
593
594 '@codemirror/autocomplete@6.20.0':
595 resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
@@ -594,8 +597,8 @@ packages:
597 '@codemirror/commands@6.10.2':
598 resolution: {integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==}
599
597 - '@codemirror/lang-javascript@6.2.4':
598 - resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
600 + '@codemirror/lang-javascript@6.2.5':
601 + resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==}
602
603 '@codemirror/lang-xml@6.1.0':
604 resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==}
@@ -603,8 +606,8 @@ packages:
606 '@codemirror/language@6.12.1':
607 resolution: {integrity: sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==}
608
606 - '@codemirror/lint@6.9.4':
607 - resolution: {integrity: sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==}
609 + '@codemirror/lint@6.9.5':
610 + resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==}
611
612 '@codemirror/search@6.6.0':
613 resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==}
@@ -615,8 +618,12 @@ packages:
618 '@codemirror/theme-one-dark@6.1.3':
619 resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
620
618 - '@codemirror/view@6.39.14':
619 - resolution: {integrity: sha512-WJcvgHm/6Q7dvGT0YFv/6PSkoc36QlR0VCESS6x9tGsnF1lWLmmYxOgX3HH6v8fo6AvSLgpcs+H0Olre6MKXlg==}
621 + '@codemirror/view@6.39.16':
622 + resolution: {integrity: sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==}
623 +
624 + '@colors/colors@1.5.0':
625 + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
626 + engines: {node: '>=0.1.90'}
627
628 '@css-render/plugin-bem@0.15.14':
629 resolution: {integrity: sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==}
@@ -659,13 +666,20 @@ packages:
666 resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
667 engines: {node: '>=20.19.0'}
668
669 + '@e18e/eslint-plugin@0.2.0':
670 + resolution: {integrity: sha512-mXgODVwhuDjTJ+UT+XSvmMmCidtGKfrV5nMIv1UtpWex2pYLsIM3RSpT8HWIMAebS9qANbXPKlSX4BE7ZvuCgA==}
671 + peerDependencies:
672 + eslint: ^9.0.0 || ^10.0.0
673 + oxlint: ^1.41.0
674 + peerDependenciesMeta:
675 + eslint:
676 + optional: true
677 + oxlint:
678 + optional: true
679 +
680 '@emotion/hash@0.8.0':
681 resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
682
665 - '@es-joy/jsdoccomment@0.78.0':
666 - resolution: {integrity: sha512-rQkU5u8hNAq2NVRzHnIUUvR6arbO0b6AOlvpTNS48CkiKSn/xtNfOzBK23JE4SiW89DgvU7GtxLVgV4Vn2HBAw==}
667 - engines: {node: '>=20.11.0'}
668 -
683 '@es-joy/jsdoccomment@0.84.0':
684 resolution: {integrity: sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==}
685 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
@@ -830,11 +844,11 @@ packages:
844 cpu: [x64]
845 os: [win32]
846
833 - '@eslint-community/eslint-plugin-eslint-comments@4.6.0':
834 - resolution: {integrity: sha512-2EX2bBQq1ez++xz2o9tEeEQkyvfieWgUFMH4rtJJri2q0Azvhja3hZGXsjPXs31R4fQkZDtWzNDDK2zQn5UE5g==}
847 + '@eslint-community/eslint-plugin-eslint-comments@4.7.1':
848 + resolution: {integrity: sha512-Ql2nJFwA8wUGpILYGOQaT1glPsmvEwE0d+a+l7AALLzQvInqdbXJdx7aSu0DpUX9dB1wMVBMhm99/++S3MdEtQ==}
849 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
850 peerDependencies:
837 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
851 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
852
853 '@eslint-community/eslint-utils@4.9.1':
854 resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
@@ -846,17 +860,17 @@ packages:
860 resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
861 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
862
849 - '@eslint/compat@1.4.1':
850 - resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==}
851 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
863 + '@eslint/compat@2.0.2':
864 + resolution: {integrity: sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==}
865 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
866 peerDependencies:
853 - eslint: ^8.40 || 9
867 + eslint: ^8.40 || 9 || 10
868 peerDependenciesMeta:
869 eslint:
870 optional: true
871
858 - '@eslint/config-array@0.23.1':
859 - resolution: {integrity: sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==}
872 + '@eslint/config-array@0.23.2':
873 + resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==}
874 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
875
876 '@eslint/config-helpers@0.5.2':
@@ -875,8 +889,8 @@ packages:
889 resolution: {integrity: sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ==}
890 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
891
878 - '@eslint/object-schema@3.0.1':
879 - resolution: {integrity: sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==}
892 + '@eslint/object-schema@3.0.2':
893 + resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==}
894 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
895
896 '@eslint/plugin-kit@0.4.1':
@@ -930,6 +944,9 @@ packages:
944 '@hapi/topo@6.0.2':
945 resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==}
946
947 + '@henrygd/queue@1.2.0':
948 + resolution: {integrity: sha512-jW/BLSTpcvExDhqJGxtIPgGr2O0IFF8XUNDwEbfCfhrXT8a4xztQ9Lv6U/vbYzYC0xVWn+3zv6YnLUh3bEFUKA==}
949 +
950 '@humanfs/core@0.19.1':
951 resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
952 engines: {node: '>=18.18.0'}
@@ -990,6 +1007,21 @@ packages:
1007 '@jridgewell/trace-mapping@0.3.31':
1008 resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
1009
1010 + '@jscpd/badge-reporter@4.0.4':
1011 + resolution: {integrity: sha512-I9b4MmLXPM2vo0SxSUWnNGKcA4PjQlD3GzXvFK60z43cN/EIdLbOq3FVwCL+dg2obUqGXKIzAm7EsDFTg0D+mQ==}
1012 +
1013 + '@jscpd/core@4.0.4':
1014 + resolution: {integrity: sha512-QGMT3iXEX1fI6lgjPH+x8eyJwhwr2KkpSF5uBpjC0Z5Xloj0yFTFLtwJT+RhxP/Ob4WYrtx2jvpKB269oIwgMQ==}
1015 +
1016 + '@jscpd/finder@4.0.4':
1017 + resolution: {integrity: sha512-qVUWY7Nzuvfd5OIk+n7/5CM98LmFroLqblRXAI2gDABwZrc7qS+WH2SNr0qoUq0f4OqwM+piiwKvwL/VDNn/Cg==}
1018 +
1019 + '@jscpd/html-reporter@4.0.4':
1020 + resolution: {integrity: sha512-YiepyeYkeH74Kx59PJRdUdonznct0wHPFkf6FLQN+mCBoy6leAWCcOfHtcexnp+UsBFDlItG5nRdKrDSxSH+Kg==}
1021 +
1022 + '@jscpd/tokenizer@4.0.4':
1023 + resolution: {integrity: sha512-xxYYY/qaLah/FlwogEbGIxx9CjDO+G9E6qawcy26WwrflzJb6wsnhjwdneN6Wb0RNCDsqvzY+bzG453jsin4UQ==}
1024 +
1025 '@juggle/resize-observer@3.4.0':
1026 resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
1027
@@ -1014,6 +1046,18 @@ packages:
1046 '@microsoft/fetch-event-source@2.0.1':
1047 resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==}
1048
1049 + '@nodelib/fs.scandir@2.1.5':
1050 + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
1051 + engines: {node: '>= 8'}
1052 +
1053 + '@nodelib/fs.stat@2.0.5':
1054 + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
1055 + engines: {node: '>= 8'}
1056 +
1057 + '@nodelib/fs.walk@1.2.8':
1058 + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
1059 + engines: {node: '>= 8'}
1060 +
1061 '@nuxt/kit@3.21.1':
1062 resolution: {integrity: sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg==}
1063 engines: {node: '>=18.12.0'}
@@ -1021,12 +1065,9 @@ packages:
1065 '@one-ini/wasm@0.1.1':
1066 resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
1067
1024 - '@ota-meshi/ast-token-store@0.2.0':
1025 - resolution: {integrity: sha512-VQNg9OyqbvjMb+/EoFl1t5mp5qU53AHd6VLIKvj0nfnBiLnom2OSm+heyXD3++GePoVNf1D/KKU8fOIHi4y0xw==}
1068 + '@ota-meshi/ast-token-store@0.3.0':
1069 + resolution: {integrity: sha512-XRO0zi2NIUKq2lUk3T1ecFSld1fMWRKE6naRFGkgkdeosx7IslyUKNv5Dcb5PJTja9tHJoFu0v/7yEpAkrkrTg==}
1070 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
1027 - peerDependencies:
1028 - '@eslint/markdown': ^7.4.0
1029 - eslint: '>=9.0.0'
1071
1072 '@parcel/watcher-android-arm64@2.5.6':
1073 resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
@@ -1274,31 +1315,42 @@ packages:
1315 cpu: [x64]
1316 os: [win32]
1317
1277 - '@shikijs/core@3.22.0':
1278 - resolution: {integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==}
1318 + '@shikijs/core@4.0.1':
1319 + resolution: {integrity: sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg==}
1320 + engines: {node: '>=20'}
1321
1280 - '@shikijs/engine-javascript@3.22.0':
1281 - resolution: {integrity: sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==}
1322 + '@shikijs/engine-javascript@4.0.1':
1323 + resolution: {integrity: sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg==}
1324 + engines: {node: '>=20'}
1325
1283 - '@shikijs/engine-oniguruma@3.22.0':
1284 - resolution: {integrity: sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==}
1326 + '@shikijs/engine-oniguruma@4.0.1':
1327 + resolution: {integrity: sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg==}
1328 + engines: {node: '>=20'}
1329
1286 - '@shikijs/langs@3.22.0':
1287 - resolution: {integrity: sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==}
1330 + '@shikijs/langs@4.0.1':
1331 + resolution: {integrity: sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w==}
1332 + engines: {node: '>=20'}
1333
1289 - '@shikijs/markdown-it@3.22.0':
1290 - resolution: {integrity: sha512-l0DehGhJN+JkkzGwULVhE57S9o7zQA2lVfpEjcvpSkxTbhzTEuoKHjuQQUN0VNbYeyj4obZwB4CTVrMijvYaeg==}
1334 + '@shikijs/markdown-it@4.0.1':
1335 + resolution: {integrity: sha512-JxF2AfpQ0Mr9+t4JkPqLocS9+FSTO49mqVR3lqgFQH1fJQ0+mq5Gq7eM71kp8cFm2oOzEf24rrZfMIDjBmVAYw==}
1336 + engines: {node: '>=20'}
1337 peerDependencies:
1338 markdown-it-async: ^2.2.0
1339 peerDependenciesMeta:
1340 markdown-it-async:
1341 optional: true
1342
1297 - '@shikijs/themes@3.22.0':
1298 - resolution: {integrity: sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==}
1343 + '@shikijs/primitive@4.0.1':
1344 + resolution: {integrity: sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA==}
1345 + engines: {node: '>=20'}
1346
1300 - '@shikijs/types@3.22.0':
1301 - resolution: {integrity: sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==}
1347 + '@shikijs/themes@4.0.1':
1348 + resolution: {integrity: sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==}
1349 + engines: {node: '>=20'}
1350 +
1351 + '@shikijs/types@4.0.1':
1352 + resolution: {integrity: sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw==}
1353 + engines: {node: '>=20'}
1354
1355 '@shikijs/vscode-textmate@10.0.2':
1356 resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
@@ -1313,75 +1365,75 @@ packages:
1365 '@standard-schema/spec@1.1.0':
1366 resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
1367
1316 - '@stylistic/eslint-plugin@5.8.0':
1317 - resolution: {integrity: sha512-WNPVF/FfBAjyi3OA7gok8swRiImNLKI4dmV3iK/GC/0xSJR7eCzBFsw9hLZVgb1+MYNLy7aDsjohxN1hA/FIfQ==}
1368 + '@stylistic/eslint-plugin@5.10.0':
1369 + resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==}
1370 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1371 peerDependencies:
1320 - eslint: '>=9.0.0'
1372 + eslint: ^9.0.0 || ^10.0.0
1373
1322 - '@tailwindcss/node@4.1.18':
1323 - resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
1374 + '@tailwindcss/node@4.2.1':
1375 + resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==}
1376
1325 - '@tailwindcss/oxide-android-arm64@4.1.18':
1326 - resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
1327 - engines: {node: '>= 10'}
1377 + '@tailwindcss/oxide-android-arm64@4.2.1':
1378 + resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==}
1379 + engines: {node: '>= 20'}
1380 cpu: [arm64]
1381 os: [android]
1382
1331 - '@tailwindcss/oxide-darwin-arm64@4.1.18':
1332 - resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
1333 - engines: {node: '>= 10'}
1383 + '@tailwindcss/oxide-darwin-arm64@4.2.1':
1384 + resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==}
1385 + engines: {node: '>= 20'}
1386 cpu: [arm64]
1387 os: [darwin]
1388
1337 - '@tailwindcss/oxide-darwin-x64@4.1.18':
1338 - resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
1339 - engines: {node: '>= 10'}
1389 + '@tailwindcss/oxide-darwin-x64@4.2.1':
1390 + resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==}
1391 + engines: {node: '>= 20'}
1392 cpu: [x64]
1393 os: [darwin]
1394
1343 - '@tailwindcss/oxide-freebsd-x64@4.1.18':
1344 - resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
1345 - engines: {node: '>= 10'}
1395 + '@tailwindcss/oxide-freebsd-x64@4.2.1':
1396 + resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==}
1397 + engines: {node: '>= 20'}
1398 cpu: [x64]
1399 os: [freebsd]
1400
1349 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
1350 - resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
1351 - engines: {node: '>= 10'}
1401 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1':
1402 + resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==}
1403 + engines: {node: '>= 20'}
1404 cpu: [arm]
1405 os: [linux]
1406
1355 - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
1356 - resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
1357 - engines: {node: '>= 10'}
1407 + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1':
1408 + resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==}
1409 + engines: {node: '>= 20'}
1410 cpu: [arm64]
1411 os: [linux]
1412 libc: [glibc]
1413
1362 - '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
1363 - resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
1364 - engines: {node: '>= 10'}
1414 + '@tailwindcss/oxide-linux-arm64-musl@4.2.1':
1415 + resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==}
1416 + engines: {node: '>= 20'}
1417 cpu: [arm64]
1418 os: [linux]
1419 libc: [musl]
1420
1369 - '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
1370 - resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
1371 - engines: {node: '>= 10'}
1421 + '@tailwindcss/oxide-linux-x64-gnu@4.2.1':
1422 + resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==}
1423 + engines: {node: '>= 20'}
1424 cpu: [x64]
1425 os: [linux]
1426 libc: [glibc]
1427
1376 - '@tailwindcss/oxide-linux-x64-musl@4.1.18':
1377 - resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
1378 - engines: {node: '>= 10'}
1428 + '@tailwindcss/oxide-linux-x64-musl@4.2.1':
1429 + resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==}
1430 + engines: {node: '>= 20'}
1431 cpu: [x64]
1432 os: [linux]
1433 libc: [musl]
1434
1383 - '@tailwindcss/oxide-wasm32-wasi@4.1.18':
1384 - resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
1435 + '@tailwindcss/oxide-wasm32-wasi@4.2.1':
1436 + resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==}
1437 engines: {node: '>=14.0.0'}
1438 cpu: [wasm32]
1439 bundledDependencies:
@@ -1392,31 +1444,27 @@ packages:
1444 - '@emnapi/wasi-threads'
1445 - tslib
1446
1395 - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
1396 - resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
1397 - engines: {node: '>= 10'}
1447 + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1':
1448 + resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==}
1449 + engines: {node: '>= 20'}
1450 cpu: [arm64]
1451 os: [win32]
1452
1401 - '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
1402 - resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
1403 - engines: {node: '>= 10'}
1453 + '@tailwindcss/oxide-win32-x64-msvc@4.2.1':
1454 + resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==}
1455 + engines: {node: '>= 20'}
1456 cpu: [x64]
1457 os: [win32]
1458
1407 - '@tailwindcss/oxide@4.1.18':
1408 - resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
1409 - engines: {node: '>= 10'}
1459 + '@tailwindcss/oxide@4.2.1':
1460 + resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==}
1461 + engines: {node: '>= 20'}
1462
1411 - '@tailwindcss/vite@4.1.18':
1412 - resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==}
1463 + '@tailwindcss/vite@4.2.1':
1464 + resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==}
1465 peerDependencies:
1466 vite: ^5.2.0 || ^6 || ^7
1467
1416 - '@trysound/sax@0.2.0':
1417 - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
1418 - engines: {node: '>=10.13.0'}
1419 -
1468 '@tsconfig/node20@20.1.9':
1469 resolution: {integrity: sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==}
1470
@@ -1450,8 +1498,8 @@ packages:
1498 '@types/hast@3.0.4':
1499 resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
1500
1453 - '@types/jsdom@27.0.0':
1454 - resolution: {integrity: sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==}
1501 + '@types/jsdom@28.0.0':
1502 + resolution: {integrity: sha512-A8TBQQC/xAOojy9kM8E46cqT00sF0h7dWjV8t8BJhUi2rG6JRh7XXQo/oLoENuZIQEpXsxLccLCnknyQd7qssQ==}
1503
1504 '@types/json-schema@7.0.15':
1505 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -1468,8 +1516,8 @@ packages:
1516 '@types/lodash-es@4.17.12':
1517 resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
1518
1471 - '@types/lodash@4.17.23':
1472 - resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==}
1519 + '@types/lodash@4.17.24':
1520 + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==}
1521
1522 '@types/markdown-it@14.1.2':
1523 resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
@@ -1486,12 +1534,15 @@ packages:
1534 '@types/ms@2.1.0':
1535 resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
1536
1489 - '@types/node@25.2.3':
1490 - resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==}
1537 + '@types/node@25.3.5':
1538 + resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==}
1539
1540 '@types/parse-json@4.0.2':
1541 resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
1542
1543 + '@types/sarif@2.1.7':
1544 + resolution: {integrity: sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==}
1545 +
1546 '@types/tern@0.23.9':
1547 resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==}
1548
@@ -1507,63 +1558,69 @@ packages:
1558 '@types/web-bluetooth@0.0.21':
1559 resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
1560
1510 - '@typescript-eslint/eslint-plugin@8.56.0':
1511 - resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==}
1561 + '@typescript-eslint/eslint-plugin@8.56.1':
1562 + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==}
1563 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1564 peerDependencies:
1514 - '@typescript-eslint/parser': ^8.56.0
1565 + '@typescript-eslint/parser': ^8.56.1
1566 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1567 typescript: '>=4.8.4 <6.0.0'
1568
1518 - '@typescript-eslint/parser@8.56.0':
1519 - resolution: {integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==}
1569 + '@typescript-eslint/parser@8.56.1':
1570 + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==}
1571 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1572 peerDependencies:
1573 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1574 typescript: '>=4.8.4 <6.0.0'
1575
1525 - '@typescript-eslint/project-service@8.56.0':
1526 - resolution: {integrity: sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==}
1576 + '@typescript-eslint/project-service@8.56.1':
1577 + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==}
1578 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1579 peerDependencies:
1580 typescript: '>=4.8.4 <6.0.0'
1581
1531 - '@typescript-eslint/scope-manager@8.56.0':
1532 - resolution: {integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==}
1582 + '@typescript-eslint/rule-tester@8.56.1':
1583 + resolution: {integrity: sha512-EWuV5Vq1EFYJEOVcILyWPO35PjnT0c6tv99PCpD12PgfZae5/Jo+F17hGjsEs2Moe+Dy1J7KIr8y037cK8+/rQ==}
1584 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1585 + peerDependencies:
1586 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1587 +
1588 + '@typescript-eslint/scope-manager@8.56.1':
1589 + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==}
1590 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1591
1535 - '@typescript-eslint/tsconfig-utils@8.56.0':
1536 - resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==}
1592 + '@typescript-eslint/tsconfig-utils@8.56.1':
1593 + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==}
1594 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1595 peerDependencies:
1596 typescript: '>=4.8.4 <6.0.0'
1597
1541 - '@typescript-eslint/type-utils@8.56.0':
1542 - resolution: {integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==}
1598 + '@typescript-eslint/type-utils@8.56.1':
1599 + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==}
1600 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1601 peerDependencies:
1602 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1603 typescript: '>=4.8.4 <6.0.0'
1604
1548 - '@typescript-eslint/types@8.56.0':
1549 - resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==}
1605 + '@typescript-eslint/types@8.56.1':
1606 + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==}
1607 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1608
1552 - '@typescript-eslint/typescript-estree@8.56.0':
1553 - resolution: {integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==}
1609 + '@typescript-eslint/typescript-estree@8.56.1':
1610 + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==}
1611 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1612 peerDependencies:
1613 typescript: '>=4.8.4 <6.0.0'
1614
1558 - '@typescript-eslint/utils@8.56.0':
1559 - resolution: {integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==}
1615 + '@typescript-eslint/utils@8.56.1':
1616 + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==}
1617 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1618 peerDependencies:
1619 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1620 typescript: '>=4.8.4 <6.0.0'
1621
1565 - '@typescript-eslint/visitor-keys@8.56.0':
1566 - resolution: {integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==}
1622 + '@typescript-eslint/visitor-keys@8.56.1':
1623 + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
1624 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1625
1626 '@ungap/structured-clone@1.3.0':
@@ -1625,14 +1682,14 @@ packages:
1682 '@vitest/utils@4.0.18':
1683 resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
1684
1628 - '@volar/language-core@2.4.27':
1629 - resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==}
1685 + '@volar/language-core@2.4.28':
1686 + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==}
1687
1631 - '@volar/source-map@2.4.27':
1632 - resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==}
1688 + '@volar/source-map@2.4.28':
1689 + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==}
1690
1634 - '@volar/typescript@2.4.27':
1635 - resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==}
1691 + '@volar/typescript@2.4.28':
1692 + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==}
1693
1694 '@vue-macros/common@3.1.2':
1695 resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==}
@@ -1678,15 +1735,27 @@ packages:
1735 '@vue/compiler-core@3.5.28':
1736 resolution: {integrity: sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==}
1737
1738 + '@vue/compiler-core@3.5.29':
1739 + resolution: {integrity: sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==}
1740 +
1741 '@vue/compiler-dom@3.5.28':
1742 resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==}
1743
1744 + '@vue/compiler-dom@3.5.29':
1745 + resolution: {integrity: sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==}
1746 +
1747 '@vue/compiler-sfc@3.5.28':
1748 resolution: {integrity: sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==}
1749
1750 + '@vue/compiler-sfc@3.5.29':
1751 + resolution: {integrity: sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==}
1752 +
1753 '@vue/compiler-ssr@3.5.28':
1754 resolution: {integrity: sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==}
1755
1756 + '@vue/compiler-ssr@3.5.29':
1757 + resolution: {integrity: sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==}
1758 +
1759 '@vue/devtools-api@6.6.4':
1760 resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
1761
@@ -1696,8 +1765,8 @@ packages:
1765 '@vue/devtools-api@8.0.6':
1766 resolution: {integrity: sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA==}
1767
1699 - '@vue/devtools-core@8.0.6':
1700 - resolution: {integrity: sha512-fN7iVtpSQQdtMORWwVZ1JiIAKriinhD+lCHqPw9Rr252ae2TczILEmW0zcAZifPW8HfYcbFkn+h7Wv6kQQCayw==}
1768 + '@vue/devtools-core@8.0.7':
1769 + resolution: {integrity: sha512-PmpiPxvg3Of80ODHVvyckxwEW1Z02VIAvARIZS1xegINn3VuNQLm9iHUmKD+o6cLkMNWV8OG8x7zo0kgydZgdg==}
1770 peerDependencies:
1771 vue: ^3.0.0
1772
@@ -1707,37 +1776,46 @@ packages:
1776 '@vue/devtools-kit@8.0.6':
1777 resolution: {integrity: sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw==}
1778
1779 + '@vue/devtools-kit@8.0.7':
1780 + resolution: {integrity: sha512-H6esJGHGl5q0E9iV3m2EoBQHJ+V83WMW83A0/+Fn95eZ2iIvdsq4+UCS6yT/Fdd4cGZSchx/MdWDreM3WqMsDw==}
1781 +
1782 '@vue/devtools-shared@7.7.9':
1783 resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
1784
1785 '@vue/devtools-shared@8.0.6':
1786 resolution: {integrity: sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg==}
1787
1716 - '@vue/language-core@3.2.4':
1717 - resolution: {integrity: sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==}
1788 + '@vue/devtools-shared@8.0.7':
1789 + resolution: {integrity: sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA==}
1790 +
1791 + '@vue/language-core@3.2.5':
1792 + resolution: {integrity: sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g==}
1793
1719 - '@vue/reactivity@3.5.28':
1720 - resolution: {integrity: sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==}
1794 + '@vue/reactivity@3.5.29':
1795 + resolution: {integrity: sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==}
1796
1722 - '@vue/runtime-core@3.5.28':
1723 - resolution: {integrity: sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==}
1797 + '@vue/runtime-core@3.5.29':
1798 + resolution: {integrity: sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==}
1799
1725 - '@vue/runtime-dom@3.5.28':
1726 - resolution: {integrity: sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==}
1800 + '@vue/runtime-dom@3.5.29':
1801 + resolution: {integrity: sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==}
1802
1728 - '@vue/server-renderer@3.5.28':
1729 - resolution: {integrity: sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==}
1803 + '@vue/server-renderer@3.5.29':
1804 + resolution: {integrity: sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==}
1805 peerDependencies:
1731 - vue: 3.5.28
1806 + vue: 3.5.29
1807
1808 '@vue/shared@3.5.28':
1809 resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==}
1810
1811 + '@vue/shared@3.5.29':
1812 + resolution: {integrity: sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==}
1813 +
1814 '@vue/test-utils@2.4.6':
1815 resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
1816
1739 - '@vue/tsconfig@0.8.1':
1740 - resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==}
1817 + '@vue/tsconfig@0.9.0':
1818 + resolution: {integrity: sha512-RP+v9Cpbsk1ZVXltCHHkYBr7+624x6gcijJXVjIcsYk7JXqvIpRtMwU2ARLvWDhmy9ffdFYxhsfJnPztADBohQ==}
1819 peerDependencies:
1820 typescript: 5.x
1821 vue: ^3.4.0
@@ -1790,17 +1868,27 @@ packages:
1868 peerDependencies:
1869 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
1870
1871 + acorn@7.4.1:
1872 + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
1873 + engines: {node: '>=0.4.0'}
1874 + hasBin: true
1875 +
1876 acorn@8.15.0:
1877 resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
1878 engines: {node: '>=0.4.0'}
1879 hasBin: true
1880
1881 + acorn@8.16.0:
1882 + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
1883 + engines: {node: '>=0.4.0'}
1884 + hasBin: true
1885 +
1886 agent-base@7.1.4:
1887 resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
1888 engines: {node: '>= 14'}
1889
1802 - ajv@6.12.6:
1803 - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
1890 + ajv@6.14.0:
1891 + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
1892
1893 algoliasearch@5.48.1:
1894 resolution: {integrity: sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==}
@@ -1857,6 +1945,12 @@ packages:
1945 resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==}
1946 engines: {node: '>=8'}
1947
1948 + asap@2.0.6:
1949 + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
1950 +
1951 + assert-never@1.4.0:
1952 + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==}
1953 +
1954 assertion-error@2.0.1:
1955 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
1956 engines: {node: '>=12'}
@@ -1875,8 +1969,15 @@ packages:
1969 asynckit@0.4.0:
1970 resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
1971
1878 - axios@1.13.5:
1879 - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==}
1972 + axios@1.13.6:
1973 + resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==}
1974 +
1975 + babel-walk@3.0.0-canary-5:
1976 + resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==}
1977 + engines: {node: '>= 10.0.0'}
1978 +
1979 + badgen@3.2.3:
1980 + resolution: {integrity: sha512-svDuwkc63E/z0ky3drpUppB83s/nlgDciH9m+STwwQoWyq7yCgew1qEfJ+9axkKdNq7MskByptWUN9j1PGMwFA==}
1981
1982 balanced-match@1.0.2:
1983 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1885,8 +1986,9 @@ packages:
1986 resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
1987 engines: {node: 20 || >=22}
1988
1888 - baseline-browser-mapping@2.9.19:
1889 - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
1989 + baseline-browser-mapping@2.10.0:
1990 + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==}
1991 + engines: {node: '>=6.0.0'}
1992 hasBin: true
1993
1994 bidi-js@1.0.3:
@@ -1895,6 +1997,10 @@ packages:
1997 birpc@2.9.0:
1998 resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
1999
2000 + blamer@1.0.7:
2001 + resolution: {integrity: sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA==}
2002 + engines: {node: '>=8.9'}
2003 +
2004 bluebird@3.7.2:
2005 resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
2006
@@ -1944,10 +2050,18 @@ packages:
2050 resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
2051 engines: {node: '>=8'}
2052
2053 + cac@7.0.0:
2054 + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==}
2055 + engines: {node: '>=20.19.0'}
2056 +
2057 call-bind-apply-helpers@1.0.2:
2058 resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
2059 engines: {node: '>= 0.4'}
2060
2061 + call-bound@1.0.4:
2062 + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
2063 + engines: {node: '>= 0.4'}
2064 +
2065 callsite@1.0.0:
2066 resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==}
2067
@@ -1981,6 +2095,9 @@ packages:
2095 character-entities@2.0.2:
2096 resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
2097
2098 + character-parser@2.2.0:
2099 + resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
2100 +
2101 check-more-types@2.24.0:
2102 resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==}
2103 engines: {node: '>= 0.8.0'}
@@ -2010,6 +2127,10 @@ packages:
2127 resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
2128 engines: {node: '>=4'}
2129
2130 + cli-table3@0.6.5:
2131 + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==}
2132 + engines: {node: 10.* || >= 12.*}
2133 +
2134 cliui@7.0.4:
2135 resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
2136
@@ -2030,6 +2151,10 @@ packages:
2151 colord@2.9.3:
2152 resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
2153
2154 + colors@1.4.0:
2155 + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
2156 + engines: {node: '>=0.1.90'}
2157 +
2158 combined-stream@1.0.8:
2159 resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
2160 engines: {node: '>= 0.8'}
@@ -2041,18 +2166,14 @@ packages:
2166 resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
2167 engines: {node: '>=14'}
2168
2169 + commander@5.1.0:
2170 + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
2171 + engines: {node: '>= 6'}
2172 +
2173 commander@7.2.0:
2174 resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
2175 engines: {node: '>= 10'}
2176
2048 - commander@8.3.0:
2049 - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
2050 - engines: {node: '>= 12'}
2051 -
2052 - comment-parser@1.4.1:
2053 - resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
2054 - engines: {node: '>= 12.0.0'}
2055 -
2177 comment-parser@1.4.5:
2178 resolution: {integrity: sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==}
2179 engines: {node: '>= 12.0.0'}
@@ -2073,6 +2194,9 @@ packages:
2194 resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
2195 engines: {node: ^14.18.0 || >=16.10.0}
2196
2197 + constantinople@4.0.1:
2198 + resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
2199 +
2200 convert-source-map@2.0.0:
2201 resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
2202
@@ -2226,14 +2350,13 @@ packages:
2350 devlop@1.1.0:
2351 resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
2352
2229 - diff-sequences@27.5.1:
2230 - resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==}
2231 - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
2232 -
2353 diff-sequences@29.6.3:
2354 resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
2355 engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
2356
2357 + doctypes@1.1.0:
2358 + resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==}
2359 +
2360 dom-serializer@2.0.0:
2361 resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
2362
@@ -2275,10 +2398,6 @@ packages:
2398 electron-to-chromium@1.5.286:
2399 resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
2400
2278 - emoji-regex-xs@2.0.1:
2279 - resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==}
2280 - engines: {node: '>=10.0.0'}
2281 -
2401 emoji-regex@8.0.0:
2402 resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
2403
@@ -2289,6 +2408,9 @@ packages:
2408 resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
2409 engines: {node: '>=14'}
2410
2411 + end-of-stream@1.4.5:
2412 + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
2413 +
2414 enhanced-resolve@5.19.0:
2415 resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==}
2416 engines: {node: '>=10.13.0'}
@@ -2360,16 +2482,10 @@ packages:
2482 peerDependencies:
2483 eslint: '>=6.0.0'
2484
2363 - eslint-compat-utils@0.6.5:
2364 - resolution: {integrity: sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==}
2365 - engines: {node: '>=12'}
2366 - peerDependencies:
2367 - eslint: '>=6.0.0'
2368 -
2369 - eslint-config-flat-gitignore@2.1.0:
2370 - resolution: {integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==}
2485 + eslint-config-flat-gitignore@2.2.1:
2486 + resolution: {integrity: sha512-wA5EqN0era7/7Gt5Botlsfin/UNY0etJSEeBgbUlFLFrBi47rAN//+39fI7fpYcl8RENutlFtvp/zRa/M/pZNg==}
2487 peerDependencies:
2372 - eslint: ^9.5.0
2488 + eslint: ^9.5.0 || ^10.0.0
2489
2490 eslint-flat-config-utils@3.0.1:
2491 resolution: {integrity: sha512-VMA3u86bLzNAwD/7DkLtQ9lolgIOx2Sj0kTMMnBvrvEz7w0rQj4aGCR+lqsqtld63gKiLyT4BnQZ3gmGDXtvjg==}
@@ -2395,41 +2511,42 @@ packages:
2511 peerDependencies:
2512 eslint: '*'
2513
2398 - eslint-plugin-command@3.4.0:
2399 - resolution: {integrity: sha512-EW4eg/a7TKEhG0s5IEti72kh3YOTlnhfFNuctq5WnB1fst37/IHTd5OkD+vnlRf3opTvUcSRihAateP6bT5ZcA==}
2514 + eslint-plugin-command@3.5.2:
2515 + resolution: {integrity: sha512-PA59QAkQDwvcCMEt5lYLJLI3zDGVKJeC4id/pcRY2XdRYhSGW7iyYT1VC1N3bmpuvu6Qb/9QptiS3GJMjeGTJg==}
2516 peerDependencies:
2517 + '@typescript-eslint/rule-tester': '*'
2518 + '@typescript-eslint/typescript-estree': '*'
2519 + '@typescript-eslint/utils': '*'
2520 eslint: '*'
2521
2522 + eslint-plugin-depend@1.5.0:
2523 + resolution: {integrity: sha512-i3UeLYmclf1Icp35+6W7CR4Bp2PIpDgBuf/mpmXK5UeLkZlvYJ21VuQKKHHAIBKRTPivPGX/gZl5JGno1o9Y0A==}
2524 + peerDependencies:
2525 + eslint: '>=8.40.0'
2526 +
2527 eslint-plugin-es-x@7.8.0:
2528 resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==}
2529 engines: {node: ^14.18.0 || >=16.0.0}
2530 peerDependencies:
2531 eslint: '>=8'
2532
2409 - eslint-plugin-import-lite@0.5.1:
2410 - resolution: {integrity: sha512-J+EqremfzXlB1WA/SKQxdZ2yeXgtpCorbcN0wS1KOTLnlSYiGR62MvAtM0zNWy1oDMjL/Lqkqf92Ahs1V5lyUg==}
2533 + eslint-plugin-import-lite@0.5.2:
2534 + resolution: {integrity: sha512-XvfdWOC5dSLEI9krIPRlNmKSI2ViIE9pVylzfV9fCq0ZpDaNeUk6o0wZv0OzN83QdadgXp1NsY0qjLINxwYCsw==}
2535 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2536 peerDependencies:
2537 eslint: '>=9.0.0'
2538
2415 - eslint-plugin-jsdoc@62.5.5:
2416 - resolution: {integrity: sha512-aRp0KVW26imgPqn17oSDnJdA3tlu+6D/xI/pqWzK5qDPQbldQ1Hsg84dYAsFyvGJhI+u/sGvzgGjMjlKQtUG/Q==}
2539 + eslint-plugin-jsdoc@62.7.1:
2540 + resolution: {integrity: sha512-4Zvx99Q7d1uggYBUX/AIjvoyqXhluGbbKrRmG8SQTLprPFg6fa293tVJH1o1GQwNe3lUydd8ZHzn37OaSncgSQ==}
2541 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2542 peerDependencies:
2419 - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
2543 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
2544
2421 - eslint-plugin-jsonc@2.21.1:
2422 - resolution: {integrity: sha512-dbNR5iEnQeORwsK2WZzr3QaMtFCY3kKJVMRHPzUpKzMhmVy2zIpVgFDpX8MNoIdoqz6KCpCfOJavhfiSbZbN+w==}
2423 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
2424 - peerDependencies:
2425 - eslint: '>=6.0.0'
2426 -
2427 - eslint-plugin-markdown-preferences@0.40.2:
2428 - resolution: {integrity: sha512-zaHQpfQijr/v7werfw6yQd05TpNoY6FTBTFAlxpFaOvoxfmQg89GLlU7KhrS0Ty++YCVVDgMJURwEn/c2w5n7g==}
2429 - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
2545 + eslint-plugin-jsonc@3.1.1:
2546 + resolution: {integrity: sha512-7TSQO8ZyvOuXWb0sYke3KUSh0DJA4/QviKfuzD3/Cy3XDjtrIrTWQbjb7j/Yy2l/DgwuM+lCS2c/jqJifv5jhg==}
2547 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2548 peerDependencies:
2431 - '@eslint/markdown': ^7.4.0
2432 - eslint: '>=9.0.0'
2549 + eslint: '>=9.38.0'
2550
2551 eslint-plugin-n@17.24.0:
2552 resolution: {integrity: sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==}
@@ -2441,16 +2558,16 @@ packages:
2558 resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==}
2559 engines: {node: '>=5.0.0'}
2560
2444 - eslint-plugin-perfectionist@5.5.0:
2445 - resolution: {integrity: sha512-lZX2KUpwOQf7J27gAg/6vt8ugdPULOLmelM8oDJPMbaN7P2zNNeyS9yxGSmJcKX0SF9qR/962l9RWM2Z5jpPzg==}
2561 + eslint-plugin-perfectionist@5.6.0:
2562 + resolution: {integrity: sha512-pxrLrfRp5wl1Vol1fAEa/G5yTXxefTPJjz07qC7a8iWFXcOZNuWBItMQ2OtTzfQIvMq6bMyYcrzc3Wz++na55Q==}
2563 engines: {node: ^20.0.0 || >=22.0.0}
2564 peerDependencies:
2448 - eslint: '>=8.45.0'
2565 + eslint: ^8.45.0 || ^9.0.0 || ^10.0.0
2566
2450 - eslint-plugin-pnpm@1.5.0:
2451 - resolution: {integrity: sha512-ayMo1GvrQ/sF/bz1aOAiH0jv9eAqU2Z+a1ycoWz/uFFK5NxQDq49BDKQtBumcOUBf2VHyiTW4a8u+6KVqoIWzQ==}
2567 + eslint-plugin-pnpm@1.6.0:
2568 + resolution: {integrity: sha512-dxmt9r3zvPaft6IugS4i0k16xag3fTbOvm/road5uV9Y8qUCQT0xzheSh3gMlYAlC6vXRpfArBDsTZ7H7JKCbg==}
2569 peerDependencies:
2453 - eslint: ^9.0.0
2570 + eslint: ^9.0.0 || ^10.0.0
2571
2572 eslint-plugin-regexp@3.0.0:
2573 resolution: {integrity: sha512-iW7hgAV8NOG6E2dz+VeKpq67YLQ9jaajOKYpoOSic2/q8y9BMdXBKkSR9gcMtbqEhNQzdW41E3wWzvhp8ExYwQ==}
@@ -2458,8 +2575,8 @@ packages:
2575 peerDependencies:
2576 eslint: '>=9.38.0'
2577
2461 - eslint-plugin-toml@1.1.0:
2462 - resolution: {integrity: sha512-0CFwlR5sUOqebF8fZOj10h8MwPkBPAFQPqnM3v5Otzp/RqIiXpfUOOBuTx/lh8Wa5WRxo29yWMA7x2k2F1kzcA==}
2578 + eslint-plugin-toml@1.3.1:
2579 + resolution: {integrity: sha512-1l00fBP03HIt9IPV7ZxBi7x0y0NMdEZmakL1jBD6N/FoKBvfKxPw5S8XkmzBecOnFBTn5Z8sNJtL5vdf9cpRMQ==}
2580 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2581 peerDependencies:
2582 eslint: '>=9.38.0'
@@ -2493,8 +2610,8 @@ packages:
2610 '@typescript-eslint/parser':
2611 optional: true
2612
2496 - eslint-plugin-yml@3.2.0:
2497 - resolution: {integrity: sha512-69gbDR+2IqaGJHyTXJ/FKJsv5QL3wKCfI2Z/rGjXwM88RxAqSOtM/AmF/YPhSWdHwezE8xyZbvDVnzgUPbO4ag==}
2613 + eslint-plugin-yml@3.3.1:
2614 + resolution: {integrity: sha512-isntsZchaTqDMNNkD+CakrgA/pdUoJ45USWBKpuqfAW1MCuw731xX/vrXfoJFZU3tTFr24nCbDYmDfT2+g4QtQ==}
2615 engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
2616 peerDependencies:
2617 eslint: '>=9.38.0'
@@ -2505,8 +2622,8 @@ packages:
2622 '@vue/compiler-sfc': ^3.3.0
2623 eslint: '>=9.0.0'
2624
2508 - eslint-scope@9.1.0:
2509 - resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==}
2625 + eslint-scope@9.1.1:
2626 + resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==}
2627 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2628
2629 eslint-visitor-keys@3.4.3:
@@ -2517,12 +2634,12 @@ packages:
2634 resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
2635 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2636
2520 - eslint-visitor-keys@5.0.0:
2521 - resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==}
2637 + eslint-visitor-keys@5.0.1:
2638 + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
2639 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2640
2524 - eslint@10.0.0:
2525 - resolution: {integrity: sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==}
2641 + eslint@10.0.2:
2642 + resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==}
2643 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2644 hasBin: true
2645 peerDependencies:
@@ -2535,14 +2652,10 @@ packages:
2652 resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
2653 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2654
2538 - espree@11.1.0:
2539 - resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==}
2655 + espree@11.1.1:
2656 + resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==}
2657 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2658
2542 - espree@9.6.1:
2543 - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
2544 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
2545 -
2659 esprima@4.0.1:
2660 resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
2661 engines: {node: '>=4'}
@@ -2573,9 +2686,16 @@ packages:
2686 event-stream@3.3.4:
2687 resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==}
2688
2689 + eventemitter3@5.0.4:
2690 + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
2691 +
2692 evtd@0.2.4:
2693 resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==}
2694
2695 + execa@4.1.0:
2696 + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==}
2697 + engines: {node: '>=10'}
2698 +
2699 execa@5.1.1:
2700 resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
2701 engines: {node: '>=10'}
@@ -2594,16 +2714,26 @@ packages:
2714 fast-deep-equal@3.1.3:
2715 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
2716
2717 + fast-glob@3.3.3:
2718 + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
2719 + engines: {node: '>=8.6.0'}
2720 +
2721 fast-json-stable-stringify@2.1.0:
2722 resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
2723
2724 fast-levenshtein@2.0.6:
2725 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
2726
2603 - fast-xml-parser@5.3.6:
2604 - resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==}
2727 + fast-xml-builder@1.0.0:
2728 + resolution: {integrity: sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==}
2729 +
2730 + fast-xml-parser@5.4.2:
2731 + resolution: {integrity: sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ==}
2732 hasBin: true
2733
2734 + fastq@1.20.1:
2735 + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
2736 +
2737 fault@2.0.1:
2738 resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
2739
@@ -2677,8 +2807,8 @@ packages:
2807 from@0.1.7:
2808 resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
2809
2680 - fs-extra@11.3.3:
2681 - resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==}
2810 + fs-extra@11.3.4:
2811 + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
2812 engines: {node: '>=14.14'}
2813
2814 fsevents@2.3.3:
@@ -2700,10 +2830,6 @@ packages:
2830 resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
2831 engines: {node: 6.* || 8.* || >= 10.*}
2832
2703 - get-east-asian-width@1.4.0:
2704 - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
2705 - engines: {node: '>=18'}
2706 -
2833 get-intrinsic@1.3.0:
2834 resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
2835 engines: {node: '>= 0.4'}
@@ -2712,6 +2838,10 @@ packages:
2838 resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
2839 engines: {node: '>= 0.4'}
2840
2841 + get-stream@5.2.0:
2842 + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
2843 + engines: {node: '>=8'}
2844 +
2845 get-stream@6.0.1:
2846 resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
2847 engines: {node: '>=10'}
@@ -2726,6 +2856,14 @@ packages:
2856 github-slugger@2.0.0:
2857 resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
2858
2859 + gitignore-to-glob@0.3.0:
2860 + resolution: {integrity: sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==}
2861 + engines: {node: '>=4.4 <5 || >=6.9'}
2862 +
2863 + glob-parent@5.1.2:
2864 + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
2865 + engines: {node: '>= 6'}
2866 +
2867 glob-parent@6.0.2:
2868 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
2869 engines: {node: '>=10.13.0'}
@@ -2751,8 +2889,8 @@ packages:
2889 resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
2890 engines: {node: '>=18'}
2891
2754 - globals@17.3.0:
2755 - resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==}
2892 + globals@17.4.0:
2893 + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==}
2894 engines: {node: '>=18'}
2895
2896 globrex@0.1.2:
@@ -2765,9 +2903,6 @@ packages:
2903 graceful-fs@4.2.11:
2904 resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
2905
2768 - graphemer@1.4.0:
2769 - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
2770 -
2906 has-symbols@1.1.0:
2907 resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
2908 engines: {node: '>= 0.4'}
@@ -2821,6 +2956,10 @@ packages:
2956 resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
2957 engines: {node: '>= 14'}
2958
2959 + human-signals@1.1.1:
2960 + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==}
2961 + engines: {node: '>=8.12.0'}
2962 +
2963 human-signals@2.1.0:
2964 resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
2965 engines: {node: '>=10.17.0'}
@@ -2879,6 +3018,9 @@ packages:
3018 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
3019 hasBin: true
3020
3021 + is-expression@4.0.0:
3022 + resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==}
3023 +
3024 is-extglob@2.1.1:
3025 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
3026 engines: {node: '>=0.10.0'}
@@ -2903,6 +3045,13 @@ packages:
3045 is-potential-custom-element-name@1.0.1:
3046 resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
3047
3048 + is-promise@2.2.2:
3049 + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
3050 +
3051 + is-regex@1.2.1:
3052 + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
3053 + engines: {node: '>= 0.4'}
3054 +
3055 is-stream@2.0.1:
3056 resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
3057 engines: {node: '>=8'}
@@ -2945,8 +3094,8 @@ packages:
3094 resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==}
3095 engines: {node: '>= 20'}
3096
2948 - jose@6.1.3:
2949 - resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
3097 + jose@6.2.0:
3098 + resolution: {integrity: sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ==}
3099
3100 js-beautify@1.15.4:
3101 resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}
@@ -2960,6 +3109,9 @@ packages:
3109 js-md5@0.8.3:
3110 resolution: {integrity: sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==}
3111
3112 + js-stringify@1.0.2:
3113 + resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
3114 +
3115 js-tokens@4.0.0:
3116 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
3117
@@ -2967,9 +3119,12 @@ packages:
3119 resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
3120 hasBin: true
3121
2970 - jsdoc-type-pratt-parser@7.0.0:
2971 - resolution: {integrity: sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==}
2972 - engines: {node: '>=20.0.0'}
3122 + jscpd-sarif-reporter@4.0.6:
3123 + resolution: {integrity: sha512-b9Sm3IPZ3+m8Lwa4gZa+4/LhDhlc/ZLEsLXKSOy1DANQ6kx0ueqZT+fUHWEdQ6m0o3+RIVIa7DmvLSojQD05ng==}
3124 +
3125 + jscpd@4.0.8:
3126 + resolution: {integrity: sha512-d2VNT/2Hv4dxT2/59He8Lyda4DYOxPRyRG9zBaOpTZAqJCVf2xLrBlZkT8Va6Lo9u3X2qz8Bpq4HrDi4JsrQhA==}
3127 + hasBin: true
3128
3129 jsdoc-type-pratt-parser@7.1.1:
3130 resolution: {integrity: sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==}
@@ -3010,16 +3165,15 @@ packages:
3165 engines: {node: '>=6'}
3166 hasBin: true
3167
3013 - jsonc-eslint-parser@2.4.2:
3014 - resolution: {integrity: sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==}
3015 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
3168 + jsonc-eslint-parser@3.1.0:
3169 + resolution: {integrity: sha512-75EA7EWZExL/j+MDKQrRbdzcRI2HOkRlmUw8fZJc1ioqFEOvBsq7Rt+A6yCxOt9w/TYNpkt52gC6nm/g5tFIng==}
3170 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
3171
3172 jsonfile@6.2.0:
3173 resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
3174
3020 - katex@0.16.28:
3021 - resolution: {integrity: sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==}
3022 - hasBin: true
3175 + jstransformer@1.0.0:
3176 + resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
3177
3178 keyv@4.5.4:
3179 resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -3042,78 +3196,78 @@ packages:
3196 resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
3197 engines: {node: '>= 0.8.0'}
3198
3045 - lightningcss-android-arm64@1.30.2:
3046 - resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
3199 + lightningcss-android-arm64@1.31.1:
3200 + resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==}
3201 engines: {node: '>= 12.0.0'}
3202 cpu: [arm64]
3203 os: [android]
3204
3051 - lightningcss-darwin-arm64@1.30.2:
3052 - resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
3205 + lightningcss-darwin-arm64@1.31.1:
3206 + resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==}
3207 engines: {node: '>= 12.0.0'}
3208 cpu: [arm64]
3209 os: [darwin]
3210
3057 - lightningcss-darwin-x64@1.30.2:
3058 - resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
3211 + lightningcss-darwin-x64@1.31.1:
3212 + resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==}
3213 engines: {node: '>= 12.0.0'}
3214 cpu: [x64]
3215 os: [darwin]
3216
3063 - lightningcss-freebsd-x64@1.30.2:
3064 - resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
3217 + lightningcss-freebsd-x64@1.31.1:
3218 + resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==}
3219 engines: {node: '>= 12.0.0'}
3220 cpu: [x64]
3221 os: [freebsd]
3222
3069 - lightningcss-linux-arm-gnueabihf@1.30.2:
3070 - resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
3223 + lightningcss-linux-arm-gnueabihf@1.31.1:
3224 + resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==}
3225 engines: {node: '>= 12.0.0'}
3226 cpu: [arm]
3227 os: [linux]
3228
3075 - lightningcss-linux-arm64-gnu@1.30.2:
3076 - resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
3229 + lightningcss-linux-arm64-gnu@1.31.1:
3230 + resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==}
3231 engines: {node: '>= 12.0.0'}
3232 cpu: [arm64]
3233 os: [linux]
3234 libc: [glibc]
3235
3082 - lightningcss-linux-arm64-musl@1.30.2:
3083 - resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
3236 + lightningcss-linux-arm64-musl@1.31.1:
3237 + resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==}
3238 engines: {node: '>= 12.0.0'}
3239 cpu: [arm64]
3240 os: [linux]
3241 libc: [musl]
3242
3089 - lightningcss-linux-x64-gnu@1.30.2:
3090 - resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
3243 + lightningcss-linux-x64-gnu@1.31.1:
3244 + resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==}
3245 engines: {node: '>= 12.0.0'}
3246 cpu: [x64]
3247 os: [linux]
3248 libc: [glibc]
3249
3096 - lightningcss-linux-x64-musl@1.30.2:
3097 - resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
3250 + lightningcss-linux-x64-musl@1.31.1:
3251 + resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==}
3252 engines: {node: '>= 12.0.0'}
3253 cpu: [x64]
3254 os: [linux]
3255 libc: [musl]
3256
3103 - lightningcss-win32-arm64-msvc@1.30.2:
3104 - resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
3257 + lightningcss-win32-arm64-msvc@1.31.1:
3258 + resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==}
3259 engines: {node: '>= 12.0.0'}
3260 cpu: [arm64]
3261 os: [win32]
3262
3109 - lightningcss-win32-x64-msvc@1.30.2:
3110 - resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
3263 + lightningcss-win32-x64-msvc@1.31.1:
3264 + resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==}
3265 engines: {node: '>= 12.0.0'}
3266 cpu: [x64]
3267 os: [win32]
3268
3115 - lightningcss@1.30.2:
3116 - resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
3269 + lightningcss@1.31.1:
3270 + resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==}
3271 engines: {node: '>= 12.0.0'}
3272
3273 lines-and-columns@1.2.4:
@@ -3133,6 +3287,9 @@ packages:
3287 lodash-es@4.17.23:
3288 resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==}
3289
3290 + lodash.merge@4.6.2:
3291 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
3292 +
3293 lodash@4.17.21:
3294 resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
3295
@@ -3170,6 +3327,9 @@ packages:
3327 resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
3328 hasBin: true
3329
3330 + markdown-table@2.0.0:
3331 + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==}
3332 +
3333 markdown-table@3.0.4:
3334 resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
3335
@@ -3204,9 +3364,6 @@ packages:
3364 mdast-util-gfm@3.1.0:
3365 resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
3366
3207 - mdast-util-math@3.0.0:
3208 - resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==}
3209 -
3367 mdast-util-phrasing@4.1.0:
3368 resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
3369
@@ -3238,6 +3395,10 @@ packages:
3395 merge-stream@2.0.0:
3396 resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
3397
3398 + merge2@1.4.1:
3399 + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
3400 + engines: {node: '>= 8'}
3401 +
3402 micromark-core-commonmark@2.0.3:
3403 resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
3404
@@ -3265,9 +3426,6 @@ packages:
3426 micromark-extension-gfm@3.0.0:
3427 resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
3428
3268 - micromark-extension-math@3.1.0:
3269 - resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==}
3270 -
3429 micromark-factory-destination@2.0.1:
3430 resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
3431
@@ -3352,6 +3510,10 @@ packages:
3510 resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==}
3511 engines: {node: 20 || >=22}
3512
3513 + minimatch@10.2.4:
3514 + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
3515 + engines: {node: 18 || 20 || >=22}
3516 +
3517 minimatch@3.1.2:
3518 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
3519
@@ -3380,6 +3542,9 @@ packages:
3542 mlly@1.8.0:
3543 resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
3544
3545 + module-replacements@2.11.0:
3546 + resolution: {integrity: sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==}
3547 +
3548 mrmime@2.0.1:
3549 resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
3550 engines: {node: '>=10'}
@@ -3425,6 +3590,10 @@ packages:
3590 node-releases@2.0.27:
3591 resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
3592
3593 + node-sarif-builder@3.4.0:
3594 + resolution: {integrity: sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==}
3595 + engines: {node: '>=20'}
3596 +
3597 nopt@7.2.1:
3598 resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
3599 engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -3451,6 +3620,10 @@ packages:
3620 engines: {node: '>=18'}
3621 hasBin: true
3622
3623 + object-assign@4.1.1:
3624 + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
3625 + engines: {node: '>=0.10.0'}
3626 +
3627 object-deep-merge@2.0.0:
3628 resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==}
3629
@@ -3463,6 +3636,9 @@ packages:
3636 ohash@2.0.11:
3637 resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
3638
3639 + once@1.4.0:
3640 + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
3641 +
3642 onetime@5.1.2:
3643 resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
3644 engines: {node: '>=6'}
@@ -3621,8 +3797,8 @@ packages:
3797 resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
3798 engines: {node: '>=4'}
3799
3624 - pnpm-workspace-yaml@1.5.0:
3625 - resolution: {integrity: sha512-PxdyJuFvq5B0qm3s9PaH/xOtSxrcvpBRr+BblhucpWjs8c79d4b7/cXhyY4AyHOHCnqklCYZTjfl0bT/mFVTRw==}
3800 + pnpm-workspace-yaml@1.6.0:
3801 + resolution: {integrity: sha512-uUy4dK3E11sp7nK+hnT7uAWfkBMe00KaUw8OG3NuNlYQoTk4sc9pcdIy1+XIP85v9Tvr02mK3JPaNNrP0QyRaw==}
3802
3803 popmotion@11.0.5:
3804 resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
@@ -3699,6 +3875,9 @@ packages:
3875 engines: {node: '>=14'}
3876 hasBin: true
3877
3878 + promise@7.3.1:
3879 + resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
3880 +
3881 property-information@7.1.0:
3882 resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
3883
@@ -3713,6 +3892,45 @@ packages:
3892 engines: {node: '>= 0.10'}
3893 hasBin: true
3894
3895 + pug-attrs@3.0.0:
3896 + resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==}
3897 +
3898 + pug-code-gen@3.0.3:
3899 + resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==}
3900 +
3901 + pug-error@2.1.0:
3902 + resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==}
3903 +
3904 + pug-filters@4.0.0:
3905 + resolution: {integrity: sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==}
3906 +
3907 + pug-lexer@5.0.1:
3908 + resolution: {integrity: sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==}
3909 +
3910 + pug-linker@4.0.0:
3911 + resolution: {integrity: sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==}
3912 +
3913 + pug-load@3.0.0:
3914 + resolution: {integrity: sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==}
3915 +
3916 + pug-parser@6.0.0:
3917 + resolution: {integrity: sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==}
3918 +
3919 + pug-runtime@3.0.1:
3920 + resolution: {integrity: sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==}
3921 +
3922 + pug-strip-comments@2.0.0:
3923 + resolution: {integrity: sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==}
3924 +
3925 + pug-walk@2.0.0:
3926 + resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==}
3927 +
3928 + pug@3.0.3:
3929 + resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==}
3930 +
3931 + pump@3.0.4:
3932 + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
3933 +
3934 punycode.js@2.3.1:
3935 resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
3936 engines: {node: '>=6'}
@@ -3727,6 +3945,9 @@ packages:
3945 quansync@1.0.0:
3946 resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}
3947
3948 + queue-microtask@1.2.3:
3949 + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
3950 +
3951 rc9@2.1.2:
3952 resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
3953
@@ -3774,6 +3995,13 @@ packages:
3995 resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
3996 hasBin: true
3997
3998 + repeat-string@1.6.1:
3999 + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
4000 + engines: {node: '>=0.10'}
4001 +
4002 + reprism@0.0.11:
4003 + resolution: {integrity: sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA==}
4004 +
4005 require-directory@2.1.1:
4006 resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
4007 engines: {node: '>=0.10.0'}
@@ -3813,6 +4041,10 @@ packages:
4041 resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
4042 engines: {node: '>=18'}
4043
4044 + reusify@1.1.0:
4045 + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
4046 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
4047 +
4048 rfdc@1.4.1:
4049 resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
4050
@@ -3838,6 +4070,9 @@ packages:
4070 resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
4071 engines: {node: '>=18'}
4072
4073 + run-parallel@1.2.0:
4074 + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
4075 +
4076 rxjs@7.8.2:
4077 resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
4078
@@ -3846,6 +4081,10 @@ packages:
4081 engines: {node: '>=14.0.0'}
4082 hasBin: true
4083
4084 + sax@1.5.0:
4085 + resolution: {integrity: sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==}
4086 + engines: {node: '>=11.0.0'}
4087 +
4088 saxes@6.0.0:
4089 resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
4090 engines: {node: '>=v12.22.7'}
@@ -3888,8 +4127,9 @@ packages:
4127 resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
4128 engines: {node: '>= 0.4'}
4129
3891 - shiki@3.22.0:
3892 - resolution: {integrity: sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==}
4130 + shiki@4.0.1:
4131 + resolution: {integrity: sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ==}
4132 + engines: {node: '>=20'}
4133
4134 siginfo@2.0.0:
4135 resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@@ -3922,6 +4162,9 @@ packages:
4162 space-separated-tokens@2.0.2:
4163 resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
4164
4165 + spark-md5@3.0.2:
4166 + resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
4167 +
4168 spdx-exceptions@2.5.0:
4169 resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
4170
@@ -3944,8 +4187,8 @@ packages:
4187 stackback@0.0.2:
4188 resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
4189
3947 - start-server-and-test@2.1.3:
3948 - resolution: {integrity: sha512-k4EcbNjeg0odaDkAMlIeDVDByqX9PIgL4tivgP2tES6Zd8o+4pTq/HgbWCyA3VHIoZopB+wGnNPKYGGSByNriQ==}
4190 + start-server-and-test@2.1.5:
4191 + resolution: {integrity: sha512-A/SbXpgXE25ScSkpLLqvGvVZT0ykN6+AzS8tVqMBCTxbJy2Nwuen59opT+afalK5aS+AuQmZs0EsLwjnuDN+/g==}
4192 engines: {node: '>=16'}
4193 hasBin: true
4194
@@ -3963,10 +4206,6 @@ packages:
4206 resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
4207 engines: {node: '>=12'}
4208
3966 - string-width@8.1.1:
3967 - resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==}
3968 - engines: {node: '>=20'}
3969 -
4209 stringify-entities@4.0.4:
4210 resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
4211
@@ -4003,8 +4242,8 @@ packages:
4242 resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
4243 engines: {node: '>= 0.4'}
4244
4006 - svgo@3.3.2:
4007 - resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
4245 + svgo@3.3.3:
4246 + resolution: {integrity: sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==}
4247 engines: {node: '>=14.0.0'}
4248 hasBin: true
4249
@@ -4019,15 +4258,15 @@ packages:
4258 resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
4259 engines: {node: '>=20'}
4260
4022 - tailwindcss@4.1.18:
4023 - resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
4261 + tailwindcss@4.2.1:
4262 + resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==}
4263
4264 tapable@2.3.0:
4265 resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
4266 engines: {node: '>=6'}
4267
4029 - taze@19.9.2:
4030 - resolution: {integrity: sha512-If8bq7lSckIMPfXV+C9jjEfdsQnRryh/foKfpX/ah6zI0TrQfUGWSGCaaD32Bqy5/KGRmLZie3EwMSr3Au21XQ==}
4268 + taze@19.10.0:
4269 + resolution: {integrity: sha512-pylMr+Yl8m4ZXu5LwWdtfCOJhLW69NuoeZTLtRzTekfheQ1ix5wOWjQlTb8S3SSxLlDcYFuajQOWllO5iyE0jg==}
4270 hasBin: true
4271
4272 thememirror@2.0.1:
@@ -4074,6 +4313,9 @@ packages:
4313 resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==}
4314 engines: {node: '>=20'}
4315
4316 + token-stream@1.0.0:
4317 + resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==}
4318 +
4319 toml-eslint-parser@1.0.3:
4320 resolution: {integrity: sha512-A5F0cM6+mDleacLIEUkmfpkBbnHJFV1d2rprHU2MXNk7mlxHq2zGojA+SRvQD1RoMo9gqjZPWEaKG4v1BQ48lw==}
4321 engines: {node: ^20.19.0 || ^22.13.0 || >=24}
@@ -4144,8 +4386,11 @@ packages:
4386 unctx@2.5.0:
4387 resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==}
4388
4147 - undici-types@7.16.0:
4148 - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
4389 + undici-types@7.18.2:
4390 + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
4391 +
4392 + undici-types@7.22.0:
4393 + resolution: {integrity: sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw==}
4394
4395 undici@7.22.0:
4396 resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
@@ -4157,9 +4402,6 @@ packages:
4402 unist-util-position@5.0.0:
4403 resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
4404
4160 - unist-util-remove-position@5.0.0:
4161 - resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==}
4162 -
4405 unist-util-stringify-position@4.0.0:
4406 resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
4407
@@ -4241,19 +4483,19 @@ packages:
4483 '@nuxt/kit':
4484 optional: true
4485
4244 - vite-plugin-vue-devtools@8.0.6:
4245 - resolution: {integrity: sha512-IiTCIJDb1ZliOT8fPbYXllyfgARzz1+R1r8RN9ScGIDzAB6o8bDME1a9JjrfdSJibL7i8DIPQH+pGv0U7haBeA==}
4486 + vite-plugin-vue-devtools@8.0.7:
4487 + resolution: {integrity: sha512-BWj/ykGpqVAJVdPyHmSTUm44buz3jPv+6jnvuFdQSRH0kAgP1cEIE4doHiFyqHXOmuB5EQVR/nh2g9YRiRNs9g==}
4488 engines: {node: '>=v14.21.3'}
4489 peerDependencies:
4248 - vite: ^6.0.0 || ^7.0.0-0
4490 + vite: ^6.0.0 || ^7.0.0-0 || ^8.0.0-0
4491
4492 vite-plugin-vue-inspector@5.3.2:
4493 resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==}
4494 peerDependencies:
4495 vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
4496
4255 - vite-svg-loader@5.1.0:
4256 - resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==}
4497 + vite-svg-loader@5.1.1:
4498 + resolution: {integrity: sha512-RPzcXA/EpKJA0585x58DBgs7my2VfeJ+j2j1EoHY4Zh82Y7hV4cR1fElgy2aZi85+QSrcLLoTStQ5uZjD68u+Q==}
4499 peerDependencies:
4500 vue: '>=3.2.13'
4501
@@ -4331,6 +4573,10 @@ packages:
4573 jsdom:
4574 optional: true
4575
4576 + void-elements@3.1.0:
4577 + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
4578 + engines: {node: '>=0.10.0'}
4579 +
4580 vooks@0.2.12:
4581 resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==}
4582 peerDependencies:
@@ -4371,8 +4617,8 @@ packages:
4617 peerDependencies:
4618 vue: ^3.0.0
4619
4374 - vue-router@5.0.2:
4375 - resolution: {integrity: sha512-YFhwaE5c5JcJpNB1arpkl4/GnO32wiUWRB+OEj1T0DlDxEZoOfbltl2xEwktNU/9o1sGcGburIXSpbLpPFe/6w==}
4620 + vue-router@5.0.3:
4621 + resolution: {integrity: sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==}
4622 peerDependencies:
4623 '@pinia/colada': '>=0.21.2'
4624 '@vue/compiler-sfc': ^3.5.17
@@ -4391,16 +4637,16 @@ packages:
4637 peerDependencies:
4638 vue: ^3.3.4
4639
4394 - vue-tsc@3.2.4:
4395 - resolution: {integrity: sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==}
4640 + vue-tsc@3.2.5:
4641 + resolution: {integrity: sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA==}
4642 hasBin: true
4643 peerDependencies:
4644 typescript: '>=5.0.0'
4645
4400 - vue3-apexcharts@1.10.0:
4401 - resolution: {integrity: sha512-sBma2In4rU5n/JBrv8KVb8if+IoY019Dse2yRDD/eRU1WGZHK07zuy9erefKzbJ7T3wP9+Jsy9bH6Vdjy85HZg==}
4646 + vue3-apexcharts@1.11.1:
4647 + resolution: {integrity: sha512-MbN3vg8bMG19wc0Lm1HkeQvODgLm56DgpIxtNUO0xpf/JCzYWVGE4jzXp2JISzy2s3Kul1yOxNQUYsLvKQ5L9g==}
4648 peerDependencies:
4403 - apexcharts: '>=4.0.0'
4649 + apexcharts: '>=5.10.0'
4650 vue: '>=3.0.0'
4651
4652 vue3-marquee@4.2.2:
@@ -4409,8 +4655,8 @@ packages:
4655 peerDependencies:
4656 vue: ^3.2
4657
4412 - vue@3.5.28:
4413 - resolution: {integrity: sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==}
4658 + vue@3.5.29:
4659 + resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==}
4660 peerDependencies:
4661 typescript: '*'
4662 peerDependenciesMeta:
@@ -4434,8 +4680,8 @@ packages:
4680 resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
4681 engines: {node: '>=18'}
4682
4437 - wait-on@9.0.3:
4438 - resolution: {integrity: sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==}
4683 + wait-on@9.0.4:
4684 + resolution: {integrity: sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==}
4685 engines: {node: '>=20.0.0'}
4686 hasBin: true
4687
@@ -4473,6 +4719,10 @@ packages:
4719 engines: {node: '>=8'}
4720 hasBin: true
4721
4722 + with@7.0.2:
4723 + resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
4724 + engines: {node: '>= 10.0.0'}
4725 +
4726 word-wrap@1.2.5:
4727 resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
4728 engines: {node: '>=0.10.0'}
@@ -4485,6 +4735,9 @@ packages:
4735 resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
4736 engines: {node: '>=12'}
4737
4738 + wrappy@1.0.2:
4739 + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
4740 +
4741 wsl-utils@0.1.0:
4742 resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
4743 engines: {node: '>=18'}
@@ -4647,48 +4900,52 @@ snapshots:
4900 dependencies:
4901 '@algolia/client-common': 5.48.1
4902
4650 - '@antfu/eslint-config@7.4.3(@vue/compiler-sfc@3.5.28)(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))':
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))':
4904 dependencies:
4905 '@antfu/install-pkg': 1.1.0
4653 - '@clack/prompts': 1.0.1
4654 - '@eslint-community/eslint-plugin-eslint-comments': 4.6.0(eslint@10.0.0(jiti@2.6.1))
4906 + '@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))
4909 '@eslint/markdown': 7.5.1
4656 - '@stylistic/eslint-plugin': 5.8.0(eslint@10.0.0(jiti@2.6.1))
4657 - '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
4658 - '@typescript-eslint/parser': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
4659 - '@vitest/eslint-plugin': 1.6.9(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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))
4914 ansis: 4.2.0
4661 - cac: 6.7.14
4662 - eslint: 10.0.0(jiti@2.6.1)
4663 - eslint-config-flat-gitignore: 2.1.0(eslint@10.0.0(jiti@2.6.1))
4915 + 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
4665 - eslint-merge-processors: 2.0.0(eslint@10.0.0(jiti@2.6.1))
4666 - eslint-plugin-antfu: 3.2.2(eslint@10.0.0(jiti@2.6.1))
4667 - eslint-plugin-command: 3.4.0(eslint@10.0.0(jiti@2.6.1))
4668 - eslint-plugin-import-lite: 0.5.1(eslint@10.0.0(jiti@2.6.1))
4669 - eslint-plugin-jsdoc: 62.5.5(eslint@10.0.0(jiti@2.6.1))
4670 - eslint-plugin-jsonc: 2.21.1(eslint@10.0.0(jiti@2.6.1))
4671 - eslint-plugin-n: 17.24.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
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)
4926 eslint-plugin-no-only-tests: 3.3.0
4673 - eslint-plugin-perfectionist: 5.5.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
4674 - eslint-plugin-pnpm: 1.5.0(eslint@10.0.0(jiti@2.6.1))
4675 - eslint-plugin-regexp: 3.0.0(eslint@10.0.0(jiti@2.6.1))
4676 - eslint-plugin-toml: 1.1.0(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1))
4677 - eslint-plugin-unicorn: 63.0.0(eslint@10.0.0(jiti@2.6.1))
4678 - eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))
4679 - eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.8.0(eslint@10.0.0(jiti@2.6.1)))(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.0.0(jiti@2.6.1)))
4680 - eslint-plugin-yml: 3.2.0(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1))
4681 - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.28)(eslint@10.0.0(jiti@2.6.1))
4682 - globals: 17.3.0
4683 - jsonc-eslint-parser: 2.4.2
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))
4936 + globals: 17.4.0
4937 local-pkg: 1.1.2
4938 parse-gitignore: 2.0.0
4939 toml-eslint-parser: 1.0.3
4687 - vue-eslint-parser: 10.4.0(eslint@10.0.0(jiti@2.6.1))
4940 + vue-eslint-parser: 10.4.0(eslint@10.0.2(jiti@2.6.1))
4941 yaml-eslint-parser: 2.0.0
4942 transitivePeerDependencies:
4943 - '@eslint/json'
4944 + - '@typescript-eslint/rule-tester'
4945 + - '@typescript-eslint/typescript-estree'
4946 + - '@typescript-eslint/utils'
4947 - '@vue/compiler-sfc'
4948 + - oxlint
4949 - supports-color
4950 - typescript
4951 - vitest
@@ -4698,7 +4955,7 @@ snapshots:
4955 package-manager-detector: 1.6.0
4956 tinyexec: 1.0.2
4957
4701 - '@antfu/ni@27.0.1':
4958 + '@antfu/ni@28.2.0':
4959 dependencies:
4960 ansis: 4.2.0
4961 fzf: 0.5.2
@@ -4919,38 +5176,36 @@ snapshots:
5176 dependencies:
5177 css-tree: 3.1.0
5178
4922 - '@clack/core@1.0.1':
5179 + '@clack/core@1.1.0':
5180 dependencies:
4924 - picocolors: 1.1.1
5181 sisteransi: 1.0.5
5182
4927 - '@clack/prompts@1.0.1':
5183 + '@clack/prompts@1.1.0':
5184 dependencies:
4929 - '@clack/core': 1.0.1
4930 - picocolors: 1.1.1
5185 + '@clack/core': 1.1.0
5186 sisteransi: 1.0.5
5187
5188 '@codemirror/autocomplete@6.20.0':
5189 dependencies:
5190 '@codemirror/language': 6.12.1
5191 '@codemirror/state': 6.5.4
4937 - '@codemirror/view': 6.39.14
5192 + '@codemirror/view': 6.39.16
5193 '@lezer/common': 1.5.1
5194
5195 '@codemirror/commands@6.10.2':
5196 dependencies:
5197 '@codemirror/language': 6.12.1
5198 '@codemirror/state': 6.5.4
4944 - '@codemirror/view': 6.39.14
5199 + '@codemirror/view': 6.39.16
5200 '@lezer/common': 1.5.1
5201
4947 - '@codemirror/lang-javascript@6.2.4':
5202 + '@codemirror/lang-javascript@6.2.5':
5203 dependencies:
5204 '@codemirror/autocomplete': 6.20.0
5205 '@codemirror/language': 6.12.1
4951 - '@codemirror/lint': 6.9.4
5206 + '@codemirror/lint': 6.9.5
5207 '@codemirror/state': 6.5.4
4953 - '@codemirror/view': 6.39.14
5208 + '@codemirror/view': 6.39.16
5209 '@lezer/common': 1.5.1
5210 '@lezer/javascript': 1.5.4
5211
@@ -4959,29 +5214,29 @@ snapshots:
5214 '@codemirror/autocomplete': 6.20.0
5215 '@codemirror/language': 6.12.1
5216 '@codemirror/state': 6.5.4
4962 - '@codemirror/view': 6.39.14
5217 + '@codemirror/view': 6.39.16
5218 '@lezer/common': 1.5.1
5219 '@lezer/xml': 1.0.6
5220
5221 '@codemirror/language@6.12.1':
5222 dependencies:
5223 '@codemirror/state': 6.5.4
4969 - '@codemirror/view': 6.39.14
5224 + '@codemirror/view': 6.39.16
5225 '@lezer/common': 1.5.1
5226 '@lezer/highlight': 1.2.3
5227 '@lezer/lr': 1.4.8
5228 style-mod: 4.1.3
5229
4975 - '@codemirror/lint@6.9.4':
5230 + '@codemirror/lint@6.9.5':
5231 dependencies:
5232 '@codemirror/state': 6.5.4
4978 - '@codemirror/view': 6.39.14
5233 + '@codemirror/view': 6.39.16
5234 crelt: 1.0.6
5235
5236 '@codemirror/search@6.6.0':
5237 dependencies:
5238 '@codemirror/state': 6.5.4
4984 - '@codemirror/view': 6.39.14
5239 + '@codemirror/view': 6.39.16
5240 crelt: 1.0.6
5241
5242 '@codemirror/state@6.5.4':
@@ -4992,23 +5247,26 @@ snapshots:
5247 dependencies:
5248 '@codemirror/language': 6.12.1
5249 '@codemirror/state': 6.5.4
4995 - '@codemirror/view': 6.39.14
5250 + '@codemirror/view': 6.39.16
5251 '@lezer/highlight': 1.2.3
5252
4998 - '@codemirror/view@6.39.14':
5253 + '@codemirror/view@6.39.16':
5254 dependencies:
5255 '@codemirror/state': 6.5.4
5256 crelt: 1.0.6
5257 style-mod: 4.1.3
5258 w3c-keyname: 2.2.8
5259
5260 + '@colors/colors@1.5.0':
5261 + optional: true
5262 +
5263 '@css-render/plugin-bem@0.15.14(css-render@0.15.14)':
5264 dependencies:
5265 css-render: 0.15.14
5266
5009 - '@css-render/vue3-ssr@0.15.14(vue@3.5.28(typescript@5.9.3))':
5267 + '@css-render/vue3-ssr@0.15.14(vue@3.5.29(typescript@5.9.3))':
5268 dependencies:
5011 - vue: 3.5.28(typescript@5.9.3)
5269 + vue: 3.5.29(typescript@5.9.3)
5270
5271 '@csstools/color-helpers@6.0.1': {}
5272
@@ -5032,20 +5290,18 @@ snapshots:
5290
5291 '@csstools/css-tokenizer@4.0.0': {}
5292
5035 - '@emotion/hash@0.8.0': {}
5036 -
5037 - '@es-joy/jsdoccomment@0.78.0':
5293 + '@e18e/eslint-plugin@0.2.0(eslint@10.0.2(jiti@2.6.1))':
5294 dependencies:
5039 - '@types/estree': 1.0.8
5040 - '@typescript-eslint/types': 8.56.0
5041 - comment-parser: 1.4.1
5042 - esquery: 1.7.0
5043 - jsdoc-type-pratt-parser: 7.0.0
5295 + eslint-plugin-depend: 1.5.0(eslint@10.0.2(jiti@2.6.1))
5296 + optionalDependencies:
5297 + eslint: 10.0.2(jiti@2.6.1)
5298 +
5299 + '@emotion/hash@0.8.0': {}
5300
5301 '@es-joy/jsdoccomment@0.84.0':
5302 dependencies:
5303 '@types/estree': 1.0.8
5048 - '@typescript-eslint/types': 8.56.0
5304 + '@typescript-eslint/types': 8.56.1
5305 comment-parser: 1.4.5
5306 esquery: 1.7.0
5307 jsdoc-type-pratt-parser: 7.1.1
@@ -5130,28 +5386,28 @@ snapshots:
5386 '@esbuild/win32-x64@0.27.3':
5387 optional: true
5388
5133 - '@eslint-community/eslint-plugin-eslint-comments@4.6.0(eslint@10.0.0(jiti@2.6.1))':
5389 + '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.0.2(jiti@2.6.1))':
5390 dependencies:
5391 escape-string-regexp: 4.0.0
5136 - eslint: 10.0.0(jiti@2.6.1)
5392 + eslint: 10.0.2(jiti@2.6.1)
5393 ignore: 7.0.5
5394
5139 - '@eslint-community/eslint-utils@4.9.1(eslint@10.0.0(jiti@2.6.1))':
5395 + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.2(jiti@2.6.1))':
5396 dependencies:
5141 - eslint: 10.0.0(jiti@2.6.1)
5397 + eslint: 10.0.2(jiti@2.6.1)
5398 eslint-visitor-keys: 3.4.3
5399
5400 '@eslint-community/regexpp@4.12.2': {}
5401
5146 - '@eslint/compat@1.4.1(eslint@10.0.0(jiti@2.6.1))':
5402 + '@eslint/compat@2.0.2(eslint@10.0.2(jiti@2.6.1))':
5403 dependencies:
5148 - '@eslint/core': 0.17.0
5404 + '@eslint/core': 1.1.0
5405 optionalDependencies:
5150 - eslint: 10.0.0(jiti@2.6.1)
5406 + eslint: 10.0.2(jiti@2.6.1)
5407
5152 - '@eslint/config-array@0.23.1':
5408 + '@eslint/config-array@0.23.2':
5409 dependencies:
5154 - '@eslint/object-schema': 3.0.1
5410 + '@eslint/object-schema': 3.0.2
5411 debug: 4.4.3
5412 minimatch: 10.2.1
5413 transitivePeerDependencies:
@@ -5183,7 +5439,7 @@ snapshots:
5439 transitivePeerDependencies:
5440 - supports-color
5441
5186 - '@eslint/object-schema@3.0.1': {}
5442 + '@eslint/object-schema@3.0.2': {}
5443
5444 '@eslint/plugin-kit@0.4.1':
5445 dependencies:
@@ -5197,10 +5453,10 @@ snapshots:
5453
5454 '@exodus/bytes@1.14.1': {}
5455
5200 - '@f3ve/vue-markdown-it@0.2.3(vue@3.5.28(typescript@5.9.3))':
5456 + '@f3ve/vue-markdown-it@0.2.3(vue@3.5.29(typescript@5.9.3))':
5457 dependencies:
5458 markdown-it: 14.1.1
5203 - vue: 3.5.28(typescript@5.9.3)
5459 + vue: 3.5.29(typescript@5.9.3)
5460
5461 '@fontsource/jetbrains-mono@5.2.8': {}
5462
@@ -5224,6 +5480,8 @@ snapshots:
5480 dependencies:
5481 '@hapi/hoek': 11.0.7
5482
5483 + '@henrygd/queue@1.2.0': {}
5484 +
5485 '@humanfs/core@0.19.1': {}
5486
5487 '@humanfs/node@0.16.7':
@@ -5237,10 +5495,10 @@ snapshots:
5495
5496 '@iconify/types@2.0.0': {}
5497
5240 - '@iconify/vue@5.0.0(vue@3.5.28(typescript@5.9.3))':
5498 + '@iconify/vue@5.0.0(vue@3.5.29(typescript@5.9.3))':
5499 dependencies:
5500 '@iconify/types': 2.0.0
5243 - vue: 3.5.28(typescript@5.9.3)
5501 + vue: 3.5.29(typescript@5.9.3)
5502
5503 '@intlify/core-base@11.2.8':
5504 dependencies:
@@ -5284,6 +5542,41 @@ snapshots:
5542 '@jridgewell/resolve-uri': 3.1.2
5543 '@jridgewell/sourcemap-codec': 1.5.5
5544
5545 + '@jscpd/badge-reporter@4.0.4':
5546 + dependencies:
5547 + badgen: 3.2.3
5548 + colors: 1.4.0
5549 + fs-extra: 11.3.4
5550 +
5551 + '@jscpd/core@4.0.4':
5552 + dependencies:
5553 + eventemitter3: 5.0.4
5554 +
5555 + '@jscpd/finder@4.0.4':
5556 + dependencies:
5557 + '@jscpd/core': 4.0.4
5558 + '@jscpd/tokenizer': 4.0.4
5559 + blamer: 1.0.7
5560 + bytes: 3.1.2
5561 + cli-table3: 0.6.5
5562 + colors: 1.4.0
5563 + fast-glob: 3.3.3
5564 + fs-extra: 11.3.4
5565 + markdown-table: 2.0.0
5566 + pug: 3.0.3
5567 +
5568 + '@jscpd/html-reporter@4.0.4':
5569 + dependencies:
5570 + colors: 1.4.0
5571 + fs-extra: 11.3.4
5572 + pug: 3.0.3
5573 +
5574 + '@jscpd/tokenizer@4.0.4':
5575 + dependencies:
5576 + '@jscpd/core': 4.0.4
5577 + reprism: 0.0.11
5578 + spark-md5: 3.0.2
5579 +
5580 '@juggle/resize-observer@3.4.0': {}
5581
5582 '@lezer/common@1.5.1': {}
@@ -5312,6 +5605,18 @@ snapshots:
5605
5606 '@microsoft/fetch-event-source@2.0.1': {}
5607
5608 + '@nodelib/fs.scandir@2.1.5':
5609 + dependencies:
5610 + '@nodelib/fs.stat': 2.0.5
5611 + run-parallel: 1.2.0
5612 +
5613 + '@nodelib/fs.stat@2.0.5': {}
5614 +
5615 + '@nodelib/fs.walk@1.2.8':
5616 + dependencies:
5617 + '@nodelib/fs.scandir': 2.1.5
5618 + fastq: 1.20.1
5619 +
5620 '@nuxt/kit@3.21.1':
5621 dependencies:
5622 c12: 3.3.3
@@ -5341,13 +5646,7 @@ snapshots:
5646
5647 '@one-ini/wasm@0.1.1': {}
5648
5344 - '@ota-meshi/ast-token-store@0.2.0(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1))':
5345 - dependencies:
5346 - '@eslint/markdown': 7.5.1
5347 - eslint: 10.0.0(jiti@2.6.1)
5348 - eslint-plugin-markdown-preferences: 0.40.2(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1))
5349 - transitivePeerDependencies:
5350 - - supports-color
5649 + '@ota-meshi/ast-token-store@0.3.0': {}
5650
5651 '@parcel/watcher-android-arm64@2.5.6':
5652 optional: true
@@ -5500,38 +5799,45 @@ snapshots:
5799 '@rollup/rollup-win32-x64-msvc@4.57.1':
5800 optional: true
5801
5503 - '@shikijs/core@3.22.0':
5802 + '@shikijs/core@4.0.1':
5803 dependencies:
5505 - '@shikijs/types': 3.22.0
5804 + '@shikijs/primitive': 4.0.1
5805 + '@shikijs/types': 4.0.1
5806 '@shikijs/vscode-textmate': 10.0.2
5807 '@types/hast': 3.0.4
5808 hast-util-to-html: 9.0.5
5809
5510 - '@shikijs/engine-javascript@3.22.0':
5810 + '@shikijs/engine-javascript@4.0.1':
5811 dependencies:
5512 - '@shikijs/types': 3.22.0
5812 + '@shikijs/types': 4.0.1
5813 '@shikijs/vscode-textmate': 10.0.2
5814 oniguruma-to-es: 4.3.4
5815
5516 - '@shikijs/engine-oniguruma@3.22.0':
5816 + '@shikijs/engine-oniguruma@4.0.1':
5817 dependencies:
5518 - '@shikijs/types': 3.22.0
5818 + '@shikijs/types': 4.0.1
5819 '@shikijs/vscode-textmate': 10.0.2
5820
5521 - '@shikijs/langs@3.22.0':
5821 + '@shikijs/langs@4.0.1':
5822 dependencies:
5523 - '@shikijs/types': 3.22.0
5823 + '@shikijs/types': 4.0.1
5824
5525 - '@shikijs/markdown-it@3.22.0':
5825 + '@shikijs/markdown-it@4.0.1':
5826 dependencies:
5827 markdown-it: 14.1.1
5528 - shiki: 3.22.0
5828 + shiki: 4.0.1
5829
5530 - '@shikijs/themes@3.22.0':
5830 + '@shikijs/primitive@4.0.1':
5831 dependencies:
5532 - '@shikijs/types': 3.22.0
5832 + '@shikijs/types': 4.0.1
5833 + '@shikijs/vscode-textmate': 10.0.2
5834 + '@types/hast': 3.0.4
5835 +
5836 + '@shikijs/themes@4.0.1':
5837 + dependencies:
5838 + '@shikijs/types': 4.0.1
5839
5534 - '@shikijs/types@3.22.0':
5840 + '@shikijs/types@4.0.1':
5841 dependencies:
5842 '@shikijs/vscode-textmate': 10.0.2
5843 '@types/hast': 3.0.4
@@ -5546,85 +5852,83 @@ snapshots:
5852
5853 '@standard-schema/spec@1.1.0': {}
5854
5549 - '@stylistic/eslint-plugin@5.8.0(eslint@10.0.0(jiti@2.6.1))':
5855 + '@stylistic/eslint-plugin@5.10.0(eslint@10.0.2(jiti@2.6.1))':
5856 dependencies:
5551 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
5552 - '@typescript-eslint/types': 8.56.0
5553 - eslint: 10.0.0(jiti@2.6.1)
5857 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
5858 + '@typescript-eslint/types': 8.56.1
5859 + eslint: 10.0.2(jiti@2.6.1)
5860 eslint-visitor-keys: 4.2.1
5861 espree: 10.4.0
5862 estraverse: 5.3.0
5863 picomatch: 4.0.3
5864
5559 - '@tailwindcss/node@4.1.18':
5865 + '@tailwindcss/node@4.2.1':
5866 dependencies:
5867 '@jridgewell/remapping': 2.3.5
5868 enhanced-resolve: 5.19.0
5869 jiti: 2.6.1
5564 - lightningcss: 1.30.2
5870 + lightningcss: 1.31.1
5871 magic-string: 0.30.21
5872 source-map-js: 1.2.1
5567 - tailwindcss: 4.1.18
5873 + tailwindcss: 4.2.1
5874
5569 - '@tailwindcss/oxide-android-arm64@4.1.18':
5875 + '@tailwindcss/oxide-android-arm64@4.2.1':
5876 optional: true
5877
5572 - '@tailwindcss/oxide-darwin-arm64@4.1.18':
5878 + '@tailwindcss/oxide-darwin-arm64@4.2.1':
5879 optional: true
5880
5575 - '@tailwindcss/oxide-darwin-x64@4.1.18':
5881 + '@tailwindcss/oxide-darwin-x64@4.2.1':
5882 optional: true
5883
5578 - '@tailwindcss/oxide-freebsd-x64@4.1.18':
5884 + '@tailwindcss/oxide-freebsd-x64@4.2.1':
5885 optional: true
5886
5581 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
5887 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1':
5888 optional: true
5889
5584 - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
5890 + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1':
5891 optional: true
5892
5587 - '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
5893 + '@tailwindcss/oxide-linux-arm64-musl@4.2.1':
5894 optional: true
5895
5590 - '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
5896 + '@tailwindcss/oxide-linux-x64-gnu@4.2.1':
5897 optional: true
5898
5593 - '@tailwindcss/oxide-linux-x64-musl@4.1.18':
5899 + '@tailwindcss/oxide-linux-x64-musl@4.2.1':
5900 optional: true
5901
5596 - '@tailwindcss/oxide-wasm32-wasi@4.1.18':
5902 + '@tailwindcss/oxide-wasm32-wasi@4.2.1':
5903 optional: true
5904
5599 - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
5905 + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1':
5906 optional: true
5907
5602 - '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
5908 + '@tailwindcss/oxide-win32-x64-msvc@4.2.1':
5909 optional: true
5910
5605 - '@tailwindcss/oxide@4.1.18':
5911 + '@tailwindcss/oxide@4.2.1':
5912 optionalDependencies:
5607 - '@tailwindcss/oxide-android-arm64': 4.1.18
5608 - '@tailwindcss/oxide-darwin-arm64': 4.1.18
5609 - '@tailwindcss/oxide-darwin-x64': 4.1.18
5610 - '@tailwindcss/oxide-freebsd-x64': 4.1.18
5611 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
5612 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
5613 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.18
5614 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.18
5615 - '@tailwindcss/oxide-linux-x64-musl': 4.1.18
5616 - '@tailwindcss/oxide-wasm32-wasi': 4.1.18
5617 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
5618 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
5619 -
5620 - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))':
5621 - dependencies:
5622 - '@tailwindcss/node': 4.1.18
5623 - '@tailwindcss/oxide': 4.1.18
5624 - tailwindcss: 4.1.18
5625 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
5626 -
5627 - '@trysound/sax@0.2.0': {}
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)
5932
5933 '@tsconfig/node20@20.1.9': {}
5934
@@ -5654,23 +5958,24 @@ snapshots:
5958 '@types/fs-extra@11.0.4':
5959 dependencies:
5960 '@types/jsonfile': 6.1.4
5657 - '@types/node': 25.2.3
5961 + '@types/node': 25.3.5
5962
5963 '@types/hast@3.0.4':
5964 dependencies:
5965 '@types/unist': 3.0.3
5966
5663 - '@types/jsdom@27.0.0':
5967 + '@types/jsdom@28.0.0':
5968 dependencies:
5665 - '@types/node': 25.2.3
5969 + '@types/node': 25.3.5
5970 '@types/tough-cookie': 4.0.5
5971 parse5: 7.3.0
5972 + undici-types: 7.22.0
5973
5974 '@types/json-schema@7.0.15': {}
5975
5976 '@types/jsonfile@6.1.4':
5977 dependencies:
5673 - '@types/node': 25.2.3
5978 + '@types/node': 25.3.5
5979
5980 '@types/katex@0.16.8': {}
5981
@@ -5678,9 +5983,9 @@ snapshots:
5983
5984 '@types/lodash-es@4.17.12':
5985 dependencies:
5681 - '@types/lodash': 4.17.23
5986 + '@types/lodash': 4.17.24
5987
5683 - '@types/lodash@4.17.23': {}
5988 + '@types/lodash@4.17.24': {}
5989
5990 '@types/markdown-it@14.1.2':
5991 dependencies:
@@ -5697,12 +6002,14 @@ snapshots:
6002
6003 '@types/ms@2.1.0': {}
6004
5700 - '@types/node@25.2.3':
6005 + '@types/node@25.3.5':
6006 dependencies:
5702 - undici-types: 7.16.0
6007 + undici-types: 7.18.2
6008
6009 '@types/parse-json@4.0.2': {}
6010
6011 + '@types/sarif@2.1.7': {}
6012 +
6013 '@types/tern@0.23.9':
6014 dependencies:
6015 '@types/estree': 1.0.8
@@ -5715,15 +6022,15 @@ snapshots:
6022
6023 '@types/web-bluetooth@0.0.21': {}
6024
5718 - '@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
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)':
6026 dependencies:
6027 '@eslint-community/regexpp': 4.12.2
5721 - '@typescript-eslint/parser': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5722 - '@typescript-eslint/scope-manager': 8.56.0
5723 - '@typescript-eslint/type-utils': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5724 - '@typescript-eslint/utils': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5725 - '@typescript-eslint/visitor-keys': 8.56.0
5726 - eslint: 10.0.0(jiti@2.6.1)
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)
6034 ignore: 7.0.5
6035 natural-compare: 1.4.0
6036 ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -5731,58 +6038,72 @@ snapshots:
6038 transitivePeerDependencies:
6039 - supports-color
6040
5734 - '@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
6041 + '@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6042 dependencies:
5736 - '@typescript-eslint/scope-manager': 8.56.0
5737 - '@typescript-eslint/types': 8.56.0
5738 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
5739 - '@typescript-eslint/visitor-keys': 8.56.0
6043 + '@typescript-eslint/scope-manager': 8.56.1
6044 + '@typescript-eslint/types': 8.56.1
6045 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
6046 + '@typescript-eslint/visitor-keys': 8.56.1
6047 debug: 4.4.3
5741 - eslint: 10.0.0(jiti@2.6.1)
6048 + eslint: 10.0.2(jiti@2.6.1)
6049 typescript: 5.9.3
6050 transitivePeerDependencies:
6051 - supports-color
6052
5746 - '@typescript-eslint/project-service@8.56.0(typescript@5.9.3)':
6053 + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)':
6054 dependencies:
5748 - '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3)
5749 - '@typescript-eslint/types': 8.56.0
6055 + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3)
6056 + '@typescript-eslint/types': 8.56.1
6057 debug: 4.4.3
6058 typescript: 5.9.3
6059 transitivePeerDependencies:
6060 - supports-color
6061
5755 - '@typescript-eslint/scope-manager@8.56.0':
6062 + '@typescript-eslint/rule-tester@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6063 dependencies:
5757 - '@typescript-eslint/types': 8.56.0
5758 - '@typescript-eslint/visitor-keys': 8.56.0
6064 + '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
6065 + '@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)
6067 + ajv: 6.14.0
6068 + eslint: 10.0.2(jiti@2.6.1)
6069 + json-stable-stringify-without-jsonify: 1.0.1
6070 + lodash.merge: 4.6.2
6071 + semver: 7.7.4
6072 + transitivePeerDependencies:
6073 + - supports-color
6074 + - typescript
6075 +
6076 + '@typescript-eslint/scope-manager@8.56.1':
6077 + dependencies:
6078 + '@typescript-eslint/types': 8.56.1
6079 + '@typescript-eslint/visitor-keys': 8.56.1
6080
5760 - '@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.9.3)':
6081 + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)':
6082 dependencies:
6083 typescript: 5.9.3
6084
5764 - '@typescript-eslint/type-utils@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
6085 + '@typescript-eslint/type-utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6086 dependencies:
5766 - '@typescript-eslint/types': 8.56.0
5767 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
5768 - '@typescript-eslint/utils': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
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)
6090 debug: 4.4.3
5770 - eslint: 10.0.0(jiti@2.6.1)
6091 + eslint: 10.0.2(jiti@2.6.1)
6092 ts-api-utils: 2.4.0(typescript@5.9.3)
6093 typescript: 5.9.3
6094 transitivePeerDependencies:
6095 - supports-color
6096
5776 - '@typescript-eslint/types@8.56.0': {}
6097 + '@typescript-eslint/types@8.56.1': {}
6098
5778 - '@typescript-eslint/typescript-estree@8.56.0(typescript@5.9.3)':
6099 + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)':
6100 dependencies:
5780 - '@typescript-eslint/project-service': 8.56.0(typescript@5.9.3)
5781 - '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3)
5782 - '@typescript-eslint/types': 8.56.0
5783 - '@typescript-eslint/visitor-keys': 8.56.0
6101 + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3)
6102 + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3)
6103 + '@typescript-eslint/types': 8.56.1
6104 + '@typescript-eslint/visitor-keys': 8.56.1
6105 debug: 4.4.3
5785 - minimatch: 9.0.5
6106 + minimatch: 10.2.4
6107 semver: 7.7.4
6108 tinyglobby: 0.2.15
6109 ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -5790,50 +6111,50 @@ snapshots:
6111 transitivePeerDependencies:
6112 - supports-color
6113
5793 - '@typescript-eslint/utils@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
6114 + '@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
6115 dependencies:
5795 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
5796 - '@typescript-eslint/scope-manager': 8.56.0
5797 - '@typescript-eslint/types': 8.56.0
5798 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3)
5799 - eslint: 10.0.0(jiti@2.6.1)
6116 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
6117 + '@typescript-eslint/scope-manager': 8.56.1
6118 + '@typescript-eslint/types': 8.56.1
6119 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
6120 + eslint: 10.0.2(jiti@2.6.1)
6121 typescript: 5.9.3
6122 transitivePeerDependencies:
6123 - supports-color
6124
5804 - '@typescript-eslint/visitor-keys@8.56.0':
6125 + '@typescript-eslint/visitor-keys@8.56.1':
6126 dependencies:
5806 - '@typescript-eslint/types': 8.56.0
5807 - eslint-visitor-keys: 5.0.0
6127 + '@typescript-eslint/types': 8.56.1
6128 + eslint-visitor-keys: 5.0.1
6129
6130 '@ungap/structured-clone@1.3.0': {}
6131
5811 - '@vitejs/plugin-vue-jsx@5.1.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))':
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))':
6133 dependencies:
6134 '@babel/core': 7.29.0
6135 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
6136 '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
6137 '@rolldown/pluginutils': 1.0.0-rc.4
6138 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0)
5818 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
5819 - vue: 3.5.28(typescript@5.9.3)
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)
6141 transitivePeerDependencies:
6142 - supports-color
6143
5823 - '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))':
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))':
6145 dependencies:
6146 '@rolldown/pluginutils': 1.0.0-rc.2
5826 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
5827 - vue: 3.5.28(typescript@5.9.3)
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)
6149
5829 - '@vitest/eslint-plugin@1.6.9(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))':
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))':
6151 dependencies:
5831 - '@typescript-eslint/scope-manager': 8.56.0
5832 - '@typescript-eslint/utils': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
5833 - eslint: 10.0.0(jiti@2.6.1)
6152 + '@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)
6155 optionalDependencies:
6156 typescript: 5.9.3
5836 - vitest: 4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
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)
6158 transitivePeerDependencies:
6159 - supports-color
6160
@@ -5846,13 +6167,13 @@ snapshots:
6167 chai: 6.2.2
6168 tinyrainbow: 3.0.3
6169
5849 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))':
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))':
6171 dependencies:
6172 '@vitest/spy': 4.0.18
6173 estree-walker: 3.0.3
6174 magic-string: 0.30.21
6175 optionalDependencies:
5855 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
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)
6177
6178 '@vitest/pretty-format@4.0.18':
6179 dependencies:
@@ -5876,19 +6197,19 @@ snapshots:
6197 '@vitest/pretty-format': 4.0.18
6198 tinyrainbow: 3.0.3
6199
5879 - '@volar/language-core@2.4.27':
6200 + '@volar/language-core@2.4.28':
6201 dependencies:
5881 - '@volar/source-map': 2.4.27
6202 + '@volar/source-map': 2.4.28
6203
5883 - '@volar/source-map@2.4.27': {}
6204 + '@volar/source-map@2.4.28': {}
6205
5885 - '@volar/typescript@2.4.27':
6206 + '@volar/typescript@2.4.28':
6207 dependencies:
5887 - '@volar/language-core': 2.4.27
6208 + '@volar/language-core': 2.4.28
6209 path-browserify: 1.0.1
6210 vscode-uri: 3.1.0
6211
5891 - '@vue-macros/common@3.1.2(vue@3.5.28(typescript@5.9.3))':
6212 + '@vue-macros/common@3.1.2(vue@3.5.29(typescript@5.9.3))':
6213 dependencies:
6214 '@vue/compiler-sfc': 3.5.28
6215 ast-kit: 2.2.0
@@ -5896,7 +6217,7 @@ snapshots:
6217 magic-string-ast: 1.0.3
6218 unplugin-utils: 0.3.1
6219 optionalDependencies:
5899 - vue: 3.5.28(typescript@5.9.3)
6220 + vue: 3.5.29(typescript@5.9.3)
6221
6222 '@vue/babel-helper-vue-transform-on@1.5.0': {}
6223
@@ -5964,11 +6285,24 @@ snapshots:
6285 estree-walker: 2.0.2
6286 source-map-js: 1.2.1
6287
6288 + '@vue/compiler-core@3.5.29':
6289 + dependencies:
6290 + '@babel/parser': 7.29.0
6291 + '@vue/shared': 3.5.29
6292 + entities: 7.0.1
6293 + estree-walker: 2.0.2
6294 + source-map-js: 1.2.1
6295 +
6296 '@vue/compiler-dom@3.5.28':
6297 dependencies:
6298 '@vue/compiler-core': 3.5.28
6299 '@vue/shared': 3.5.28
6300
6301 + '@vue/compiler-dom@3.5.29':
6302 + dependencies:
6303 + '@vue/compiler-core': 3.5.29
6304 + '@vue/shared': 3.5.29
6305 +
6306 '@vue/compiler-sfc@3.5.28':
6307 dependencies:
6308 '@babel/parser': 7.29.0
@@ -5981,11 +6315,28 @@ snapshots:
6315 postcss: 8.5.6
6316 source-map-js: 1.2.1
6317
6318 + '@vue/compiler-sfc@3.5.29':
6319 + dependencies:
6320 + '@babel/parser': 7.29.0
6321 + '@vue/compiler-core': 3.5.29
6322 + '@vue/compiler-dom': 3.5.29
6323 + '@vue/compiler-ssr': 3.5.29
6324 + '@vue/shared': 3.5.29
6325 + estree-walker: 2.0.2
6326 + magic-string: 0.30.21
6327 + postcss: 8.5.6
6328 + source-map-js: 1.2.1
6329 +
6330 '@vue/compiler-ssr@3.5.28':
6331 dependencies:
6332 '@vue/compiler-dom': 3.5.28
6333 '@vue/shared': 3.5.28
6334
6335 + '@vue/compiler-ssr@3.5.29':
6336 + dependencies:
6337 + '@vue/compiler-dom': 3.5.29
6338 + '@vue/shared': 3.5.29
6339 +
6340 '@vue/devtools-api@6.6.4': {}
6341
6342 '@vue/devtools-api@7.7.9':
@@ -5996,17 +6347,11 @@ snapshots:
6347 dependencies:
6348 '@vue/devtools-kit': 8.0.6
6349
5999 - '@vue/devtools-core@8.0.6(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))':
6350 + '@vue/devtools-core@8.0.7(vue@3.5.29(typescript@5.9.3))':
6351 dependencies:
6001 - '@vue/devtools-kit': 8.0.6
6002 - '@vue/devtools-shared': 8.0.6
6003 - mitt: 3.0.1
6004 - nanoid: 5.1.6
6005 - pathe: 2.0.3
6006 - vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
6007 - vue: 3.5.28(typescript@5.9.3)
6008 - transitivePeerDependencies:
6009 - - vite
6352 + '@vue/devtools-kit': 8.0.7
6353 + '@vue/devtools-shared': 8.0.7
6354 + vue: 3.5.29(typescript@5.9.3)
6355
6356 '@vue/devtools-kit@7.7.9':
6357 dependencies:
@@ -6028,6 +6373,13 @@ snapshots:
6373 speakingurl: 14.0.1
6374 superjson: 2.2.6
6375
6376 + '@vue/devtools-kit@8.0.7':
6377 + dependencies:
6378 + '@vue/devtools-shared': 8.0.7
6379 + birpc: 2.9.0
6380 + hookable: 5.5.3
6381 + perfect-debounce: 2.1.0
6382 +
6383 '@vue/devtools-shared@7.7.9':
6384 dependencies:
6385 rfdc: 1.4.1
@@ -6036,9 +6388,11 @@ snapshots:
6388 dependencies:
6389 rfdc: 1.4.1
6390
6039 - '@vue/language-core@3.2.4':
6391 + '@vue/devtools-shared@8.0.7': {}
6392 +
6393 + '@vue/language-core@3.2.5':
6394 dependencies:
6041 - '@volar/language-core': 2.4.27
6395 + '@volar/language-core': 2.4.28
6396 '@vue/compiler-dom': 3.5.28
6397 '@vue/shared': 3.5.28
6398 alien-signals: 3.1.2
@@ -6046,93 +6400,99 @@ snapshots:
6400 path-browserify: 1.0.1
6401 picomatch: 4.0.3
6402
6049 - '@vue/reactivity@3.5.28':
6403 + '@vue/reactivity@3.5.29':
6404 dependencies:
6051 - '@vue/shared': 3.5.28
6405 + '@vue/shared': 3.5.29
6406
6053 - '@vue/runtime-core@3.5.28':
6407 + '@vue/runtime-core@3.5.29':
6408 dependencies:
6055 - '@vue/reactivity': 3.5.28
6056 - '@vue/shared': 3.5.28
6409 + '@vue/reactivity': 3.5.29
6410 + '@vue/shared': 3.5.29
6411
6058 - '@vue/runtime-dom@3.5.28':
6412 + '@vue/runtime-dom@3.5.29':
6413 dependencies:
6060 - '@vue/reactivity': 3.5.28
6061 - '@vue/runtime-core': 3.5.28
6062 - '@vue/shared': 3.5.28
6414 + '@vue/reactivity': 3.5.29
6415 + '@vue/runtime-core': 3.5.29
6416 + '@vue/shared': 3.5.29
6417 csstype: 3.2.3
6418
6065 - '@vue/server-renderer@3.5.28(vue@3.5.28(typescript@5.9.3))':
6419 + '@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3))':
6420 dependencies:
6067 - '@vue/compiler-ssr': 3.5.28
6068 - '@vue/shared': 3.5.28
6069 - vue: 3.5.28(typescript@5.9.3)
6421 + '@vue/compiler-ssr': 3.5.29
6422 + '@vue/shared': 3.5.29
6423 + vue: 3.5.29(typescript@5.9.3)
6424
6425 '@vue/shared@3.5.28': {}
6426
6427 + '@vue/shared@3.5.29': {}
6428 +
6429 '@vue/test-utils@2.4.6':
6430 dependencies:
6431 js-beautify: 1.15.4
6432 vue-component-type-helpers: 2.2.12
6433
6078 - '@vue/tsconfig@0.8.1(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))':
6434 + '@vue/tsconfig@0.9.0(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))':
6435 optionalDependencies:
6436 typescript: 5.9.3
6081 - vue: 3.5.28(typescript@5.9.3)
6437 + vue: 3.5.29(typescript@5.9.3)
6438
6083 - '@vueuse/core@13.9.0(vue@3.5.28(typescript@5.9.3))':
6439 + '@vueuse/core@13.9.0(vue@3.5.29(typescript@5.9.3))':
6440 dependencies:
6441 '@types/web-bluetooth': 0.0.21
6442 '@vueuse/metadata': 13.9.0
6087 - '@vueuse/shared': 13.9.0(vue@3.5.28(typescript@5.9.3))
6088 - vue: 3.5.28(typescript@5.9.3)
6443 + '@vueuse/shared': 13.9.0(vue@3.5.29(typescript@5.9.3))
6444 + vue: 3.5.29(typescript@5.9.3)
6445
6090 - '@vueuse/core@14.2.1(vue@3.5.28(typescript@5.9.3))':
6446 + '@vueuse/core@14.2.1(vue@3.5.29(typescript@5.9.3))':
6447 dependencies:
6448 '@types/web-bluetooth': 0.0.21
6449 '@vueuse/metadata': 14.2.1
6094 - '@vueuse/shared': 14.2.1(vue@3.5.28(typescript@5.9.3))
6095 - vue: 3.5.28(typescript@5.9.3)
6450 + '@vueuse/shared': 14.2.1(vue@3.5.29(typescript@5.9.3))
6451 + vue: 3.5.29(typescript@5.9.3)
6452
6453 '@vueuse/metadata@13.9.0': {}
6454
6455 '@vueuse/metadata@14.2.1': {}
6456
6101 - '@vueuse/motion@3.0.3(vue@3.5.28(typescript@5.9.3))':
6457 + '@vueuse/motion@3.0.3(vue@3.5.29(typescript@5.9.3))':
6458 dependencies:
6103 - '@vueuse/core': 13.9.0(vue@3.5.28(typescript@5.9.3))
6104 - '@vueuse/shared': 13.9.0(vue@3.5.28(typescript@5.9.3))
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))
6461 defu: 6.1.4
6462 framesync: 6.1.2
6463 popmotion: 11.0.5
6464 style-value-types: 5.1.2
6109 - vue: 3.5.28(typescript@5.9.3)
6465 + vue: 3.5.29(typescript@5.9.3)
6466 optionalDependencies:
6467 '@nuxt/kit': 3.21.1
6468 transitivePeerDependencies:
6469 - magicast
6470
6115 - '@vueuse/shared@13.9.0(vue@3.5.28(typescript@5.9.3))':
6471 + '@vueuse/shared@13.9.0(vue@3.5.29(typescript@5.9.3))':
6472 dependencies:
6117 - vue: 3.5.28(typescript@5.9.3)
6473 + vue: 3.5.29(typescript@5.9.3)
6474
6119 - '@vueuse/shared@14.2.1(vue@3.5.28(typescript@5.9.3))':
6475 + '@vueuse/shared@14.2.1(vue@3.5.29(typescript@5.9.3))':
6476 dependencies:
6121 - vue: 3.5.28(typescript@5.9.3)
6477 + vue: 3.5.29(typescript@5.9.3)
6478
6479 '@yr/monotone-cubic-spline@1.0.3': {}
6480
6481 abbrev@2.0.0: {}
6482
6127 - acorn-jsx@5.3.2(acorn@8.15.0):
6483 + acorn-jsx@5.3.2(acorn@8.16.0):
6484 dependencies:
6129 - acorn: 8.15.0
6485 + acorn: 8.16.0
6486 +
6487 + acorn@7.4.1: {}
6488
6489 acorn@8.15.0: {}
6490
6491 + acorn@8.16.0: {}
6492 +
6493 agent-base@7.1.4: {}
6494
6135 - ajv@6.12.6:
6495 + ajv@6.14.0:
6496 dependencies:
6497 fast-deep-equal: 3.1.3
6498 fast-json-stable-stringify: 2.1.0
@@ -6190,6 +6550,10 @@ snapshots:
6550
6551 arrify@2.0.1: {}
6552
6553 + asap@2.0.6: {}
6554 +
6555 + assert-never@1.4.0: {}
6556 +
6557 assertion-error@2.0.1: {}
6558
6559 ast-kit@2.2.0:
@@ -6206,7 +6570,7 @@ snapshots:
6570
6571 asynckit@0.4.0: {}
6572
6209 - axios@1.13.5(debug@4.4.3):
6573 + axios@1.13.6(debug@4.4.3):
6574 dependencies:
6575 follow-redirects: 1.15.11(debug@4.4.3)
6576 form-data: 4.0.5
@@ -6214,13 +6578,19 @@ snapshots:
6578 transitivePeerDependencies:
6579 - debug
6580
6581 + babel-walk@3.0.0-canary-5:
6582 + dependencies:
6583 + '@babel/types': 7.29.0
6584 +
6585 + badgen@3.2.3: {}
6586 +
6587 balanced-match@1.0.2: {}
6588
6589 balanced-match@4.0.2:
6590 dependencies:
6591 jackspeak: 4.2.3
6592
6223 - baseline-browser-mapping@2.9.19: {}
6593 + baseline-browser-mapping@2.10.0: {}
6594
6595 bidi-js@1.0.3:
6596 dependencies:
@@ -6228,6 +6598,11 @@ snapshots:
6598
6599 birpc@2.9.0: {}
6600
6601 + blamer@1.0.7:
6602 + dependencies:
6603 + execa: 4.1.0
6604 + which: 2.0.2
6605 +
6606 bluebird@3.7.2: {}
6607
6608 boolbase@1.0.0: {}
@@ -6251,7 +6626,7 @@ snapshots:
6626
6627 browserslist@4.28.1:
6628 dependencies:
6254 - baseline-browser-mapping: 2.9.19
6629 + baseline-browser-mapping: 2.10.0
6630 caniuse-lite: 1.0.30001770
6631 electron-to-chromium: 1.5.286
6632 node-releases: 2.0.27
@@ -6283,11 +6658,18 @@ snapshots:
6658
6659 cac@6.7.14: {}
6660
6661 + cac@7.0.0: {}
6662 +
6663 call-bind-apply-helpers@1.0.2:
6664 dependencies:
6665 es-errors: 1.3.0
6666 function-bind: 1.1.2
6667
6668 + call-bound@1.0.4:
6669 + dependencies:
6670 + call-bind-apply-helpers: 1.0.2
6671 + get-intrinsic: 1.3.0
6672 +
6673 callsite@1.0.0: {}
6674
6675 callsites@3.1.0: {}
@@ -6308,6 +6690,10 @@ snapshots:
6690
6691 character-entities@2.0.2: {}
6692
6693 + character-parser@2.2.0:
6694 + dependencies:
6695 + is-regex: 1.2.1
6696 +
6697 check-more-types@2.24.0: {}
6698
6699 chokidar@4.0.3:
@@ -6334,6 +6720,12 @@ snapshots:
6720 dependencies:
6721 escape-string-regexp: 1.0.5
6722
6723 + cli-table3@0.6.5:
6724 + dependencies:
6725 + string-width: 4.2.3
6726 + optionalDependencies:
6727 + '@colors/colors': 1.5.0
6728 +
6729 cliui@7.0.4:
6730 dependencies:
6731 string-width: 4.2.3
@@ -6351,10 +6743,10 @@ snapshots:
6743 '@codemirror/autocomplete': 6.20.0
6744 '@codemirror/commands': 6.10.2
6745 '@codemirror/language': 6.12.1
6354 - '@codemirror/lint': 6.9.4
6746 + '@codemirror/lint': 6.9.5
6747 '@codemirror/search': 6.6.0
6748 '@codemirror/state': 6.5.4
6357 - '@codemirror/view': 6.39.14
6749 + '@codemirror/view': 6.39.16
6750
6751 color-convert@2.0.1:
6752 dependencies:
@@ -6364,6 +6756,8 @@ snapshots:
6756
6757 colord@2.9.3: {}
6758
6759 + colors@1.4.0: {}
6760 +
6761 combined-stream@1.0.8:
6762 dependencies:
6763 delayed-stream: 1.0.0
@@ -6372,11 +6766,9 @@ snapshots:
6766
6767 commander@10.0.1: {}
6768
6375 - commander@7.2.0: {}
6376 -
6377 - commander@8.3.0: {}
6769 + commander@5.1.0: {}
6770
6379 - comment-parser@1.4.1: {}
6771 + commander@7.2.0: {}
6772
6773 comment-parser@1.4.5: {}
6774
@@ -6394,6 +6786,11 @@ snapshots:
6786 consola@3.4.2:
6787 optional: true
6788
6789 + constantinople@4.0.1:
6790 + dependencies:
6791 + '@babel/parser': 7.29.0
6792 + '@babel/types': 7.29.0
6793 +
6794 convert-source-map@2.0.0: {}
6795
6796 copy-anything@4.0.5:
@@ -6557,10 +6954,10 @@ snapshots:
6954 dependencies:
6955 dequal: 2.0.3
6956
6560 - diff-sequences@27.5.1: {}
6561 -
6957 diff-sequences@29.6.3: {}
6958
6959 + doctypes@1.1.0: {}
6960 +
6961 dom-serializer@2.0.0:
6962 dependencies:
6963 domelementtype: 2.3.0
@@ -6608,14 +7005,16 @@ snapshots:
7005
7006 electron-to-chromium@1.5.286: {}
7007
6611 - emoji-regex-xs@2.0.1: {}
6612 -
7008 emoji-regex@8.0.0: {}
7009
7010 emoji-regex@9.2.2: {}
7011
7012 empathic@2.0.0: {}
7013
7014 + end-of-stream@1.4.5:
7015 + dependencies:
7016 + once: 1.4.0
7017 +
7018 enhanced-resolve@5.19.0:
7019 dependencies:
7020 graceful-fs: 4.2.11
@@ -6690,57 +7089,62 @@ snapshots:
7089
7090 escape-string-regexp@5.0.0: {}
7091
6693 - eslint-compat-utils@0.5.1(eslint@10.0.0(jiti@2.6.1)):
6694 - dependencies:
6695 - eslint: 10.0.0(jiti@2.6.1)
6696 - semver: 7.7.4
6697 -
6698 - eslint-compat-utils@0.6.5(eslint@10.0.0(jiti@2.6.1)):
7092 + eslint-compat-utils@0.5.1(eslint@10.0.2(jiti@2.6.1)):
7093 dependencies:
6700 - eslint: 10.0.0(jiti@2.6.1)
7094 + eslint: 10.0.2(jiti@2.6.1)
7095 semver: 7.7.4
7096
6703 - eslint-config-flat-gitignore@2.1.0(eslint@10.0.0(jiti@2.6.1)):
7097 + eslint-config-flat-gitignore@2.2.1(eslint@10.0.2(jiti@2.6.1)):
7098 dependencies:
6705 - '@eslint/compat': 1.4.1(eslint@10.0.0(jiti@2.6.1))
6706 - eslint: 10.0.0(jiti@2.6.1)
7099 + '@eslint/compat': 2.0.2(eslint@10.0.2(jiti@2.6.1))
7100 + eslint: 10.0.2(jiti@2.6.1)
7101
7102 eslint-flat-config-utils@3.0.1:
7103 dependencies:
7104 '@eslint/config-helpers': 0.5.2
7105 pathe: 2.0.3
7106
6713 - eslint-json-compat-utils@0.2.1(eslint@10.0.0(jiti@2.6.1))(jsonc-eslint-parser@2.4.2):
7107 + eslint-json-compat-utils@0.2.1(eslint@10.0.2(jiti@2.6.1))(jsonc-eslint-parser@3.1.0):
7108 dependencies:
6715 - eslint: 10.0.0(jiti@2.6.1)
7109 + eslint: 10.0.2(jiti@2.6.1)
7110 esquery: 1.7.0
6717 - jsonc-eslint-parser: 2.4.2
7111 + jsonc-eslint-parser: 3.1.0
7112
6719 - eslint-merge-processors@2.0.0(eslint@10.0.0(jiti@2.6.1)):
7113 + eslint-merge-processors@2.0.0(eslint@10.0.2(jiti@2.6.1)):
7114 dependencies:
6721 - eslint: 10.0.0(jiti@2.6.1)
7115 + eslint: 10.0.2(jiti@2.6.1)
7116
6723 - eslint-plugin-antfu@3.2.2(eslint@10.0.0(jiti@2.6.1)):
7117 + eslint-plugin-antfu@3.2.2(eslint@10.0.2(jiti@2.6.1)):
7118 dependencies:
6725 - eslint: 10.0.0(jiti@2.6.1)
7119 + eslint: 10.0.2(jiti@2.6.1)
7120
6727 - eslint-plugin-command@3.4.0(eslint@10.0.0(jiti@2.6.1)):
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)):
7122 dependencies:
6729 - '@es-joy/jsdoccomment': 0.78.0
6730 - eslint: 10.0.0(jiti@2.6.1)
7123 + '@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)
7128
6732 - eslint-plugin-es-x@7.8.0(eslint@10.0.0(jiti@2.6.1)):
7129 + eslint-plugin-depend@1.5.0(eslint@10.0.2(jiti@2.6.1)):
7130 dependencies:
6734 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
7131 + empathic: 2.0.0
7132 + eslint: 10.0.2(jiti@2.6.1)
7133 + module-replacements: 2.11.0
7134 + semver: 7.7.4
7135 +
7136 + eslint-plugin-es-x@7.8.0(eslint@10.0.2(jiti@2.6.1)):
7137 + dependencies:
7138 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7139 '@eslint-community/regexpp': 4.12.2
6736 - eslint: 10.0.0(jiti@2.6.1)
6737 - eslint-compat-utils: 0.5.1(eslint@10.0.0(jiti@2.6.1))
7140 + eslint: 10.0.2(jiti@2.6.1)
7141 + eslint-compat-utils: 0.5.1(eslint@10.0.2(jiti@2.6.1))
7142
6739 - eslint-plugin-import-lite@0.5.1(eslint@10.0.0(jiti@2.6.1)):
7143 + eslint-plugin-import-lite@0.5.2(eslint@10.0.2(jiti@2.6.1)):
7144 dependencies:
6741 - eslint: 10.0.0(jiti@2.6.1)
7145 + eslint: 10.0.2(jiti@2.6.1)
7146
6743 - eslint-plugin-jsdoc@62.5.5(eslint@10.0.0(jiti@2.6.1)):
7147 + eslint-plugin-jsdoc@62.7.1(eslint@10.0.2(jiti@2.6.1)):
7148 dependencies:
7149 '@es-joy/jsdoccomment': 0.84.0
7150 '@es-joy/resolve.exports': 1.2.0
@@ -6748,8 +7152,8 @@ snapshots:
7152 comment-parser: 1.4.5
7153 debug: 4.4.3
7154 escape-string-regexp: 4.0.0
6751 - eslint: 10.0.0(jiti@2.6.1)
6752 - espree: 11.1.0
7155 + eslint: 10.0.2(jiti@2.6.1)
7156 + espree: 11.1.1
7157 esquery: 1.7.0
7158 html-entities: 2.6.0
7159 object-deep-merge: 2.0.0
@@ -6760,47 +7164,27 @@ snapshots:
7164 transitivePeerDependencies:
7165 - supports-color
7166
6763 - eslint-plugin-jsonc@2.21.1(eslint@10.0.0(jiti@2.6.1)):
7167 + eslint-plugin-jsonc@3.1.1(eslint@10.0.2(jiti@2.6.1)):
7168 dependencies:
6765 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
6766 - diff-sequences: 27.5.1
6767 - eslint: 10.0.0(jiti@2.6.1)
6768 - eslint-compat-utils: 0.6.5(eslint@10.0.0(jiti@2.6.1))
6769 - eslint-json-compat-utils: 0.2.1(eslint@10.0.0(jiti@2.6.1))(jsonc-eslint-parser@2.4.2)
6770 - espree: 10.4.0
6771 - graphemer: 1.4.0
6772 - jsonc-eslint-parser: 2.4.2
7169 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7170 + '@eslint/core': 1.1.0
7171 + '@eslint/plugin-kit': 0.6.0
7172 + '@ota-meshi/ast-token-store': 0.3.0
7173 + 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)
7176 + jsonc-eslint-parser: 3.1.0
7177 natural-compare: 1.4.0
7178 synckit: 0.11.12
7179 transitivePeerDependencies:
7180 - '@eslint/json'
7181
6778 - eslint-plugin-markdown-preferences@0.40.2(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1)):
7182 + eslint-plugin-n@17.24.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3):
7183 dependencies:
6780 - '@eslint/markdown': 7.5.1
6781 - diff-sequences: 29.6.3
6782 - emoji-regex-xs: 2.0.1
6783 - eslint: 10.0.0(jiti@2.6.1)
6784 - mdast-util-from-markdown: 2.0.2
6785 - mdast-util-frontmatter: 2.0.1
6786 - mdast-util-gfm: 3.1.0
6787 - mdast-util-math: 3.0.0
6788 - micromark-extension-frontmatter: 2.0.0
6789 - micromark-extension-gfm: 3.0.0
6790 - micromark-extension-math: 3.1.0
6791 - micromark-factory-space: 2.0.1
6792 - micromark-util-character: 2.1.1
6793 - micromark-util-symbol: 2.0.1
6794 - string-width: 8.1.1
6795 - transitivePeerDependencies:
6796 - - supports-color
6797 -
6798 - eslint-plugin-n@17.24.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
6799 - dependencies:
6800 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
7184 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7185 enhanced-resolve: 5.19.0
6802 - eslint: 10.0.0(jiti@2.6.1)
6803 - eslint-plugin-es-x: 7.8.0(eslint@10.0.0(jiti@2.6.1))
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))
7188 get-tsconfig: 4.13.6
7189 globals: 15.15.0
7190 globrex: 0.1.2
@@ -6812,58 +7196,57 @@ snapshots:
7196
7197 eslint-plugin-no-only-tests@3.3.0: {}
7198
6815 - eslint-plugin-perfectionist@5.5.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
7199 + eslint-plugin-perfectionist@5.6.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3):
7200 dependencies:
6817 - '@typescript-eslint/utils': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
6818 - eslint: 10.0.0(jiti@2.6.1)
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)
7203 natural-orderby: 5.0.0
7204 transitivePeerDependencies:
7205 - supports-color
7206 - typescript
7207
6824 - eslint-plugin-pnpm@1.5.0(eslint@10.0.0(jiti@2.6.1)):
7208 + eslint-plugin-pnpm@1.6.0(eslint@10.0.2(jiti@2.6.1)):
7209 dependencies:
7210 empathic: 2.0.0
6827 - eslint: 10.0.0(jiti@2.6.1)
6828 - jsonc-eslint-parser: 2.4.2
7211 + eslint: 10.0.2(jiti@2.6.1)
7212 + jsonc-eslint-parser: 3.1.0
7213 pathe: 2.0.3
6830 - pnpm-workspace-yaml: 1.5.0
7214 + pnpm-workspace-yaml: 1.6.0
7215 tinyglobby: 0.2.15
7216 yaml: 2.8.2
7217 yaml-eslint-parser: 2.0.0
7218
6835 - eslint-plugin-regexp@3.0.0(eslint@10.0.0(jiti@2.6.1)):
7219 + eslint-plugin-regexp@3.0.0(eslint@10.0.2(jiti@2.6.1)):
7220 dependencies:
6837 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
7221 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7222 '@eslint-community/regexpp': 4.12.2
7223 comment-parser: 1.4.5
6840 - eslint: 10.0.0(jiti@2.6.1)
7224 + eslint: 10.0.2(jiti@2.6.1)
7225 jsdoc-type-pratt-parser: 7.1.1
7226 refa: 0.12.1
7227 regexp-ast-analysis: 0.7.1
7228 scslre: 0.3.0
7229
6846 - eslint-plugin-toml@1.1.0(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1)):
7230 + eslint-plugin-toml@1.3.1(eslint@10.0.2(jiti@2.6.1)):
7231 dependencies:
7232 '@eslint/core': 1.1.0
7233 '@eslint/plugin-kit': 0.6.0
6850 - '@ota-meshi/ast-token-store': 0.2.0(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1))
7234 + '@ota-meshi/ast-token-store': 0.3.0
7235 debug: 4.4.3
6852 - eslint: 10.0.0(jiti@2.6.1)
7236 + eslint: 10.0.2(jiti@2.6.1)
7237 toml-eslint-parser: 1.0.3
7238 transitivePeerDependencies:
6855 - - '@eslint/markdown'
7239 - supports-color
7240
6858 - eslint-plugin-unicorn@63.0.0(eslint@10.0.0(jiti@2.6.1)):
7241 + eslint-plugin-unicorn@63.0.0(eslint@10.0.2(jiti@2.6.1)):
7242 dependencies:
7243 '@babel/helper-validator-identifier': 7.28.5
6861 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
7244 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7245 change-case: 5.4.4
7246 ci-info: 4.4.0
7247 clean-regexp: 1.0.0
7248 core-js-compat: 3.48.0
6866 - eslint: 10.0.0(jiti@2.6.1)
7249 + eslint: 10.0.2(jiti@2.6.1)
7250 find-up-simple: 1.0.1
7251 globals: 16.5.0
7252 indent-string: 5.0.0
@@ -6875,47 +7258,46 @@ snapshots:
7258 semver: 7.7.4
7259 strip-indent: 4.1.1
7260
6878 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1)):
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)):
7262 dependencies:
6880 - eslint: 10.0.0(jiti@2.6.1)
7263 + eslint: 10.0.2(jiti@2.6.1)
7264 optionalDependencies:
6882 - '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
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)
7266
6884 - eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.8.0(eslint@10.0.0(jiti@2.6.1)))(@typescript-eslint/parser@8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.0.0(jiti@2.6.1))):
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))):
7268 dependencies:
6886 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
6887 - eslint: 10.0.0(jiti@2.6.1)
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)
7271 natural-compare: 1.4.0
7272 nth-check: 2.1.1
7273 postcss-selector-parser: 7.1.1
7274 semver: 7.7.4
6892 - vue-eslint-parser: 10.4.0(eslint@10.0.0(jiti@2.6.1))
7275 + vue-eslint-parser: 10.4.0(eslint@10.0.2(jiti@2.6.1))
7276 xml-name-validator: 4.0.0
7277 optionalDependencies:
6895 - '@stylistic/eslint-plugin': 5.8.0(eslint@10.0.0(jiti@2.6.1))
6896 - '@typescript-eslint/parser': 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
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)
7280
6898 - eslint-plugin-yml@3.2.0(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1)):
7281 + eslint-plugin-yml@3.3.1(eslint@10.0.2(jiti@2.6.1)):
7282 dependencies:
7283 '@eslint/core': 1.1.0
7284 '@eslint/plugin-kit': 0.6.0
6902 - '@ota-meshi/ast-token-store': 0.2.0(@eslint/markdown@7.5.1)(eslint@10.0.0(jiti@2.6.1))
7285 + '@ota-meshi/ast-token-store': 0.3.0
7286 debug: 4.4.3
7287 diff-sequences: 29.6.3
7288 escape-string-regexp: 5.0.0
6906 - eslint: 10.0.0(jiti@2.6.1)
7289 + eslint: 10.0.2(jiti@2.6.1)
7290 natural-compare: 1.4.0
7291 yaml-eslint-parser: 2.0.0
7292 transitivePeerDependencies:
6910 - - '@eslint/markdown'
7293 - supports-color
7294
6913 - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.28)(eslint@10.0.0(jiti@2.6.1)):
7295 + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.29)(eslint@10.0.2(jiti@2.6.1)):
7296 dependencies:
6915 - '@vue/compiler-sfc': 3.5.28
6916 - eslint: 10.0.0(jiti@2.6.1)
7297 + '@vue/compiler-sfc': 3.5.29
7298 + eslint: 10.0.2(jiti@2.6.1)
7299
6918 - eslint-scope@9.1.0:
7300 + eslint-scope@9.1.1:
7301 dependencies:
7302 '@types/esrecurse': 4.3.1
7303 '@types/estree': 1.0.8
@@ -6926,13 +7308,13 @@ snapshots:
7308
7309 eslint-visitor-keys@4.2.1: {}
7310
6929 - eslint-visitor-keys@5.0.0: {}
7311 + eslint-visitor-keys@5.0.1: {}
7312
6931 - eslint@10.0.0(jiti@2.6.1):
7313 + eslint@10.0.2(jiti@2.6.1):
7314 dependencies:
6933 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
7315 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
7316 '@eslint-community/regexpp': 4.12.2
6935 - '@eslint/config-array': 0.23.1
7317 + '@eslint/config-array': 0.23.2
7318 '@eslint/config-helpers': 0.5.2
7319 '@eslint/core': 1.1.0
7320 '@eslint/plugin-kit': 0.6.0
@@ -6940,13 +7322,13 @@ snapshots:
7322 '@humanwhocodes/module-importer': 1.0.1
7323 '@humanwhocodes/retry': 0.4.3
7324 '@types/estree': 1.0.8
6943 - ajv: 6.12.6
7325 + ajv: 6.14.0
7326 cross-spawn: 7.0.6
7327 debug: 4.4.3
7328 escape-string-regexp: 4.0.0
6947 - eslint-scope: 9.1.0
6948 - eslint-visitor-keys: 5.0.0
6949 - espree: 11.1.0
7329 + eslint-scope: 9.1.1
7330 + eslint-visitor-keys: 5.0.1
7331 + espree: 11.1.1
7332 esquery: 1.7.0
7333 esutils: 2.0.3
7334 fast-deep-equal: 3.1.3
@@ -6967,21 +7349,15 @@ snapshots:
7349
7350 espree@10.4.0:
7351 dependencies:
6970 - acorn: 8.15.0
6971 - acorn-jsx: 5.3.2(acorn@8.15.0)
7352 + acorn: 8.16.0
7353 + acorn-jsx: 5.3.2(acorn@8.16.0)
7354 eslint-visitor-keys: 4.2.1
7355
6974 - espree@11.1.0:
7356 + espree@11.1.1:
7357 dependencies:
6976 - acorn: 8.15.0
6977 - acorn-jsx: 5.3.2(acorn@8.15.0)
6978 - eslint-visitor-keys: 5.0.0
6979 -
6980 - espree@9.6.1:
6981 - dependencies:
6982 - acorn: 8.15.0
6983 - acorn-jsx: 5.3.2(acorn@8.15.0)
6984 - eslint-visitor-keys: 3.4.3
7358 + acorn: 8.16.0
7359 + acorn-jsx: 5.3.2(acorn@8.16.0)
7360 + eslint-visitor-keys: 5.0.1
7361
7362 esprima@4.0.1: {}
7363
@@ -7013,8 +7389,22 @@ snapshots:
7389 stream-combiner: 0.0.4
7390 through: 2.3.8
7391
7392 + eventemitter3@5.0.4: {}
7393 +
7394 evtd@0.2.4: {}
7395
7396 + execa@4.1.0:
7397 + dependencies:
7398 + cross-spawn: 7.0.6
7399 + get-stream: 5.2.0
7400 + human-signals: 1.1.1
7401 + is-stream: 2.0.1
7402 + merge-stream: 2.0.0
7403 + npm-run-path: 4.0.1
7404 + onetime: 5.1.2
7405 + signal-exit: 3.0.7
7406 + strip-final-newline: 2.0.0
7407 +
7408 execa@5.1.1:
7409 dependencies:
7410 cross-spawn: 7.0.6
@@ -7037,14 +7427,29 @@ snapshots:
7427
7428 fast-deep-equal@3.1.3: {}
7429
7430 + fast-glob@3.3.3:
7431 + dependencies:
7432 + '@nodelib/fs.stat': 2.0.5
7433 + '@nodelib/fs.walk': 1.2.8
7434 + glob-parent: 5.1.2
7435 + merge2: 1.4.1
7436 + micromatch: 4.0.8
7437 +
7438 fast-json-stable-stringify@2.1.0: {}
7439
7440 fast-levenshtein@2.0.6: {}
7441
7044 - fast-xml-parser@5.3.6:
7442 + fast-xml-builder@1.0.0: {}
7443 +
7444 + fast-xml-parser@5.4.2:
7445 dependencies:
7446 + fast-xml-builder: 1.0.0
7447 strnum: 2.1.2
7448
7449 + fastq@1.20.1:
7450 + dependencies:
7451 + reusify: 1.1.0
7452 +
7453 fault@2.0.1:
7454 dependencies:
7455 format: 0.2.2
@@ -7111,7 +7516,7 @@ snapshots:
7516
7517 from@0.1.7: {}
7518
7114 - fs-extra@11.3.3:
7519 + fs-extra@11.3.4:
7520 dependencies:
7521 graceful-fs: 4.2.11
7522 jsonfile: 6.2.0
@@ -7128,8 +7533,6 @@ snapshots:
7533
7534 get-caller-file@2.0.5: {}
7535
7131 - get-east-asian-width@1.4.0: {}
7132 -
7536 get-intrinsic@1.3.0:
7537 dependencies:
7538 call-bind-apply-helpers: 1.0.2
@@ -7148,6 +7551,10 @@ snapshots:
7551 dunder-proto: 1.0.1
7552 es-object-atoms: 1.1.1
7553
7554 + get-stream@5.2.0:
7555 + dependencies:
7556 + pump: 3.0.4
7557 +
7558 get-stream@6.0.1: {}
7559
7560 get-tsconfig@4.13.6:
@@ -7166,6 +7573,12 @@ snapshots:
7573
7574 github-slugger@2.0.0: {}
7575
7576 + gitignore-to-glob@0.3.0: {}
7577 +
7578 + glob-parent@5.1.2:
7579 + dependencies:
7580 + is-glob: 4.0.3
7581 +
7582 glob-parent@6.0.2:
7583 dependencies:
7584 is-glob: 4.0.3
@@ -7197,7 +7610,7 @@ snapshots:
7610
7611 globals@16.5.0: {}
7612
7200 - globals@17.3.0: {}
7613 + globals@17.4.0: {}
7614
7615 globrex@0.1.2: {}
7616
@@ -7205,8 +7618,6 @@ snapshots:
7618
7619 graceful-fs@4.2.11: {}
7620
7208 - graphemer@1.4.0: {}
7209 -
7621 has-symbols@1.1.0: {}
7622
7623 has-tostringtag@1.0.2:
@@ -7271,6 +7682,8 @@ snapshots:
7682 transitivePeerDependencies:
7683 - supports-color
7684
7685 + human-signals@1.1.1: {}
7686 +
7687 human-signals@2.1.0: {}
7688
7689 ignore@5.3.2: {}
@@ -7313,6 +7726,11 @@ snapshots:
7726
7727 is-docker@3.0.0: {}
7728
7729 + is-expression@4.0.0:
7730 + dependencies:
7731 + acorn: 7.4.1
7732 + object-assign: 4.1.1
7733 +
7734 is-extglob@2.1.1: {}
7735
7736 is-fullwidth-code-point@3.0.0: {}
@@ -7329,6 +7747,15 @@ snapshots:
7747
7748 is-potential-custom-element-name@1.0.1: {}
7749
7750 + is-promise@2.2.2: {}
7751 +
7752 + is-regex@1.2.1:
7753 + dependencies:
7754 + call-bound: 1.0.4
7755 + gopd: 1.2.0
7756 + has-tostringtag: 1.0.2
7757 + hasown: 2.0.2
7758 +
7759 is-stream@2.0.1: {}
7760
7761 is-what@5.5.0: {}
@@ -7369,7 +7796,7 @@ snapshots:
7796 '@hapi/topo': 6.0.2
7797 '@standard-schema/spec': 1.1.0
7798
7372 - jose@6.1.3: {}
7799 + jose@6.2.0: {}
7800
7801 js-beautify@1.15.4:
7802 dependencies:
@@ -7383,6 +7810,8 @@ snapshots:
7810
7811 js-md5@0.8.3: {}
7812
7813 + js-stringify@1.0.2: {}
7814 +
7815 js-tokens@4.0.0: {}
7816
7817 js-yaml@3.14.2:
@@ -7390,7 +7819,24 @@ snapshots:
7819 argparse: 1.0.10
7820 esprima: 4.0.1
7821
7393 - jsdoc-type-pratt-parser@7.0.0: {}
7822 + jscpd-sarif-reporter@4.0.6:
7823 + dependencies:
7824 + colors: 1.4.0
7825 + fs-extra: 11.3.4
7826 + node-sarif-builder: 3.4.0
7827 +
7828 + jscpd@4.0.8:
7829 + dependencies:
7830 + '@jscpd/badge-reporter': 4.0.4
7831 + '@jscpd/core': 4.0.4
7832 + '@jscpd/finder': 4.0.4
7833 + '@jscpd/html-reporter': 4.0.4
7834 + '@jscpd/tokenizer': 4.0.4
7835 + colors: 1.4.0
7836 + commander: 5.1.0
7837 + fs-extra: 11.3.4
7838 + gitignore-to-glob: 0.3.0
7839 + jscpd-sarif-reporter: 4.0.6
7840
7841 jsdoc-type-pratt-parser@7.1.1: {}
7842
@@ -7435,11 +7881,10 @@ snapshots:
7881
7882 json5@2.2.3: {}
7883
7438 - jsonc-eslint-parser@2.4.2:
7884 + jsonc-eslint-parser@3.1.0:
7885 dependencies:
7440 - acorn: 8.15.0
7441 - eslint-visitor-keys: 3.4.3
7442 - espree: 9.6.1
7886 + acorn: 8.16.0
7887 + eslint-visitor-keys: 5.0.1
7888 semver: 7.7.4
7889
7890 jsonfile@6.2.0:
@@ -7448,9 +7893,10 @@ snapshots:
7893 optionalDependencies:
7894 graceful-fs: 4.2.11
7895
7451 - katex@0.16.28:
7896 + jstransformer@1.0.0:
7897 dependencies:
7453 - commander: 8.3.0
7898 + is-promise: 2.2.2
7899 + promise: 7.3.1
7900
7901 keyv@4.5.4:
7902 dependencies:
@@ -7471,54 +7917,54 @@ snapshots:
7917 prelude-ls: 1.2.1
7918 type-check: 0.4.0
7919
7474 - lightningcss-android-arm64@1.30.2:
7920 + lightningcss-android-arm64@1.31.1:
7921 optional: true
7922
7477 - lightningcss-darwin-arm64@1.30.2:
7923 + lightningcss-darwin-arm64@1.31.1:
7924 optional: true
7925
7480 - lightningcss-darwin-x64@1.30.2:
7926 + lightningcss-darwin-x64@1.31.1:
7927 optional: true
7928
7483 - lightningcss-freebsd-x64@1.30.2:
7929 + lightningcss-freebsd-x64@1.31.1:
7930 optional: true
7931
7486 - lightningcss-linux-arm-gnueabihf@1.30.2:
7932 + lightningcss-linux-arm-gnueabihf@1.31.1:
7933 optional: true
7934
7489 - lightningcss-linux-arm64-gnu@1.30.2:
7935 + lightningcss-linux-arm64-gnu@1.31.1:
7936 optional: true
7937
7492 - lightningcss-linux-arm64-musl@1.30.2:
7938 + lightningcss-linux-arm64-musl@1.31.1:
7939 optional: true
7940
7495 - lightningcss-linux-x64-gnu@1.30.2:
7941 + lightningcss-linux-x64-gnu@1.31.1:
7942 optional: true
7943
7498 - lightningcss-linux-x64-musl@1.30.2:
7944 + lightningcss-linux-x64-musl@1.31.1:
7945 optional: true
7946
7501 - lightningcss-win32-arm64-msvc@1.30.2:
7947 + lightningcss-win32-arm64-msvc@1.31.1:
7948 optional: true
7949
7504 - lightningcss-win32-x64-msvc@1.30.2:
7950 + lightningcss-win32-x64-msvc@1.31.1:
7951 optional: true
7952
7507 - lightningcss@1.30.2:
7953 + lightningcss@1.31.1:
7954 dependencies:
7955 detect-libc: 2.1.2
7956 optionalDependencies:
7511 - lightningcss-android-arm64: 1.30.2
7512 - lightningcss-darwin-arm64: 1.30.2
7513 - lightningcss-darwin-x64: 1.30.2
7514 - lightningcss-freebsd-x64: 1.30.2
7515 - lightningcss-linux-arm-gnueabihf: 1.30.2
7516 - lightningcss-linux-arm64-gnu: 1.30.2
7517 - lightningcss-linux-arm64-musl: 1.30.2
7518 - lightningcss-linux-x64-gnu: 1.30.2
7519 - lightningcss-linux-x64-musl: 1.30.2
7520 - lightningcss-win32-arm64-msvc: 1.30.2
7521 - lightningcss-win32-x64-msvc: 1.30.2
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
7968
7969 lines-and-columns@1.2.4: {}
7970
@@ -7538,6 +7984,8 @@ snapshots:
7984
7985 lodash-es@4.17.23: {}
7986
7987 + lodash.merge@4.6.2: {}
7988 +
7989 lodash@4.17.21: {}
7990
7991 lodash@4.17.23: {}
@@ -7573,6 +8021,10 @@ snapshots:
8021 punycode.js: 2.3.1
8022 uc.micro: 2.1.0
8023
8024 + markdown-table@2.0.0:
8025 + dependencies:
8026 + repeat-string: 1.6.1
8027 +
8028 markdown-table@3.0.4: {}
8029
8030 math-intrinsics@1.1.0: {}
@@ -7669,18 +8121,6 @@ snapshots:
8121 transitivePeerDependencies:
8122 - supports-color
8123
7672 - mdast-util-math@3.0.0:
7673 - dependencies:
7674 - '@types/hast': 3.0.4
7675 - '@types/mdast': 4.0.4
7676 - devlop: 1.1.0
7677 - longest-streak: 3.1.0
7678 - mdast-util-from-markdown: 2.0.2
7679 - mdast-util-to-markdown: 2.1.2
7680 - unist-util-remove-position: 5.0.0
7681 - transitivePeerDependencies:
7682 - - supports-color
7683 -
8124 mdast-util-phrasing@4.1.0:
8125 dependencies:
8126 '@types/mdast': 4.0.4
@@ -7726,6 +8166,8 @@ snapshots:
8166
8167 merge-stream@2.0.0: {}
8168
8169 + merge2@1.4.1: {}
8170 +
8171 micromark-core-commonmark@2.0.3:
8172 dependencies:
8173 decode-named-character-reference: 1.3.0
@@ -7810,16 +8252,6 @@ snapshots:
8252 micromark-util-combine-extensions: 2.0.1
8253 micromark-util-types: 2.0.2
8254
7813 - micromark-extension-math@3.1.0:
7814 - dependencies:
7815 - '@types/katex': 0.16.8
7816 - devlop: 1.1.0
7817 - katex: 0.16.28
7818 - micromark-factory-space: 2.0.1
7819 - micromark-util-character: 2.1.1
7820 - micromark-util-symbol: 2.0.1
7821 - micromark-util-types: 2.0.2
7822 -
8255 micromark-factory-destination@2.0.1:
8256 dependencies:
8257 micromark-util-character: 2.1.1
@@ -7953,6 +8385,10 @@ snapshots:
8385 dependencies:
8386 brace-expansion: 5.0.2
8387
8388 + minimatch@10.2.4:
8389 + dependencies:
8390 + brace-expansion: 5.0.2
8391 +
8392 minimatch@3.1.2:
8393 dependencies:
8394 brace-expansion: 1.1.12
@@ -7982,6 +8418,8 @@ snapshots:
8418 pkg-types: 1.3.1
8419 ufo: 1.6.3
8420
8421 + module-replacements@2.11.0: {}
8422 +
8423 mrmime@2.0.1: {}
8424
8425 ms@2.1.3: {}
@@ -7996,12 +8434,12 @@ snapshots:
8434 arrify: 2.0.1
8435 minimatch: 3.1.2
8436
7999 - naive-ui@2.43.2(vue@3.5.28(typescript@5.9.3)):
8437 + naive-ui@2.43.2(vue@3.5.29(typescript@5.9.3)):
8438 dependencies:
8439 '@css-render/plugin-bem': 0.15.14(css-render@0.15.14)
8002 - '@css-render/vue3-ssr': 0.15.14(vue@3.5.28(typescript@5.9.3))
8440 + '@css-render/vue3-ssr': 0.15.14(vue@3.5.29(typescript@5.9.3))
8441 '@types/katex': 0.16.8
8004 - '@types/lodash': 4.17.23
8442 + '@types/lodash': 4.17.24
8443 '@types/lodash-es': 4.17.12
8444 async-validator: 4.2.5
8445 css-render: 0.15.14
@@ -8014,10 +8452,10 @@ snapshots:
8452 lodash-es: 4.17.23
8453 seemly: 0.3.10
8454 treemate: 0.3.11
8017 - vdirs: 0.1.8(vue@3.5.28(typescript@5.9.3))
8018 - vooks: 0.2.12(vue@3.5.28(typescript@5.9.3))
8019 - vue: 3.5.28(typescript@5.9.3)
8020 - vueuc: 0.4.65(vue@3.5.28(typescript@5.9.3))
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))
8459
8460 nanoid@3.3.11: {}
8461
@@ -8034,6 +8472,11 @@ snapshots:
8472
8473 node-releases@2.0.27: {}
8474
8475 + node-sarif-builder@3.4.0:
8476 + dependencies:
8477 + '@types/sarif': 2.1.7
8478 + fs-extra: 11.3.4
8479 +
8480 nopt@7.2.1:
8481 dependencies:
8482 abbrev: 2.0.0
@@ -8066,6 +8509,8 @@ snapshots:
8509 tinyexec: 1.0.2
8510 optional: true
8511
8512 + object-assign@4.1.1: {}
8513 +
8514 object-deep-merge@2.0.0: {}
8515
8516 obug@2.1.1: {}
@@ -8078,6 +8523,10 @@ snapshots:
8523
8524 ohash@2.0.11: {}
8525
8526 + once@1.4.0:
8527 + dependencies:
8528 + wrappy: 1.0.2
8529 +
8530 onetime@5.1.2:
8531 dependencies:
8532 mimic-fn: 2.1.0
@@ -8192,17 +8641,17 @@ snapshots:
8641
8642 pidtree@0.6.0: {}
8643
8195 - pinia-plugin-persistedstate@4.7.1(@nuxt/kit@3.21.1)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))):
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))):
8645 dependencies:
8646 defu: 6.1.4
8647 optionalDependencies:
8648 '@nuxt/kit': 3.21.1
8200 - pinia: 3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))
8649 + pinia: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
8650
8202 - pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)):
8651 + pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)):
8652 dependencies:
8653 '@vue/devtools-api': 7.7.9
8205 - vue: 3.5.28(typescript@5.9.3)
8654 + vue: 3.5.29(typescript@5.9.3)
8655 optionalDependencies:
8656 typescript: 5.9.3
8657
@@ -8224,7 +8673,7 @@ snapshots:
8673
8674 pluralize@8.0.0: {}
8675
8227 - pnpm-workspace-yaml@1.5.0:
8676 + pnpm-workspace-yaml@1.6.0:
8677 dependencies:
8678 yaml: 2.8.2
8679
@@ -8254,6 +8703,10 @@ snapshots:
8703
8704 prettier@3.8.1: {}
8705
8706 + promise@7.3.1:
8707 + dependencies:
8708 + asap: 2.0.6
8709 +
8710 property-information@7.1.0: {}
8711
8712 proto-list@1.2.4: {}
@@ -8264,6 +8717,78 @@ snapshots:
8717 dependencies:
8718 event-stream: 3.3.4
8719
8720 + pug-attrs@3.0.0:
8721 + dependencies:
8722 + constantinople: 4.0.1
8723 + js-stringify: 1.0.2
8724 + pug-runtime: 3.0.1
8725 +
8726 + pug-code-gen@3.0.3:
8727 + dependencies:
8728 + constantinople: 4.0.1
8729 + doctypes: 1.1.0
8730 + js-stringify: 1.0.2
8731 + pug-attrs: 3.0.0
8732 + pug-error: 2.1.0
8733 + pug-runtime: 3.0.1
8734 + void-elements: 3.1.0
8735 + with: 7.0.2
8736 +
8737 + pug-error@2.1.0: {}
8738 +
8739 + pug-filters@4.0.0:
8740 + dependencies:
8741 + constantinople: 4.0.1
8742 + jstransformer: 1.0.0
8743 + pug-error: 2.1.0
8744 + pug-walk: 2.0.0
8745 + resolve: 1.22.11
8746 +
8747 + pug-lexer@5.0.1:
8748 + dependencies:
8749 + character-parser: 2.2.0
8750 + is-expression: 4.0.0
8751 + pug-error: 2.1.0
8752 +
8753 + pug-linker@4.0.0:
8754 + dependencies:
8755 + pug-error: 2.1.0
8756 + pug-walk: 2.0.0
8757 +
8758 + pug-load@3.0.0:
8759 + dependencies:
8760 + object-assign: 4.1.1
8761 + pug-walk: 2.0.0
8762 +
8763 + pug-parser@6.0.0:
8764 + dependencies:
8765 + pug-error: 2.1.0
8766 + token-stream: 1.0.0
8767 +
8768 + pug-runtime@3.0.1: {}
8769 +
8770 + pug-strip-comments@2.0.0:
8771 + dependencies:
8772 + pug-error: 2.1.0
8773 +
8774 + pug-walk@2.0.0: {}
8775 +
8776 + pug@3.0.3:
8777 + dependencies:
8778 + pug-code-gen: 3.0.3
8779 + pug-filters: 4.0.0
8780 + pug-lexer: 5.0.1
8781 + pug-linker: 4.0.0
8782 + pug-load: 3.0.0
8783 + pug-parser: 6.0.0
8784 + pug-runtime: 3.0.1
8785 + pug-strip-comments: 2.0.0
8786 +
8787 + pump@3.0.4:
8788 + dependencies:
8789 + end-of-stream: 1.4.5
8790 + once: 1.4.0
8791 +
8792 punycode.js@2.3.1: {}
8793
8794 punycode@2.3.1: {}
@@ -8272,6 +8797,8 @@ snapshots:
8797
8798 quansync@1.0.0: {}
8799
8800 + queue-microtask@1.2.3: {}
8801 +
8802 rc9@2.1.2:
8803 dependencies:
8804 defu: 6.1.4
@@ -8322,6 +8849,10 @@ snapshots:
8849 dependencies:
8850 jsesc: 3.1.0
8851
8852 + repeat-string@1.6.1: {}
8853 +
8854 + reprism@0.0.11: {}
8855 +
8856 require-directory@2.1.1: {}
8857
8858 require-from-string@2.0.2: {}
@@ -8352,6 +8883,8 @@ snapshots:
8883 onetime: 7.0.0
8884 signal-exit: 4.1.0
8885
8886 + reusify@1.1.0: {}
8887 +
8888 rfdc@1.4.1: {}
8889
8890 rollup-plugin-visualizer@5.14.0(rollup@4.57.1):
@@ -8396,6 +8929,10 @@ snapshots:
8929
8930 run-applescript@7.1.0: {}
8931
8932 + run-parallel@1.2.0:
8933 + dependencies:
8934 + queue-microtask: 1.2.3
8935 +
8936 rxjs@7.8.2:
8937 dependencies:
8938 tslib: 2.8.1
@@ -8408,6 +8945,8 @@ snapshots:
8945 optionalDependencies:
8946 '@parcel/watcher': 2.5.6
8947
8948 + sax@1.5.0: {}
8949 +
8950 saxes@6.0.0:
8951 dependencies:
8952 xmlchars: 2.2.0
@@ -8441,14 +8980,14 @@ snapshots:
8980
8981 shell-quote@1.8.3: {}
8982
8444 - shiki@3.22.0:
8983 + shiki@4.0.1:
8984 dependencies:
8446 - '@shikijs/core': 3.22.0
8447 - '@shikijs/engine-javascript': 3.22.0
8448 - '@shikijs/engine-oniguruma': 3.22.0
8449 - '@shikijs/langs': 3.22.0
8450 - '@shikijs/themes': 3.22.0
8451 - '@shikijs/types': 3.22.0
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
8991 '@shikijs/vscode-textmate': 10.0.2
8992 '@types/hast': 3.0.4
8993
@@ -8474,6 +9013,8 @@ snapshots:
9013
9014 space-separated-tokens@2.0.2: {}
9015
9016 + spark-md5@3.0.2: {}
9017 +
9018 spdx-exceptions@2.5.0: {}
9019
9020 spdx-expression-parse@4.0.0:
@@ -8493,7 +9034,7 @@ snapshots:
9034
9035 stackback@0.0.2: {}
9036
8496 - start-server-and-test@2.1.3:
9037 + start-server-and-test@2.1.5:
9038 dependencies:
9039 arg: 5.0.2
9040 bluebird: 3.7.2
@@ -8502,7 +9043,7 @@ snapshots:
9043 execa: 5.1.1
9044 lazy-ass: 1.6.0
9045 ps-tree: 1.2.0
8505 - wait-on: 9.0.3(debug@4.4.3)
9046 + wait-on: 9.0.4(debug@4.4.3)
9047 transitivePeerDependencies:
9048 - supports-color
9049
@@ -8524,11 +9065,6 @@ snapshots:
9065 emoji-regex: 9.2.2
9066 strip-ansi: 7.1.2
9067
8527 - string-width@8.1.1:
8528 - dependencies:
8529 - get-east-asian-width: 1.4.0
8530 - strip-ansi: 7.1.2
8531 -
9068 stringify-entities@4.0.4:
9069 dependencies:
9070 character-entities-html4: 2.1.0
@@ -8561,15 +9097,15 @@ snapshots:
9097
9098 supports-preserve-symlinks-flag@1.0.0: {}
9099
8564 - svgo@3.3.2:
9100 + svgo@3.3.3:
9101 dependencies:
8566 - '@trysound/sax': 0.2.0
9102 commander: 7.2.0
9103 css-select: 5.2.2
9104 css-tree: 2.3.1
9105 css-what: 6.2.2
9106 csso: 5.0.5
9107 picocolors: 1.1.1
9108 + sax: 1.5.0
9109
9110 symbol-tree@3.2.4: {}
9111
@@ -8579,30 +9115,31 @@ snapshots:
9115
9116 tagged-tag@1.0.0: {}
9117
8582 - tailwindcss@4.1.18: {}
9118 + tailwindcss@4.2.1: {}
9119
9120 tapable@2.3.0: {}
9121
8586 - taze@19.9.2:
9122 + taze@19.10.0:
9123 dependencies:
8588 - '@antfu/ni': 27.0.1
8589 - cac: 6.7.14
9124 + '@antfu/ni': 28.2.0
9125 + '@henrygd/queue': 1.2.0
9126 + cac: 7.0.0
9127 find-up-simple: 1.0.1
9128 ofetch: 1.5.1
9129 package-manager-detector: 1.6.0
9130 pathe: 2.0.3
8594 - pnpm-workspace-yaml: 1.5.0
9131 + pnpm-workspace-yaml: 1.6.0
9132 restore-cursor: 5.1.0
9133 tinyexec: 1.0.2
9134 tinyglobby: 0.2.15
9135 unconfig: 7.5.0
9136 yaml: 2.8.2
9137
8601 - thememirror@2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.14):
9138 + thememirror@2.0.1(@codemirror/language@6.12.1)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16):
9139 dependencies:
9140 '@codemirror/language': 6.12.1
9141 '@codemirror/state': 6.5.4
8605 - '@codemirror/view': 6.39.14
9142 + '@codemirror/view': 6.39.16
9143
9144 through@2.3.8: {}
9145
@@ -8634,9 +9171,11 @@ snapshots:
9171 '@sindresorhus/base62': 1.0.0
9172 reserved-identifiers: 1.2.0
9173
9174 + token-stream@1.0.0: {}
9175 +
9176 toml-eslint-parser@1.0.3:
9177 dependencies:
8639 - eslint-visitor-keys: 5.0.0
9178 + eslint-visitor-keys: 5.0.1
9179
9180 totalist@3.0.1: {}
9181
@@ -8702,7 +9241,9 @@ snapshots:
9241 unplugin: 2.3.11
9242 optional: true
9243
8705 - undici-types@7.16.0: {}
9244 + undici-types@7.18.2: {}
9245 +
9246 + undici-types@7.22.0: {}
9247
9248 undici@7.22.0: {}
9249
@@ -8714,11 +9255,6 @@ snapshots:
9255 dependencies:
9256 '@types/unist': 3.0.3
9257
8717 - unist-util-remove-position@5.0.0:
8718 - dependencies:
8719 - '@types/unist': 3.0.3
8720 - unist-util-visit: 5.1.0
8721 -
9258 unist-util-stringify-position@4.0.0:
9259 dependencies:
9260 '@types/unist': 3.0.3
@@ -8778,10 +9314,10 @@ snapshots:
9314
9315 validator@13.15.26: {}
9316
8781 - vdirs@0.1.8(vue@3.5.28(typescript@5.9.3)):
9317 + vdirs@0.1.8(vue@3.5.29(typescript@5.9.3)):
9318 dependencies:
9319 evtd: 0.2.4
8784 - vue: 3.5.28(typescript@5.9.3)
9320 + vue: 3.5.29(typescript@5.9.3)
9321
9322 vfile-message@4.0.3:
9323 dependencies:
@@ -8804,17 +9340,17 @@ snapshots:
9340 - rollup
9341 - supports-color
9342
8807 - vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)):
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)):
9344 dependencies:
9345 birpc: 2.9.0
8810 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
8811 - vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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))
9348
8813 - vite-hot-client@2.1.0(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)):
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)):
9350 dependencies:
8815 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
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)
9352
8817 - vite-plugin-inspect@11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)):
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)):
9354 dependencies:
9355 ansis: 4.2.0
9356 debug: 4.4.3
@@ -8824,28 +9360,28 @@ snapshots:
9360 perfect-debounce: 2.1.0
9361 sirv: 3.0.2
9362 unplugin-utils: 0.3.1
8827 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
8828 - vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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))
9365 optionalDependencies:
9366 '@nuxt/kit': 3.21.1
9367 transitivePeerDependencies:
9368 - supports-color
9369
8834 - vite-plugin-vue-devtools@8.0.6(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)):
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)):
9371 dependencies:
8836 - '@vue/devtools-core': 8.0.6(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
8837 - '@vue/devtools-kit': 8.0.6
8838 - '@vue/devtools-shared': 8.0.6
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
9375 sirv: 3.0.2
8840 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
8841 - vite-plugin-inspect: 11.3.3(@nuxt/kit@3.21.1)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
8842 - vite-plugin-vue-inspector: 5.3.2(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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))
9379 transitivePeerDependencies:
9380 - '@nuxt/kit'
9381 - supports-color
9382 - vue
9383
8848 - vite-plugin-vue-inspector@5.3.2(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)):
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)):
9385 dependencies:
9386 '@babel/core': 7.29.0
9387 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0)
@@ -8856,16 +9392,19 @@ snapshots:
9392 '@vue/compiler-dom': 3.5.28
9393 kolorist: 1.8.0
9394 magic-string: 0.30.21
8859 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
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)
9396 transitivePeerDependencies:
9397 - supports-color
9398
8863 - vite-svg-loader@5.1.0(vue@3.5.28(typescript@5.9.3)):
9399 + vite-svg-loader@5.1.1(vue@3.5.29(typescript@5.9.3)):
9400 dependencies:
8865 - svgo: 3.3.2
8866 - vue: 3.5.28(typescript@5.9.3)
9401 + debug: 4.4.3
9402 + svgo: 3.3.3
9403 + vue: 3.5.29(typescript@5.9.3)
9404 + transitivePeerDependencies:
9405 + - supports-color
9406
8868 - vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2):
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):
9408 dependencies:
9409 esbuild: 0.27.3
9410 fdir: 6.5.0(picomatch@4.0.3)
@@ -8874,17 +9413,17 @@ snapshots:
9413 rollup: 4.57.1
9414 tinyglobby: 0.2.15
9415 optionalDependencies:
8877 - '@types/node': 25.2.3
9416 + '@types/node': 25.3.5
9417 fsevents: 2.3.3
9418 jiti: 2.6.1
8880 - lightningcss: 1.30.2
9419 + lightningcss: 1.31.1
9420 sass: 1.97.3
9421 yaml: 2.8.2
9422
8884 - vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2):
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):
9424 dependencies:
9425 '@vitest/expect': 4.0.18
8887 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2))
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
@@ -8901,10 +9440,10 @@ snapshots:
9440 tinyexec: 1.0.2
9441 tinyglobby: 0.2.15
9442 tinyrainbow: 3.0.3
8904 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(yaml@2.8.2)
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)
9444 why-is-node-running: 2.3.0
9445 optionalDependencies:
8907 - '@types/node': 25.2.3
9446 + '@types/node': 25.3.5
9447 jsdom: 28.1.0
9448 transitivePeerDependencies:
9449 - jiti
@@ -8919,59 +9458,61 @@ snapshots:
9458 - tsx
9459 - yaml
9460
8922 - vooks@0.2.12(vue@3.5.28(typescript@5.9.3)):
9461 + void-elements@3.1.0: {}
9462 +
9463 + vooks@0.2.12(vue@3.5.29(typescript@5.9.3)):
9464 dependencies:
9465 evtd: 0.2.4
8925 - vue: 3.5.28(typescript@5.9.3)
9466 + vue: 3.5.29(typescript@5.9.3)
9467
9468 vscode-uri@3.1.0: {}
9469
8929 - vue-advanced-cropper@2.8.9(vue@3.5.28(typescript@5.9.3)):
9470 + vue-advanced-cropper@2.8.9(vue@3.5.29(typescript@5.9.3)):
9471 dependencies:
9472 classnames: 2.5.1
9473 debounce: 1.2.1
9474 easy-bem: 1.1.1
8934 - vue: 3.5.28(typescript@5.9.3)
9475 + vue: 3.5.29(typescript@5.9.3)
9476
8936 - vue-codemirror@6.1.1(codemirror@6.0.2)(vue@3.5.28(typescript@5.9.3)):
9477 + vue-codemirror@6.1.1(codemirror@6.0.2)(vue@3.5.29(typescript@5.9.3)):
9478 dependencies:
9479 '@codemirror/commands': 6.10.2
9480 '@codemirror/language': 6.12.1
9481 '@codemirror/state': 6.5.4
8941 - '@codemirror/view': 6.39.14
9482 + '@codemirror/view': 6.39.16
9483 codemirror: 6.0.2
8943 - vue: 3.5.28(typescript@5.9.3)
9484 + vue: 3.5.29(typescript@5.9.3)
9485
9486 vue-component-type-helpers@2.2.12: {}
9487
8947 - vue-eslint-parser@10.4.0(eslint@10.0.0(jiti@2.6.1)):
9488 + vue-eslint-parser@10.4.0(eslint@10.0.2(jiti@2.6.1)):
9489 dependencies:
9490 debug: 4.4.3
8950 - eslint: 10.0.0(jiti@2.6.1)
8951 - eslint-scope: 9.1.0
8952 - eslint-visitor-keys: 5.0.0
8953 - espree: 11.1.0
9491 + eslint: 10.0.2(jiti@2.6.1)
9492 + eslint-scope: 9.1.1
9493 + eslint-visitor-keys: 5.0.1
9494 + espree: 11.1.1
9495 esquery: 1.7.0
9496 semver: 7.7.4
9497 transitivePeerDependencies:
9498 - supports-color
9499
8959 - vue-highlight-words@3.0.1(vue@3.5.28(typescript@5.9.3)):
9500 + vue-highlight-words@3.0.1(vue@3.5.29(typescript@5.9.3)):
9501 dependencies:
9502 highlight-words-core: 1.2.3
8962 - vue: 3.5.28(typescript@5.9.3)
9503 + vue: 3.5.29(typescript@5.9.3)
9504
8964 - vue-i18n@11.2.8(vue@3.5.28(typescript@5.9.3)):
9505 + vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)):
9506 dependencies:
9507 '@intlify/core-base': 11.2.8
9508 '@intlify/shared': 11.2.8
9509 '@vue/devtools-api': 6.6.4
8969 - vue: 3.5.28(typescript@5.9.3)
9510 + vue: 3.5.29(typescript@5.9.3)
9511
8971 - vue-router@5.0.2(@vue/compiler-sfc@3.5.28)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))(vue@3.5.28(typescript@5.9.3)):
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)):
9513 dependencies:
9514 '@babel/generator': 7.29.1
8974 - '@vue-macros/common': 3.1.2(vue@3.5.28(typescript@5.9.3))
9515 + '@vue-macros/common': 3.1.2(vue@3.5.29(typescript@5.9.3))
9516 '@vue/devtools-api': 8.0.6
9517 ast-walker-scope: 0.8.3
9518 chokidar: 5.0.0
@@ -8986,56 +9527,56 @@ snapshots:
9527 tinyglobby: 0.2.15
9528 unplugin: 3.0.0
9529 unplugin-utils: 0.3.1
8989 - vue: 3.5.28(typescript@5.9.3)
9530 + vue: 3.5.29(typescript@5.9.3)
9531 yaml: 2.8.2
9532 optionalDependencies:
8992 - '@vue/compiler-sfc': 3.5.28
8993 - pinia: 3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))
9533 + '@vue/compiler-sfc': 3.5.29
9534 + pinia: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
9535
8995 - vue-sjv@0.0.6(vue@3.5.28(typescript@5.9.3)):
9536 + vue-sjv@0.0.6(vue@3.5.29(typescript@5.9.3)):
9537 dependencies:
8997 - vue: 3.5.28(typescript@5.9.3)
9538 + vue: 3.5.29(typescript@5.9.3)
9539
8999 - vue-tsc@3.2.4(typescript@5.9.3):
9540 + vue-tsc@3.2.5(typescript@5.9.3):
9541 dependencies:
9001 - '@volar/typescript': 2.4.27
9002 - '@vue/language-core': 3.2.4
9542 + '@volar/typescript': 2.4.28
9543 + '@vue/language-core': 3.2.5
9544 typescript: 5.9.3
9545
9005 - vue3-apexcharts@1.10.0(apexcharts@5.5.1)(vue@3.5.28(typescript@5.9.3)):
9546 + vue3-apexcharts@1.11.1(apexcharts@5.5.1)(vue@3.5.29(typescript@5.9.3)):
9547 dependencies:
9548 apexcharts: 5.5.1
9008 - vue: 3.5.28(typescript@5.9.3)
9549 + vue: 3.5.29(typescript@5.9.3)
9550
9010 - vue3-marquee@4.2.2(vue@3.5.28(typescript@5.9.3)):
9551 + vue3-marquee@4.2.2(vue@3.5.29(typescript@5.9.3)):
9552 dependencies:
9012 - vue: 3.5.28(typescript@5.9.3)
9553 + vue: 3.5.29(typescript@5.9.3)
9554
9014 - vue@3.5.28(typescript@5.9.3):
9555 + vue@3.5.29(typescript@5.9.3):
9556 dependencies:
9016 - '@vue/compiler-dom': 3.5.28
9017 - '@vue/compiler-sfc': 3.5.28
9018 - '@vue/runtime-dom': 3.5.28
9019 - '@vue/server-renderer': 3.5.28(vue@3.5.28(typescript@5.9.3))
9020 - '@vue/shared': 3.5.28
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
9562 optionalDependencies:
9563 typescript: 5.9.3
9564
9024 - vuedraggable@4.1.0(vue@3.5.28(typescript@5.9.3)):
9565 + vuedraggable@4.1.0(vue@3.5.29(typescript@5.9.3)):
9566 dependencies:
9567 sortablejs: 1.14.0
9027 - vue: 3.5.28(typescript@5.9.3)
9568 + vue: 3.5.29(typescript@5.9.3)
9569
9029 - vueuc@0.4.65(vue@3.5.28(typescript@5.9.3)):
9570 + vueuc@0.4.65(vue@3.5.29(typescript@5.9.3)):
9571 dependencies:
9031 - '@css-render/vue3-ssr': 0.15.14(vue@3.5.28(typescript@5.9.3))
9572 + '@css-render/vue3-ssr': 0.15.14(vue@3.5.29(typescript@5.9.3))
9573 '@juggle/resize-observer': 3.4.0
9574 css-render: 0.15.14
9575 evtd: 0.2.4
9576 seemly: 0.3.10
9036 - vdirs: 0.1.8(vue@3.5.28(typescript@5.9.3))
9037 - vooks: 0.2.12(vue@3.5.28(typescript@5.9.3))
9038 - vue: 3.5.28(typescript@5.9.3)
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)
9580
9581 w3c-keyname@2.2.8: {}
9582
@@ -9043,9 +9584,9 @@ snapshots:
9584 dependencies:
9585 xml-name-validator: 5.0.0
9586
9046 - wait-on@9.0.3(debug@4.4.3):
9587 + wait-on@9.0.4(debug@4.4.3):
9588 dependencies:
9048 - axios: 1.13.5(debug@4.4.3)
9589 + axios: 1.13.6(debug@4.4.3)
9590 joi: 18.0.2
9591 lodash: 4.17.23
9592 minimist: 1.2.8
@@ -9084,6 +9625,13 @@ snapshots:
9625 siginfo: 2.0.0
9626 stackback: 0.0.2
9627
9628 + with@7.0.2:
9629 + dependencies:
9630 + '@babel/parser': 7.29.0
9631 + '@babel/types': 7.29.0
9632 + assert-never: 1.4.0
9633 + babel-walk: 3.0.0-canary-5
9634 +
9635 word-wrap@1.2.5: {}
9636
9637 wrap-ansi@7.0.0:
@@ -9098,6 +9646,8 @@ snapshots:
9646 string-width: 5.1.2
9647 strip-ansi: 7.1.2
9648
9649 + wrappy@1.0.2: {}
9650 +
9651 wsl-utils@0.1.0:
9652 dependencies:
9653 is-wsl: 3.1.1
@@ -9108,9 +9658,9 @@ snapshots:
9658
9659 xmlchars@2.2.0: {}
9660
9111 - xmllint-wasm@5.1.0(@types/node@25.2.3):
9661 + xmllint-wasm@5.1.0(@types/node@25.3.5):
9662 dependencies:
9113 - '@types/node': 25.2.3
9663 + '@types/node': 25.3.5
9664
9665 xmllint@0.1.1: {}
9666
@@ -9120,7 +9670,7 @@ snapshots:
9670
9671 yaml-eslint-parser@2.0.0:
9672 dependencies:
9123 - eslint-visitor-keys: 5.0.0
9673 + eslint-visitor-keys: 5.0.1
9674 yaml: 2.8.2
9675
9676 yaml@1.10.2: {}
frontend/scripts/tokens-tool.js
+2 -2
@@ -10,6 +10,7 @@ const GLOBAL_KEYS = ["border-radius", "line-heights", "font-sizes", "font-famili
10 const TYPO_KEYS = ["typo"]
11 const COLOR_KEYS = ["color"]
12 const COLOR_OPACITY_LIST = [] // [5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90]
13 +const COLOR_SUFFIX_REGEX = /-\d{3}$/
14 const TOKENS_MAP = [
15 {
16 token: "colors",
@@ -133,9 +134,8 @@ async function importTokens(tokensPath) {
134
135 for (const k in group) {
136 for (const sk of COLOR_KEYS) {
136 - const regex = /-\d{3}$/
137 // exclude opacity variants
138 - if (k.includes(sk) && !regex.test(k)) {
138 + if (k.includes(sk) && !COLOR_SUFFIX_REGEX.test(k)) {
139 const skParsed = tokenNameSanitize(_.camelCase(sk), "type")
140 const name = _.camelCase(k.replace(`${sk}-`, ""))
141 let value = group[k].value
frontend/src/api/endpoints/agents.ts
+1 -1
@@ -19,7 +19,7 @@ export interface AgentPayload {
19
20 export type VulnerabilitySeverityType = "Low" | "Medium" | "High" | "Critical" | "All"
21
22 -// TODO: refactor
22 +// TODO-FE: refactor
23 export default {
24 getAgents(agentId?: string) {
25 return HttpClient.get<FlaskBaseResponse & { agents: Agent[] }>(`/agents${agentId ? `/${agentId}` : ""}`)
frontend/src/api/endpoints/copilotSearches.ts new
+146
@@ -0,0 +1,146 @@
1 +import type {
2 + ExecuteGraylogQueryRequest,
3 + ExecuteSearchRequest,
4 + ExecuteSearchResponse,
5 + GraylogQueryResponse,
6 + PlatformFilter,
7 + ProvisionGraylogAlertRequest,
8 + ProvisionGraylogAlertResponse,
9 + RefreshResponse,
10 + RuleDetailResponse,
11 + RuleListQuery,
12 + RuleListResponse,
13 + RuleStatsResponse
14 +} from "@/types/copilotSearches.d"
15 +import type { FlaskBaseResponse } from "@/types/flask.d"
16 +import { HttpClient } from "../httpClient"
17 +
18 +export default {
19 + /**
20 + * List all detection rules with optional filtering
21 + */
22 + getRules(query?: RuleListQuery, signal?: AbortSignal) {
23 + return HttpClient.get<FlaskBaseResponse & RuleListResponse>(`/copilot_searches`, {
24 + params: {
25 + platform: query?.platform,
26 + status: query?.status,
27 + severity: query?.severity,
28 + mitre_id: query?.mitre_id,
29 + search: query?.search,
30 + has_graylog: query?.has_graylog,
31 + skip: query?.skip || 0,
32 + limit: query?.limit || 100
33 + },
34 + signal
35 + })
36 + },
37 +
38 + /**
39 + * List all Linux detection rules
40 + */
41 + getLinuxRules(query?: Omit<RuleListQuery, "platform">, signal?: AbortSignal) {
42 + return HttpClient.get<FlaskBaseResponse & RuleListResponse>(`/copilot_searches/linux`, {
43 + params: {
44 + status: query?.status,
45 + severity: query?.severity,
46 + mitre_id: query?.mitre_id,
47 + search: query?.search,
48 + has_graylog: query?.has_graylog,
49 + skip: query?.skip || 0,
50 + limit: query?.limit || 100
51 + },
52 + signal
53 + })
54 + },
55 +
56 + /**
57 + * List all Windows detection rules
58 + */
59 + getWindowsRules(query?: Omit<RuleListQuery, "platform">, signal?: AbortSignal) {
60 + return HttpClient.get<FlaskBaseResponse & RuleListResponse>(`/copilot_searches/windows`, {
61 + params: {
62 + status: query?.status,
63 + severity: query?.severity,
64 + mitre_id: query?.mitre_id,
65 + search: query?.search,
66 + has_graylog: query?.has_graylog,
67 + skip: query?.skip || 0,
68 + limit: query?.limit || 100
69 + },
70 + signal
71 + })
72 + },
73 +
74 + /**
75 + * Get statistics about loaded detection rules
76 + */
77 + getStats(signal?: AbortSignal) {
78 + return HttpClient.get<FlaskBaseResponse & RuleStatsResponse>(`/copilot_searches/stats`, {
79 + signal
80 + })
81 + },
82 +
83 + /**
84 + * Get full details of a specific rule by its ID
85 + */
86 + getRuleById(ruleId: string, signal?: AbortSignal) {
87 + return HttpClient.get<FlaskBaseResponse & RuleDetailResponse>(`/copilot_searches/id/${ruleId}`, {
88 + signal
89 + })
90 + },
91 +
92 + /**
93 + * Get full details of a specific rule by its name
94 + */
95 + getRuleByName(ruleName: string, signal?: AbortSignal) {
96 + return HttpClient.get<FlaskBaseResponse & RuleDetailResponse>(
97 + `/copilot_searches/name/${encodeURIComponent(ruleName)}`,
98 + {
99 + signal
100 + }
101 + )
102 + },
103 +
104 + /**
105 + * Get all rules that detect a specific MITRE ATT&CK technique
106 + */
107 + getRulesByMitre(techniqueId: string, platform?: PlatformFilter, signal?: AbortSignal) {
108 + return HttpClient.get<FlaskBaseResponse & RuleListResponse>(`/copilot_searches/mitre/${techniqueId}`, {
109 + params: {
110 + platform
111 + },
112 + signal
113 + })
114 + },
115 +
116 + /**
117 + * Manually refresh the rules cache from GitHub
118 + */
119 + refreshCache() {
120 + return HttpClient.post<FlaskBaseResponse & RefreshResponse>(`/copilot_searches/refresh`)
121 + },
122 +
123 + /**
124 + * Execute a detection rule search against the Wazuh indexer
125 + */
126 + executeSearch(request: ExecuteSearchRequest) {
127 + return HttpClient.post<FlaskBaseResponse & ExecuteSearchResponse>(`/copilot_searches/execute`, request)
128 + },
129 +
130 + /**
131 + * Generate a Graylog query from a rule with parameter substitution
132 + */
133 + generateGraylogQuery(request: ExecuteGraylogQueryRequest) {
134 + return HttpClient.post<FlaskBaseResponse & GraylogQueryResponse>(`/copilot_searches/graylog`, request)
135 + },
136 +
137 + /**
138 + * Provision a Graylog event definition from a CoPilot Search rule
139 + */
140 + provisionGraylogAlert(request: ProvisionGraylogAlertRequest) {
141 + return HttpClient.post<FlaskBaseResponse & ProvisionGraylogAlertResponse>(
142 + `/copilot_searches/provision/graylog`,
143 + request
144 + )
145 + }
146 +}
frontend/src/api/endpoints/githubAudit.ts
+1 -1
@@ -19,7 +19,7 @@ import { HttpClient } from "../httpClient"
19
20 const BASE_PATH = "/github-audit"
21
22 -// TODO: refactor
22 +// TODO-FE: refactor
23 export default {
24 // ==================== Configuration Endpoints ====================
25
frontend/src/api/endpoints/incidentManagement/alerts.ts
+2 -2
@@ -38,7 +38,7 @@ export interface AlertIocPayload {
38 ioc_description: string
39 }
40
41 -// TODO: refactor
41 +// TODO-FE: refactor
42 export default {
43 getAlertsList(args: Partial<AlertsQuery>, signal?: AbortSignal) {
44 let url = `/incidents/db_operations/alerts`
@@ -65,7 +65,7 @@ export default {
65 url = `/incidents/db_operations/alerts/source/${args.filter.source}`
66 }
67
68 - // TODO: remove any
68 + // TODO-FE: remove any
69 const params: any = {
70 page: args.page || 1,
71 page_size: args.pageSize || 25,
frontend/src/api/endpoints/incidentManagement/cases.ts
+20 -2
@@ -6,6 +6,7 @@ import type {
6 CaseDataStore,
7 CasePayload,
8 CaseReportTemplateDataStore,
9 + CasesListResponse,
10 CaseStatus
11 } from "@/types/incidentManagement/cases.d"
12 import { HttpClient } from "../../httpClient"
@@ -18,6 +19,12 @@ export type CasesFilter =
19
20 export type CasesFilterTypes = KeysOfUnion<CasesFilter>
21
22 +export interface CasesPaginationParams {
23 + page?: number
24 + pageSize?: number
25 + order?: "asc" | "desc"
26 +}
27 +
28 export interface CaseReportPayload {
29 case_id: number
30 file_name: string
@@ -29,7 +36,7 @@ export type CaseCommentPayload = Omit<CaseComment, "id">
36 export type CaseCommentUpdatePayload = Omit<CaseComment, "id"> & { comment_id: number }
37
38 export default {
32 - getCasesList(filters?: Partial<UnionToIntersection<CasesFilter>>) {
39 + getCasesList(filters?: Partial<UnionToIntersection<CasesFilter>>, pagination?: CasesPaginationParams) {
40 let url = `/incidents/db_operations/cases`
41
42 if (filters?.status) {
@@ -45,7 +52,18 @@ export default {
52 url = `/agents/${filters.hostname}/cases`
53 }
54
48 - return HttpClient.get<FlaskBaseResponse & { cases: Case[] }>(url)
55 + const params: Record<string, number | string> = {}
56 + if (pagination?.page !== undefined) {
57 + params.page = pagination.page
58 + }
59 + if (pagination?.pageSize !== undefined) {
60 + params.page_size = pagination.pageSize
61 + }
62 + if (pagination?.order !== undefined) {
63 + params.order = pagination.order
64 + }
65 +
66 + return HttpClient.get<CasesListResponse>(url, { params })
67 },
68 getCase(caseId: number) {
69 return HttpClient.get<FlaskBaseResponse & { cases: Case[] }>(`/incidents/db_operations/case/${caseId}`)
frontend/src/api/endpoints/incidentManagement/exclusionRules.ts
+1 -1
@@ -24,7 +24,7 @@ export interface ExclusionRulePayload {
24
25 export default {
26 getExclusionRulesList(args: Partial<ExclusionRulesQuery>, signal?: AbortSignal) {
27 - // TODO: remove any
27 + // TODO-FE: remove any
28 const params: any = {
29 skip: args.pagination?.skip || 0,
30 limit: args.pagination?.limit || 25
frontend/src/api/endpoints/license.ts
+2 -2
@@ -59,7 +59,7 @@ export default {
59 retrieveDockerCompose() {
60 return HttpClient.post<FlaskBaseResponse & { docker_compose: string }>(`/license/retrieve-docker-compose`)
61 },
62 - // TODO: remove, deprecated
62 + // TODO-FE: remove, deprecated
63 /** @deprecated */
64 extendLicense(period: number) {
65 return HttpClient.post<FlaskBaseResponse>(
@@ -70,7 +70,7 @@ export default {
70 }
71 )
72 },
73 - // TODO: remove, deprecated
73 + // TODO-FE: remove, deprecated
74 /** @deprecated */
75 createLicense({ name, email, companyName }: NewLicensePayload) {
76 return HttpClient.post<FlaskBaseResponse>(`/license/create_new_key`, {
frontend/src/api/endpoints/networkConnectors.ts
+1 -1
@@ -54,7 +54,7 @@ export interface SentinelOneProvisionPayload extends SentinelOneProvision {
54 integration_name: string
55 }
56
57 -// TODO: refactor
57 +// TODO-FE: refactor
58 export default {
59 // #region Network Connector
60 getAvailableNetworkConnectors() {
frontend/src/api/endpoints/patchTuesday.ts
+1 -1
@@ -11,7 +11,7 @@ import { HttpClient } from "../httpClient"
11
12 const BASE_PATH = "/patch-tuesday"
13
14 -// TODO: refactor
14 +// TODO-FE: refactor
15 export default {
16 /**
17 * Get full Patch Tuesday data for a specific cycle
frontend/src/api/endpoints/snapshots.ts
+1 -1
@@ -13,7 +13,7 @@ import type {
13 } from "@/types/snapshots.d"
14 import { HttpClient } from "../httpClient"
15
16 -// TODO: refactor
16 +// TODO-FE: refactor
17 export default {
18 // Repository endpoints
19 getRepositories() {
frontend/src/api/endpoints/vulnerabilities.ts
+1 -1
@@ -8,7 +8,7 @@ import type {
8 } from "@/types/vulnerabilities.d"
9 import { HttpClient } from "../httpClient"
10
11 -// TODO: refactor
11 +// TODO-FE: refactor
12 export default {
13 /**
14 * Search vulnerabilities directly from Wazuh indexer with filtering and pagination
frontend/src/api/endpoints/wazuh/indices.ts
+1 -1
@@ -2,7 +2,7 @@ import type { FlaskBaseResponse } from "@/types/flask.d"
2 import type { ClusterHealth, IndexAllocation, IndexShard, IndexStats } from "@/types/indices.d"
3 import { HttpClient } from "../../httpClient"
4
5 -// TODO: refactor
5 +// TODO-FE: refactor
6 export default {
7 getAllocation() {
8 return HttpClient.get<FlaskBaseResponse & { node_allocation: IndexAllocation[] }>("/wazuh_indexer/allocation")
frontend/src/api/index.ts
+2
@@ -8,6 +8,7 @@ import cloudSecurityAssessment from "./endpoints/cloudSecurityAssessment"
8 import connectors from "./endpoints/connectors"
9 import copilotAction from "./endpoints/copilotAction"
10 import copilotMCP from "./endpoints/copilotMCP"
11 +import copilotSearches from "./endpoints/copilotSearches"
12 import customerPortal from "./endpoints/customerPortal"
13 import customers from "./endpoints/customers"
14 import flow from "./endpoints/flow"
@@ -65,6 +66,7 @@ export default {
66 githubAudit,
67 scheduler,
68 networkConnectors,
69 + copilotSearches,
70 cloudSecurityAssessment,
71 webVulnerabilityAssessment,
72 incidentManagement,
frontend/src/api/sseClient.ts
+3 -1
@@ -2,6 +2,8 @@ import { fetchEventSource } from "@microsoft/fetch-event-source"
2 import { useAuthStore } from "@/stores/auth"
3 import { HttpClient } from "./httpClient"
4
5 +const TRAILING_SLASH_REGEX = /\/$/
6 +
7 /** Converte Record<string, string | number> in query string, escludendo undefined/null */
8 function paramsToQueryString(params?: Record<string, string | number | undefined>): string {
9 if (!params || Object.keys(params).length === 0) {
@@ -42,7 +44,7 @@ export async function createSSEStream(options: SSEClientOptions): Promise<void>
44 const baseURL = options.baseURL ?? HttpClient.defaults.baseURL ?? "/api"
45 const authStore = useAuthStore()
46
45 - const base = (baseURL ?? "").replace(/\/$/, "")
47 + const base = (baseURL ?? "").replace(TRAILING_SLASH_REGEX, "")
48 const cleanPath = path.startsWith("/") ? path : `/${path}`
49 const queryString = paramsToQueryString(params)
50 const url = `${base}${cleanPath}${queryString}`
frontend/src/app-layouts/common/Navbar/Navbar.vue
+34 -3
@@ -18,6 +18,12 @@
18 @update:expanded-keys="handleUpdateExpandedKeys"
19 />
20 </nav>
21 +
22 + <div class="hidden">
23 + <StackProvisioningButton ref="stackProvisioningButton" />
24 + <ActiveResponseWizardButton ref="activeResponseWizardButton" />
25 + <ThreatIntelButton ref="threatIntelButton" />
26 + </div>
27 </template>
28
29 <script lang="ts" setup>
@@ -26,9 +32,13 @@ import type { MenuMixedOption } from "naive-ui/es/menu/src/interface"
32 import type { RouteRecordNormalized } from "vue-router"
33 import _uniq from "lodash/uniq"
34 import { NMenu } from "naive-ui"
29 -import { computed, onBeforeMount, ref } from "vue"
35 +import { computed, onBeforeMount, ref, watch } from "vue"
36 import { useRoute, useRouter } from "vue-router"
37 +import ActiveResponseWizardButton from "@/components/activeResponse/ActiveResponseWizardButton.vue"
38 +import StackProvisioningButton from "@/components/stackProvisioning/StackProvisioningButton.vue"
39 +import ThreatIntelButton from "@/components/threatIntel/ThreatIntelButton.vue"
40 import { useThemeStore } from "@/stores/theme"
41 +
42 import getItems from "./items"
43
44 const { collapsed = false } = defineProps<{
@@ -42,11 +52,33 @@ const menu = ref<MenuInst | null>(null)
52 const expandedKeys = ref<string[] | undefined>(undefined)
53
54 const themeStore = useThemeStore()
45 -
55 const menuOptions = computed<MenuMixedOption[]>(() => getItems())
56 const collapsedWidth = computed<number>(() => themeStore.sidebar.closeWidth)
57 const sidebarCollapsed = computed<boolean>(() => themeStore.sidebar.collapsed)
58
59 +const stackProvisioningButton = ref<InstanceType<typeof StackProvisioningButton> | null>(null)
60 +const threatIntelButton = ref<InstanceType<typeof ThreatIntelButton> | null>(null)
61 +const activeResponseWizardButton = ref<InstanceType<typeof ActiveResponseWizardButton> | null>(null)
62 +
63 +watch(selectedKey, val => {
64 + handleMenuSelect(val)
65 +})
66 +
67 +function handleMenuSelect(key: string | null) {
68 + switch (key) {
69 + case "Tools-ThreatIntel":
70 + threatIntelButton.value?.openDrawer()
71 + break
72 + case "Tools-StackProvisioning":
73 + stackProvisioningButton.value?.openModal()
74 + break
75 + case "Tools-ActiveResponse":
76 + activeResponseWizardButton.value?.openModal()
77 + break
78 + }
79 + selectedKey.value = key
80 +}
81 +
82 function setMenuKey(matched: RouteRecordNormalized[]) {
83 for (const match of matched) {
84 if (match.name && typeof match.name === "string") {
@@ -72,7 +104,6 @@ onBeforeMount(() => {
104 })
105 })
106
75 -// handler to simulate the accordion behavior in a specific submenu
107 function handleUpdateExpandedKeys(value: string[]) {
108 const submenu = "components"
109
frontend/src/app-layouts/common/Navbar/items.tsx
+73 -1
@@ -19,8 +19,9 @@ const ExternalServicesIcon = "carbon:ibm-cloud-direct-link-2-dedicated"
19 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
23 -// TODO: refactor
24 +// TODO-FE: refactor
25 export default function getItems(): MenuMixedOption[] {
26 return [
27 {
@@ -242,6 +243,19 @@ export default function getItems(): MenuMixedOption[] {
243 ),
244 key: "CopilotActions"
245 },
246 + {
247 + label: () =>
248 + h(
249 + RouterLink,
250 + {
251 + to: {
252 + name: "CopilotSearches"
253 + }
254 + },
255 + { default: () => "CoPilot Searches" }
256 + ),
257 + key: "CopilotSearches"
258 + },
259 {
260 label: () =>
261 h(
@@ -482,6 +496,64 @@ export default function getItems(): MenuMixedOption[] {
496 }
497 ]
498 },
499 + {
500 + label: "Tools",
501 + key: "Tools",
502 + icon: renderIcon(ToolsIcon),
503 + children: [
504 + {
505 + label: "Stack Provisioning",
506 + key: "Tools-StackProvisioning"
507 + },
508 + {
509 + label: () =>
510 + h(
511 + RouterLink,
512 + {
513 + to: {
514 + name: "CloudSecurityAssessment"
515 + }
516 + },
517 + { default: () => "Cloud Security Assessment" }
518 + ),
519 + key: "CloudSecurityAssessment"
520 + },
521 + {
522 + label: () =>
523 + h(
524 + RouterLink,
525 + {
526 + to: {
527 + name: "WebVulnerabilityAssessment"
528 + }
529 + },
530 + { default: () => "Web Vulnerability Assessment" }
531 + ),
532 + key: "WebVulnerabilityAssessment"
533 + },
534 + {
535 + label: () =>
536 + h(
537 + RouterLink,
538 + {
539 + to: {
540 + name: "GitHubAudit"
541 + }
542 + },
543 + { default: () => "GitHub Audit" }
544 + ),
545 + key: "GitHubAudit"
546 + },
547 + {
548 + label: "Active Response",
549 + key: "Tools-ActiveResponse"
550 + },
551 + {
552 + label: "Threat Intel",
553 + key: "Tools-ThreatIntel"
554 + }
555 + ]
556 + },
557 {
558 label: () =>
559 h(
frontend/src/app-layouts/common/Toolbar/Breadcrumb.vue
+1 -1
@@ -64,7 +64,7 @@ function checkRoute(route: RouteLocationNormalizedLoaded) {
64 }
65
66 if (route.meta?.title && newItems.length) {
67 - const lastItem = newItems[newItems.length - 1]
67 + const lastItem = newItems.at(-1)
68 if (lastItem) {
69 lastItem.name = route.meta.title
70 }
frontend/src/app-layouts/common/Toolbar/LocaleSwitch.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-popselect v-model:value="currentLocale" :options="list" :render-label="renderLabel">
2 + <n-popselect v-model:value="currentLocale" :options="list" :render-label>
3 <Icon :size="19" :name="MultiLanguageIcon" />
4 </n-popselect>
5 </template>
frontend/src/app-layouts/common/Toolbar/PinnedPagesV2.vue
+1 -1
@@ -111,7 +111,7 @@ function pinPage(page: Page) {
111
112 function checkRoute(route: RouteLocationNormalized) {
113 const splitName = _split(route.name?.toString(), "-")
114 - const title = route.meta?.title || splitName[splitName.length - 1]
114 + const title = route.meta?.title || splitName.at(-1)
115
116 if (route.name && title && !route.meta?.skipPin) {
117 const page: Page = {
frontend/src/components/activeResponse/ActiveResponseActions.vue
+2 -2
@@ -18,8 +18,8 @@
18 segmented
19 >
20 <ActiveResponseInvokeForm
21 - :active-response="activeResponse"
22 - :agent-id="agentId"
21 + :active-response
22 + :agent-id
23 @mounted="activeResponseInvokeFormCTX = $event"
24 @submitted="close()"
25 @start-loading="loadingInvoke = true"
frontend/src/components/activeResponse/ActiveResponseAgent.vue
+2 -2
@@ -6,8 +6,8 @@
6 <ActiveResponseItem
7 v-for="activeResponse of activeResponseList"
8 :key="activeResponse.name"
9 - :active-response="activeResponse"
10 - :embedded="embedded"
9 + :active-response
10 + :embedded
11 :agent-id="agent.agent_id"
12 class="item-appear item-appear-bottom item-appear-005 mb-2"
13 />
frontend/src/components/activeResponse/ActiveResponseInvokeForm.vue
+1 -1
@@ -22,7 +22,7 @@
22 <div class="flex gap-3">
23 <slot name="additionalActions"></slot>
24 </div>
25 - <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">Submit</n-button>
25 + <n-button type="primary" :disabled="!isValid" :loading @click="validate()">Submit</n-button>
26 </div>
27 </div>
28 </template>
frontend/src/components/activeResponse/ActiveResponseItem.vue
+2 -2
@@ -22,7 +22,7 @@
22 </template>
23
24 <template v-if="!hideActions" #footerExtra>
25 - <ActiveResponseActions :agent-id="agentId" :active-response="activeResponse" size="small" />
25 + <ActiveResponseActions :agent-id :active-response size="small" />
26 </template>
27 </CardEntity>
28
@@ -34,7 +34,7 @@
34 :bordered="false"
35 segmented
36 >
37 - <ActiveResponseDetails :active-response="activeResponse" />
37 + <ActiveResponseDetails :active-response />
38 </n-modal>
39 </div>
40 </template>
frontend/src/components/activeResponse/ActiveResponseWizard.vue
+2 -2
@@ -4,7 +4,7 @@
4 <div class="flex grow flex-col">
5 <n-scrollbar x-scrollable trigger="none">
6 <div class="p-7 pt-4">
7 - <n-steps :current="current" size="small" :status="currentStatus">
7 + <n-steps :current size="small" :status="currentStatus">
8 <n-step title="Operative System" />
9 <n-step title="Active Response" />
10 <n-step title="Submission" />
@@ -42,7 +42,7 @@
42 <ActiveResponseItem
43 v-for="activeResponse of activeResponseFiltered"
44 :key="activeResponse.name"
45 - :active-response="activeResponse"
45 + :active-response
46 embedded
47 clickable
48 hide-actions
frontend/src/components/activeResponse/ActiveResponseWizardButton.vue
+16 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button :size="size" :type="type" :loading="loading" @click="showInvokeWizard = true">
2 + <n-button :size :type :secondary :loading @click="openModal">
3 <template #icon>
4 <Icon :name="InvokeIcon" />
5 </template>
@@ -27,9 +27,10 @@ import { ref, watch } from "vue"
27 import Icon from "@/components/common/Icon.vue"
28 import ActiveResponseWizard from "./ActiveResponseWizard.vue"
29
30 -const { type, size } = defineProps<{
30 +defineProps<{
31 size?: Size
32 type?: Type
33 + secondary?: boolean
34 }>()
35
36 const InvokeIcon = "solar:playback-speed-outline"
@@ -37,7 +38,20 @@ const showInvokeWizard = ref(false)
38 const activeResponseWizardCTX = ref<{ reset: () => void } | null>(null)
39 const loading = ref(false)
40
41 +function openModal() {
42 + showInvokeWizard.value = true
43 +}
44 +
45 +function closeModal() {
46 + showInvokeWizard.value = false
47 +}
48 +
49 watch(showInvokeWizard, () => {
50 activeResponseWizardCTX.value?.reset()
51 })
52 +
53 +defineExpose({
54 + openModal,
55 + closeModal
56 +})
57 </script>
frontend/src/components/agents/AgentCard.vue
+1 -1
@@ -82,7 +82,7 @@
82 </template>
83
84 <script setup lang="ts">
85 -// TODO: refactor
85 +// TODO-FE: refactor
86 import type { Agent } from "@/types/agents.d"
87 import { NButton, NCheckbox, NTooltip, useDialog, useMessage } from "naive-ui"
88 import { computed, ref, toRefs } from "vue"
frontend/src/components/agents/AgentToolbar.vue
+1 -1
@@ -100,7 +100,7 @@
100 </template>
101
102 <script setup lang="ts">
103 -// TODO: refactor
103 +// TODO-FE: refactor
104 import type { DropdownMixedOption } from "naive-ui/es/dropdown/src/interface"
105 import type { Agent } from "@/types/agents.d"
106 import type { Customer } from "@/types/customers.d"
frontend/src/components/agents/AgentVelociraptorIdForm.vue
+1 -1
@@ -21,7 +21,7 @@
21 />
22 </template>
23 </n-input>
24 - <n-button type="primary" ghost :loading="loading" size="small" @click="updateAgent()">
24 + <n-button type="primary" ghost :loading size="small" @click="updateAgent()">
25 <span v-if="!loading">Save</span>
26 </n-button>
27 </n-input-group>
frontend/src/components/agents/BulkDeleteModal.vue
+3 -3
@@ -110,7 +110,7 @@
110 <template #footer>
111 <n-space justify="end">
112 <n-button @click="closeModal">Cancel</n-button>
113 - <n-button type="error" :loading="loading" :disabled="!canDelete" @click="confirmDelete">
113 + <n-button type="error" :loading :disabled="!canDelete" @click="confirmDelete">
114 <template #icon>
115 <n-icon><Icon :name="DeleteIcon" /></n-icon>
116 </template>
@@ -143,13 +143,13 @@
143 </template>
144 <template #action>
145 <n-button @click="showConfirmDialog = false">Cancel</n-button>
146 - <n-button type="error" :loading="loading" @click="executeDelete">Confirm Delete</n-button>
146 + <n-button type="error" :loading @click="executeDelete">Confirm Delete</n-button>
147 </template>
148 </n-modal>
149 </template>
150
151 <script setup lang="ts">
152 -// TODO: refactor
152 +// TODO-FE: refactor
153 import type { Agent, BulkDeleteAgentsResponse, BulkDeleteFilterRequest } from "@/types/agents.d"
154 import {
155 NAlert,
frontend/src/components/agents/agentFlow/AgentFlowItem.vue
+4 -4
@@ -17,7 +17,7 @@
17 </div>
18 </template>
19 <div class="flex flex-col px-1 py-2">
20 - <AgentFlowTimeline :flow="flow" />
20 + <AgentFlowTimeline :flow />
21 </div>
22 </n-popover>
23 </template>
@@ -110,7 +110,7 @@
110 </n-tab-pane>
111 <n-tab-pane name="Timeline" tab="Timeline" display-directive="show:lazy">
112 <div class="p-7 pt-4">
113 - <AgentFlowTimeline :flow="flow" />
113 + <AgentFlowTimeline :flow />
114 </div>
115 </n-tab-pane>
116 <n-tab-pane name="Logs" tab="Logs" display-directive="show:lazy">
@@ -139,7 +139,7 @@
139 <AgentFlowQueryStat
140 v-for="stat of flow.query_stats"
141 :key="stat.first_active + stat.last_active"
142 - :stat="stat"
142 + :stat
143 embedded
144 class="item-appear item-appear-bottom item-appear-005 mb-2"
145 />
@@ -159,7 +159,7 @@
159 <n-tab-pane name="Collect" tab="Collect" display-directive="show:lazy">
160 <n-scrollbar class="max-h-106" trigger="none">
161 <div class="px-7">
162 - <AgentFlowCollectList :flow="flow" />
162 + <AgentFlowCollectList :flow />
163 </div>
164 </n-scrollbar>
165 </n-tab-pane>
frontend/src/components/agents/agentFlow/AgentFlowQueryStat.vue
+1 -1
@@ -54,7 +54,7 @@
54 <n-tabs type="line" animated :tabs-padding="24">
55 <n-tab-pane name="Info" tab="Info" display-directive="show">
56 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
57 - <CardKV v-for="(value, key) of properties" :key="key">
57 + <CardKV v-for="(value, key) of properties" :key>
58 <template #key>
59 {{ key }}
60 </template>
frontend/src/components/agents/agentFlow/mock.ts
+1 -1
@@ -105,7 +105,7 @@ export const collect_result: CollectResult[] = [
105 {
106 ___id: "2345678",
107 uno: 1,
108 - due: new Date().getTime(),
108 + due: Date.now(),
109 tre: "tre"
110 }
111 ]
frontend/src/components/agents/dataStore/AgentDataStoreTab.vue
+6 -6
@@ -18,7 +18,7 @@
18 clearable
19 />
20
21 - <n-button type="primary" secondary size="small" :loading="loading" @click="getArtifacts()">
21 + <n-button type="primary" secondary size="small" :loading @click="getArtifacts()">
22 <template #icon>
23 <Icon :name="RefreshIcon" />
24 </template>
@@ -66,7 +66,7 @@
66 <div v-if="artifactsFiltered.length > pageSize" class="mt-4 flex justify-end">
67 <n-pagination
68 v-model:page="page"
69 - :page-size="pageSize"
69 + :page-size
70 :page-slot="5"
71 :item-count="artifactsFiltered.length"
72 size="small"
@@ -108,13 +108,13 @@ import {
108 useDialog,
109 useMessage
110 } from "naive-ui"
111 -import { computed, onMounted, ref } from "vue"
111 +import { computed, onBeforeMount, ref } from "vue"
112 import Api from "@/api"
113 import Icon from "@/components/common/Icon.vue"
114 import ArtifactCard from "./ArtifactCard.vue"
115 import ArtifactDetails from "./ArtifactDetails.vue"
116
117 -// TODO: join AgentDataStoreTab + AgentDataStoreTabCompact
117 +// TODO-FE: join AgentDataStoreTab + AgentDataStoreTabCompact
118
119 const props = defineProps<{
120 agent: Agent
@@ -229,13 +229,13 @@ function showArtifactDetails(artifact: AgentArtifactData) {
229 showDetailsModal.value = true
230 }
231
232 -onMounted(() => {
232 +onBeforeMount(() => {
233 getArtifacts()
234 })
235 </script>
236
237 <style lang="scss" scoped>
238 -// TODO: remove style
238 +// TODO-FE: remove style
239
240 .agent-data-store-tab {
241 .filters-section {
frontend/src/components/agents/dataStore/AgentDataStoreTabCompact.vue
+5 -5
@@ -23,7 +23,7 @@
23 style="max-width: 150px"
24 />
25
26 - <n-button type="primary" secondary size="small" :loading="loading" @click="getArtifacts()">
26 + <n-button type="primary" secondary size="small" :loading @click="getArtifacts()">
27 <template #icon>
28 <Icon :name="RefreshIcon" />
29 </template>
@@ -72,7 +72,7 @@
72 <div v-if="artifactsFiltered.length > pageSize" class="mt-3 flex justify-end">
73 <n-pagination
74 v-model:page="page"
75 - :page-size="pageSize"
75 + :page-size
76 :page-slot="5"
77 :item-count="artifactsFiltered.length"
78 size="small"
@@ -112,7 +112,7 @@ import {
112 useDialog,
113 useMessage
114 } from "naive-ui"
115 -import { computed, onMounted, ref } from "vue"
115 +import { computed, onBeforeMount, ref } from "vue"
116 import Api from "@/api"
117 import Icon from "@/components/common/Icon.vue"
118 import ArtifactCardCompact from "./ArtifactCardCompact.vue"
@@ -229,13 +229,13 @@ function showArtifactDetails(artifact: AgentArtifactData) {
229 showDetailsModal.value = true
230 }
231
232 -onMounted(() => {
232 +onBeforeMount(() => {
233 getArtifacts()
234 })
235 </script>
236
237 <style lang="scss" scoped>
238 -// TODO: remove style
238 +// TODO-FE: remove style
239
240 .agent-data-store-tab-compact {
241 .filters-bar {
frontend/src/components/agents/dataStore/ArtifactCard.vue
+2 -2
@@ -74,7 +74,7 @@ import Icon from "@/components/common/Icon.vue"
74 import { useSettingsStore } from "@/stores/settings"
75 import { formatDate } from "@/utils/format"
76
77 -// TODO: join ArtifactCard + ArtifactCardCompact
77 +// TODO-FE: join ArtifactCard + ArtifactCardCompact
78
79 const {
80 artifact,
@@ -118,7 +118,7 @@ const statusType = computed<BadgeProps["type"]>(() => {
118 </script>
119
120 <style lang="scss" scoped>
121 -// TODO: remove style
121 +// TODO-FE: remove style
122
123 .artifact-card {
124 border-radius: var(--border-radius);
frontend/src/components/agents/dataStore/ArtifactCardCompact.vue
+2 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-card size="small" :bordered="true" hoverable class="artifact-card-compact">
2 + <n-card size="small" bordered hoverable class="artifact-card-compact">
3 <div class="flex items-start justify-between gap-3">
4 <div class="min-w-0 flex-1">
5 <div class="mb-1 flex items-center gap-2">
@@ -83,7 +83,7 @@ const statusType = computed<TagProps["type"]>(() => {
83 </script>
84
85 <style lang="scss" scoped>
86 -// TODO: remove style
86 +// TODO-FE: remove style
87
88 .artifact-card-compact {
89 transition: all 0.2s var(--bezier-ease);
frontend/src/components/agents/dataStore/ArtifactDetails.vue
+1 -1
@@ -137,7 +137,7 @@ const visibleFields = computed(() => fields.value.filter(field => field.conditio
137 </script>
138
139 <style lang="scss" scoped>
140 -// TODO: remove style
140 +// TODO-FE: remove style
141 .artifact-details {
142 :deep() {
143 code {
frontend/src/components/agents/fileCollection/FileCollectionForm.vue
+2 -2
@@ -31,7 +31,7 @@
31 <div class="flex items-center gap-3">
32 <n-button
33 type="primary"
34 - :loading="loading"
34 + :loading
35 :disabled="!formData.file_path || !formData.root_disk"
36 @click="handleSubmit"
37 >
@@ -205,7 +205,7 @@ defineExpose({
205 </script>
206
207 <style lang="scss" scoped>
208 -// TODO: remove style
208 +// TODO-FE: remove style
209
210 .file-collection-form {
211 max-width: 600px;
frontend/src/components/agents/sca/ScaItem.vue
+2 -2
@@ -29,7 +29,7 @@
29 </n-card>
30 </div>
31 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 px-7">
32 - <CardKV v-for="(value, key) of properties" :key="key">
32 + <CardKV v-for="(value, key) of properties" :key>
33 <template #key>
34 {{ key }}
35 </template>
@@ -72,7 +72,7 @@
72 </n-tab-pane>
73 <n-tab-pane name="SCA Results" tab="SCA Results" display-directive="show:lazy">
74 <div class="p-7 pt-4">
75 - <ScaResults :sca="sca" :agent />
75 + <ScaResults :sca :agent />
76 </div>
77 </n-tab-pane>
78 </n-tabs>
frontend/src/components/agents/sca/ScaResultItem.vue
+1 -1
@@ -66,7 +66,7 @@
66 :bordered="false"
67 segmented
68 >
69 - <ScaResultItemDetails :data="data" />
69 + <ScaResultItemDetails :data />
70 </n-modal>
71 </div>
72 </template>
frontend/src/components/agents/sca/ScaResultItemDetails.vue
+1 -1
@@ -39,7 +39,7 @@
39 </div>
40
41 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 px-7">
42 - <CardKV v-for="(value, key) of properties" :key="key">
42 + <CardKV v-for="(value, key) of properties" :key>
43 <template #key>
44 {{ key }}
45 </template>
frontend/src/components/agents/sca/ScaResults.vue
+5 -5
@@ -35,9 +35,9 @@
35 <n-pagination
36 v-model:page="currentPage"
37 v-model:page-size="pageSize"
38 - :page-slot="pageSlot"
39 - :show-size-picker="showSizePicker"
40 - :page-sizes="pageSizes"
38 + :page-slot
39 + :show-size-picker
40 + :page-sizes
41 :item-count="itemsFiltered.length"
42 :simple="simpleMode"
43 />
@@ -78,7 +78,7 @@
78 <n-pagination
79 v-if="itemsPaginated.length > 3"
80 v-model:page="currentPage"
81 - :page-size="pageSize"
81 + :page-size
82 :item-count="itemsFiltered.length"
83 :page-slot="6"
84 :simple="simpleMode"
@@ -88,7 +88,7 @@
88 </template>
89
90 <script setup lang="ts">
91 -// TODO: refactor
91 +// TODO-FE: refactor
92 import type { Agent, AgentSca, ScaPolicyResult } from "@/types/agents.d"
93 import { useResizeObserver } from "@vueuse/core"
94 import { NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
frontend/src/components/agents/sca/ScaTable.vue
+1 -1
@@ -1,7 +1,7 @@
1 <template>
2 <n-spin class="sca-table" :show="loading">
3 <n-scrollbar x-scrollable class="w-full">
4 - <n-table :bordered="true" class="min-w-max">
4 + <n-table bordered class="min-w-max">
5 <thead>
6 <tr>
7 <th></th>
frontend/src/components/aiChatbot/ChatQuery.vue
+2 -2
@@ -25,7 +25,7 @@
25 <n-select
26 v-model:value="selectedServer"
27 :options="serverOptions"
28 - :render-option="renderOption"
28 + :render-option
29 size="tiny"
30 :consistent-menu-width="false"
31 class="w-auto!"
@@ -93,7 +93,7 @@
93 </template>
94
95 <script setup lang="ts">
96 -// TODO: refactor
96 +// TODO-FE: refactor
97 import type { RemovableRef } from "@vueuse/core"
98 import type { SelectOption } from "naive-ui"
99 import type { VNode } from "vue"
frontend/src/components/aiChatbot/ChatQuestions.vue
+1 -1
@@ -75,7 +75,7 @@ function getExampleQuestions(server: string) {
75 }
76
77 function upsertExampleQuestions(server: string, questions: ExampleQuestion[]) {
78 - if (!exampleQuestions.value.find(o => o.server === server)) {
78 + if (!exampleQuestions.value.some(o => o.server === server)) {
79 exampleQuestions.value.push({
80 server,
81 questions
frontend/src/components/alerts/Alert.vue
+1 -1
@@ -130,7 +130,7 @@
130 <n-tabs type="line" animated :tabs-padding="24">
131 <n-tab-pane v-if="alert._id" name="Agent" tab="Agent" display-directive="show">
132 <div v-if="agentProperties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
133 - <CardKV v-for="(value, key) of agentProperties" :key="key">
133 + <CardKV v-for="(value, key) of agentProperties" :key>
134 <template #key>
135 {{ key }}
136 </template>
frontend/src/components/alerts/AlertActions.vue
+7 -6
@@ -1,6 +1,6 @@
1 <template>
2 <div class="flex flex-wrap justify-end gap-2">
3 - <n-button v-if="socAlertFieldValue" type="success" secondary :size="size" @click.stop="gotoSocAlertUrl()">
3 + <n-button v-if="socAlertFieldValue" type="success" secondary :size @click.stop="gotoSocAlertUrl()">
4 <template #icon>
5 <Icon :name="ViewIcon" />
6 </template>
@@ -11,7 +11,7 @@
11 :loading="loadingSocAlert"
12 type="warning"
13 secondary
14 - :size="size"
14 + :size
15 @click.stop="createAlert()"
16 >
17 <template #icon>
@@ -19,7 +19,7 @@
19 </template>
20 Create SOC Alert
21 </n-button>
22 - <n-button v-if="alertAskMessage" type="success" secondary :size="size" @click.stop="showSocResponse = true">
22 + <n-button v-if="alertAskMessage" type="success" secondary :size @click.stop="showSocResponse = true">
23 <template #icon>
24 <Icon :name="ViewIcon" />
25 </template>
@@ -30,7 +30,7 @@
30 :loading="loadingAskSoc"
31 type="warning"
32 secondary
33 - :size="size"
33 + :size
34 @click.stop="askSOCFortress()"
35 >
36 <template #icon>
@@ -42,7 +42,7 @@
42 v-if="isWazuhRulesVisible"
43 :loading="loadingWazuhRuleExclude"
44 secondary
45 - :size="size"
45 + :size
46 @click.stop="wazuhManagerRuleExclude()"
47 >
48 <template #icon>
@@ -86,6 +86,7 @@
86 </template>
87
88 <script setup lang="ts">
89 +import type { Size } from "naive-ui/es/button/src/interface"
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"
@@ -97,7 +98,7 @@ import AlertWazuhRules from "./AlertWazuhRules.vue"
98
99 const { alert, size, socAlertField } = defineProps<{
100 alert: Alert
100 - size?: "tiny" | "small" | "medium" | "large"
101 + size?: Size
102 socAlertField: SocAlertField
103 }>()
104
frontend/src/components/alerts/AlertWazuhRules.vue
+3 -1
@@ -33,7 +33,9 @@ const CodeSource = defineAsyncComponent(() => import("@/components/common/CodeSo
33
34 const { data } = toRefs(props)
35
36 -const wazuh_rule = computed(() => data.value.wazuh_rule.replace(/\\\\/g, "\\\\\\\\"))
36 +const WAZUH_RULE_BACKSLASH_REGEX = /\\\\/g
37 +
38 +const wazuh_rule = computed(() => data.value.wazuh_rule.replace(WAZUH_RULE_BACKSLASH_REGEX, "\\\\\\\\"))
39 </script>
40
41 <style lang="scss" scoped>
frontend/src/components/alerts/AlertsFilters.vue
+1 -1
@@ -56,7 +56,7 @@
56 </template>
57
58 <script setup lang="ts">
59 -// TODO: refactor
59 +// TODO-FE: refactor
60 import type { SelectOption } from "naive-ui"
61 import type { VNodeChild } from "vue"
62 import type { AlertsQueryTimeRange, AlertsSummaryQuery } from "@/api/endpoints/alerts"
frontend/src/components/alerts/AlertsGraylogList.vue
+1 -1
@@ -42,7 +42,7 @@
42 <AlertsSummaryItem
43 v-for="alertsSummary of alertsSummaryList"
44 :key="alertsSummary.index_name"
45 - :alerts-summary="alertsSummary"
45 + :alerts-summary
46 class="item-appear item-appear-bottom item-appear-005"
47 />
48 </template>
frontend/src/components/alerts/AlertsList.vue
+3 -3
@@ -48,7 +48,7 @@
48 <AlertsSummaryItem
49 v-for="alertsSummary of alertsSummaryList"
50 :key="alertsSummary.index_name"
51 - :alerts-summary="alertsSummary"
51 + :alerts-summary
52 class="item-appear item-appear-bottom item-appear-005"
53 />
54 </template>
@@ -66,7 +66,7 @@
66 display-directive="show"
67 >
68 <n-drawer-content title="Alerts stats" closable body-content-style="padding:0" :native-scrollbar="false">
69 - <AlertsStats :filters="filters" @mounted="alertsStatsCTX = $event" />
69 + <AlertsStats :filters @mounted="alertsStatsCTX = $event" />
70 </n-drawer-content>
71 </n-drawer>
72
@@ -79,7 +79,7 @@
79 :class="{ 'opacity-0': loadingFilters }"
80 >
81 <n-drawer-content title="Alerts filters" closable :native-scrollbar="false">
82 - <AlertsFilters :filters="filters" @search="startSearch(true)">
82 + <AlertsFilters :filters @search="startSearch(true)">
83 <div v-if="!isFilterPreselected" class="mb-6">
84 <n-select
85 v-model:value="filterType"
frontend/src/components/alerts/AlertsStats.vue
+3 -8
@@ -10,7 +10,7 @@
10 <AlertsStatsItem
11 v-for="summary of countByHost"
12 :key="summary.agent_name"
13 - :summary="summary"
13 + :summary
14 class="mb-2"
15 />
16 </template>
@@ -30,12 +30,7 @@
30
31 <div class="list">
32 <template v-if="countByRule.length">
33 - <AlertsStatsItem
34 - v-for="summary of countByRule"
35 - :key="summary.rule"
36 - :summary="summary"
37 - class="mb-2"
38 - />
33 + <AlertsStatsItem v-for="summary of countByRule" :key="summary.rule" :summary class="mb-2" />
34 </template>
35 <template v-else>
36 <n-empty
@@ -56,7 +51,7 @@
51 <AlertsStatsItem
52 v-for="summary of countByRuleHost"
53 :key="summary.agent_name + summary.rule"
59 - :summary="summary"
54 + :summary
55 class="mb-2"
56 />
57 </template>
frontend/src/components/alerts/AlertsSummary.vue
+1 -1
@@ -31,7 +31,7 @@
31 <div class="alert-list" :class="{ expand: showAllAlerts }">
32 <n-scrollbar class="list-scroll" trigger="none">
33 <div class="flex flex-col gap-2">
34 - <Alert v-for="alert of alertsSummary.alerts" :key="alert._id" :alert="alert" embedded />
34 + <Alert v-for="alert of alertsSummary.alerts" :key="alert._id" :alert embedded />
35 </div>
36 </n-scrollbar>
37
frontend/src/components/artifacts/ArtifactsCollect.vue
+6 -4
@@ -74,7 +74,7 @@
74 size="small"
75 type="primary"
76 secondary
77 - :loading="loading"
77 + :loading
78 :disabled="!areFiltersValid"
79 @click="getData()"
80 >
@@ -176,7 +176,7 @@
176 v-for="collect of collectList"
177 :key="`${collect.___id}`"
178 embedded
179 - :collect="collect"
179 + :collect
180 class="item-appear item-appear-bottom item-appear-005"
181 />
182 </template>
@@ -189,7 +189,7 @@
189 </template>
190
191 <script setup lang="ts">
192 -// TODO: refactor
192 +// TODO-FE: refactor
193 import type { ArtifactsQuery, CollectRequest } from "@/api/endpoints/artifacts"
194 import type { Agent } from "@/types/agents.d"
195 import type { Artifact, ArtifactParameter, CollectResult } from "@/types/artifacts.d"
@@ -269,12 +269,14 @@ const artifactsOptions = computed(() => {
269 return (artifactsList.value || []).map(o => ({ value: o.name, label: o.name }))
270 })
271
272 +const WINDOWS_PATH_BACKSLASH_REGEX = /\\/g
273 +
274 // Function to escape backslashes in Windows paths
275 function escapeBackslashes(value: string): string {
276 // Only escape if it looks like a Windows path (contains backslashes)
277 if (value && typeof value === "string" && value.includes("\\")) {
278 // Replace single backslashes with double backslashes
277 - return value.replace(/\\/g, "\\\\")
279 + return value.replace(WINDOWS_PATH_BACKSLASH_REGEX, "\\\\")
280 }
281 return value
282 }
frontend/src/components/artifacts/ArtifactsCommand.vue
+2 -2
@@ -85,7 +85,7 @@
85 size="small"
86 type="primary"
87 secondary
88 - :loading="loading"
88 + :loading
89 :disabled="!areFiltersValid"
90 @click="getData()"
91 >
@@ -100,7 +100,7 @@
100 <CommandItem
101 v-for="command of commandList"
102 :key="command.Stdout"
103 - :command="command"
103 + :command
104 class="item-appear item-appear-bottom item-appear-005"
105 />
106 </template>
frontend/src/components/artifacts/ArtifactsList.vue
+6 -6
@@ -23,9 +23,9 @@
23 <n-pagination
24 v-model:page="currentPage"
25 v-model:page-size="pageSize"
26 - :page-slot="pageSlot"
27 - :show-size-picker="showSizePicker"
28 - :page-sizes="pageSizes"
26 + :page-slot
27 + :show-size-picker
28 + :page-sizes
29 :item-count="totalArtifacts"
30 :simple="simpleMode"
31 />
@@ -103,7 +103,7 @@
103 <ArtifactItem
104 v-for="artifact of itemsPaginated"
105 :key="artifact.name"
106 - :artifact="artifact"
106 + :artifact
107 class="item-appear item-appear-bottom item-appear-005"
108 />
109 </template>
@@ -116,7 +116,7 @@
116 <n-pagination
117 v-if="itemsPaginated.length > 3"
118 v-model:page="currentPage"
119 - :page-size="pageSize"
119 + :page-size
120 :item-count="totalArtifacts"
121 :page-slot="6"
122 />
@@ -125,7 +125,7 @@
125 </template>
126
127 <script setup lang="ts">
128 -// TODO: refactor
128 +// TODO-FE: refactor
129 import type { ArtifactsQuery } from "@/api/endpoints/artifacts"
130 import type { Agent } from "@/types/agents.d"
131 import type { Artifact } from "@/types/artifacts.d"
frontend/src/components/artifacts/ArtifactsQuarantine.vue
+3 -3
@@ -49,7 +49,7 @@
49 size="small"
50 type="primary"
51 secondary
52 - :loading="loading"
52 + :loading
53 :disabled="!areFiltersValid"
54 @click="getData()"
55 >
@@ -65,7 +65,7 @@
65 <QuarantineItem
66 v-for="quarantine of quarantineList"
67 :key="quarantine.Result + quarantine.Time"
68 - :quarantine="quarantine"
68 + :quarantine
69 class="item-appear item-appear-bottom item-appear-005"
70 />
71 </template>
@@ -78,7 +78,7 @@
78 </template>
79
80 <script setup lang="ts">
81 -// TODO: refactor
81 +// TODO-FE: refactor
82 import type { QuarantineRequest } from "@/api/endpoints/artifacts"
83 import type { Agent } from "@/types/agents.d"
84 import type { Artifact, QuarantineResult } from "@/types/artifacts.d"
frontend/src/components/auth/SignUp.vue
+2 -2
@@ -139,7 +139,7 @@
139 <n-button
140 type="success"
141 size="large"
142 - :loading="loading"
142 + :loading
143 :disabled="!accountStepValid || !detailsStepValid"
144 @click="signUp"
145 >
@@ -158,7 +158,7 @@
158 </template>
159
160 <script lang="ts" setup>
161 -// TODO: refactor
161 +// TODO-FE: refactor
162 import type { FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui"
163 import type { SelectBaseOption } from "naive-ui/es/select/src/interface"
164 import type { RegisterPayload } from "@/types/auth.d"
frontend/src/components/cloudSecurityAssessment/CloudSecurityAssessmentButton.vue
+3 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button :size="size" :type="type" @click="gotoPage()">
2 + <n-button :size :type :secondary @click="gotoPage()">
3 <template #icon>
4 <Icon :name="CloudIcon" />
5 </template>
@@ -13,9 +13,10 @@ import { NButton } from "naive-ui"
13 import { useRouter } from "vue-router"
14 import Icon from "@/components/common/Icon.vue"
15
16 -const { type, size } = defineProps<{
16 +defineProps<{
17 size?: Size
18 type?: Type
19 + secondary?: boolean
20 }>()
21
22 const CloudIcon = "carbon:cloud-data-ops"
frontend/src/components/cloudSecurityAssessment/CreationReportForm.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="creation-report-form">
3 - <n-form ref="baseFormRef" :model="baseForm" :rules="rules">
3 + <n-form ref="baseFormRef" :model="baseForm" :rules>
4 <div class="flex flex-col gap-2">
5 <div class="flex items-start gap-4">
6 <n-form-item label="Type" path="report_type" class="w-32">
frontend/src/components/cloudSecurityAssessment/FormTypes/AwsTypeForm.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="form" :rules="rules">
2 + <n-form ref="formRef" :model="form" :rules>
3 <div class="flex flex-col gap-2">
4 <n-form-item label="Access Key ID" path="access_key_id">
5 <n-input
frontend/src/components/cloudSecurityAssessment/FormTypes/AzureTypeForm.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="form" :rules="rules">
2 + <n-form ref="formRef" :model="form" :rules>
3 <div class="flex flex-col gap-2">
4 <n-form-item label="Username" path="username">
5 <n-input v-model:value.trim="form.username" placeholder="Please insert Username" clearable />
frontend/src/components/cloudSecurityAssessment/FormTypes/GcpTypeForm.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="form" :rules="rules">
2 + <n-form ref="formRef" :model="form" :rules>
3 <div class="flex flex-col gap-2">
4 <n-form-item label="Configuration file" path="file">
5 <n-upload v-model:file-list="fileList" :max="1" accept="application/json, .json, .JSON">
frontend/src/components/common/AccessDenied.vue
+1 -1
@@ -27,7 +27,7 @@
27 </template>
28
29 <script setup lang="ts">
30 -// TODO: refactor
30 +// TODO-FE: refactor
31 import { useRouter } from "vue-router"
32 import Icon from "@/components/common/Icon.vue"
33
frontend/src/components/common/Badge.vue
+11 -5
@@ -2,15 +2,15 @@
2 <component
3 :is="!!href ? 'a' : 'div'"
4 class="badge"
5 - :href="href"
6 - :class="[type, color, { 'cursor-help': hintCursor, cursor: pointCursor, fluid, bright }]"
5 + :href
6 + :class="[type, color, { 'cursor-help': hintCursor, cursor: pointCursor, fluid, bright }, `badge-size-${size}`]"
7 >
8 <span v-if="$slots.label || $slots.iconLeft || $slots.iconRight" class="flex items-center gap-2">
9 <slot name="iconLeft"></slot>
10 <slot name="label"></slot>
11 <slot name="iconRight"></slot>
12 </span>
13 - <span v-if="$slots.value">
13 + <span v-if="$slots.value" class="flex items-center gap-2">
14 <slot name="value"></slot>
15 </span>
16 </component>
@@ -19,8 +19,8 @@
19 <script setup lang="ts">
20 export type BadgeColor = "danger" | "warning" | "success" | "primary"
21
22 -// TODO: refactor
23 -const { type, hintCursor, pointCursor, color, href, fluid } = defineProps<{
22 +// TODO-FE: refactor
23 +const { type, hintCursor, pointCursor, color, href, fluid, size } = defineProps<{
24 type?: "splitted" | "muted" | "active" | "cursor"
25 hintCursor?: boolean
26 pointCursor?: boolean
@@ -28,6 +28,7 @@ const { type, hintCursor, pointCursor, color, href, fluid } = defineProps<{
28 bright?: boolean
29 color?: BadgeColor
30 href?: string
31 + size?: "small" | "medium"
32 }>()
33 </script>
34
@@ -176,5 +177,10 @@ const { type, hintCursor, pointCursor, color, href, fluid } = defineProps<{
177 }
178 }
179 }
180 +
181 + &.badge-size-small {
182 + font-size: 12px;
183 + height: 20px;
184 + }
185 }
186 </style>
frontend/src/components/common/ImageCropper.vue
+4 -4
@@ -1,6 +1,6 @@
1 <template>
2 <div class="image-cropper">
3 - <slot :open-cropper="openCropper" />
3 + <slot :open-cropper />
4
5 <n-modal v-model:show="showCropper">
6 <n-card class="image-cropper-modal flex flex-col" content-class="p-5!">
@@ -17,9 +17,9 @@
17 ref="cropper"
18 class="cropper aspect-square"
19 :src="img"
20 - :stencil-size="stencilSize"
21 - :stencil-props="stencilProps"
22 - :resize-image="resizeImage"
20 + :stencil-size
21 + :stencil-props
22 + :resize-image
23 image-restriction="stencil"
24 :stencil-component="stencil"
25 />
frontend/src/components/common/ListPercentage.vue
+3 -3
@@ -12,16 +12,16 @@
12 </div>
13 <div
14 v-for="item of list"
15 - :key="item[labelKey as keyof typeof item]"
15 + :key="item?.[labelKey as keyof typeof item] ?? ''"
16 class="flex items-center justify-between gap-4"
17 >
18 <div class="basis-2/3 truncate font-mono">
19 - {{ item[labelKey as keyof typeof item] }}
19 + {{ item?.[labelKey as keyof typeof item] ?? "" }}
20 </div>
21 <div class="grow">
22 <n-progress
23 type="line"
24 - :percentage="parseInt(item[percentageKey as keyof typeof item], 10)"
24 + :percentage="parseInt(String(item?.[percentageKey as keyof typeof item] ?? 0), 10)"
25 indicator-placement="inside"
26 :indicator-text-color="style['bg-default-color']"
27 :color="style['fg-default-color']"
frontend/src/components/common/LocaleSelect.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <div class="max-w-60">
3 - <n-select v-model:value="currentLocale" :options="list" :render-label="renderLabel" />
3 + <n-select v-model:value="currentLocale" :options="list" :render-label />
4 </div>
5 </template>
6
frontend/src/components/common/PaginationIndeterminate.vue
+3 -10
@@ -1,13 +1,6 @@
1 <template>
2 <div class="pagination-indeterminate flex items-center gap-2">
3 - <n-input-number
4 - v-model:value="page"
5 - size="small"
6 - :min="1"
7 - button-placement="both"
8 - class="page"
9 - :disabled="disabled"
10 - >
3 + <n-input-number v-model:value="page" size="small" :min="1" button-placement="both" class="page" :disabled>
4 <template #minus-icon>
5 <Icon :name="ArrowBackIcon" />
6 </template>
@@ -22,7 +15,7 @@
15 :options="pageSizesOptions"
16 :show-checkmark="false"
17 class="page-sizes"
25 - :disabled="disabled"
18 + :disabled
19 />
20 <n-select
21 v-if="showSort"
@@ -31,7 +24,7 @@
24 :options="sortOptions"
25 :show-checkmark="false"
26 class="sort"
34 - :disabled="disabled"
27 + :disabled
28 />
29 </div>
30 </template>
frontend/src/components/common/PlatformBadge.vue new
+21
@@ -0,0 +1,21 @@
1 +<template>
2 + <Badge>
3 + <template #value>
4 + <div class="flex items-center gap-2">
5 + <PlatformIcon :platform :size="14" />
6 + <span class="whitespace-nowrap">{{ platformName }}</span>
7 + </div>
8 + </template>
9 + </Badge>
10 +</template>
11 +
12 +<script setup lang="ts">
13 +import { computed } from "vue"
14 +import Badge from "@/components/common/Badge.vue"
15 +import PlatformIcon from "@/components/common/PlatformIcon.vue"
16 +import { getOS } from "@/utils"
17 +
18 +const { platform } = defineProps<{ platform: string }>()
19 +
20 +const platformName = computed(() => getOS(platform))
21 +</script>
frontend/src/components/common/PlatformIcon.vue new
+13
@@ -0,0 +1,13 @@
1 +<template>
2 + <Icon :name="platformIcon" :size />
3 +</template>
4 +
5 +<script setup lang="ts">
6 +import { computed } from "vue"
7 +import Icon from "@/components/common/Icon.vue"
8 +import { getOS, iconFromOs } from "@/utils"
9 +
10 +const { platform, size = 14 } = defineProps<{ platform: string; size?: number }>()
11 +
12 +const platformIcon = computed(() => iconFromOs(getOS(platform)))
13 +</script>
frontend/src/components/common/PropsList.vue new
+47
@@ -0,0 +1,47 @@
1 +<template>
2 + <n-card size="small" content-class="divide-border flex flex-col divide-y px-3.5! py-1!" :embedded :title :segmented>
3 + <div v-for="(value, key) of list" :key class="group/prop-item flex items-center justify-between gap-4 py-2">
4 + <div class="text-secondary text-sm whitespace-nowrap">{{ key }}</div>
5 + <div class="group-hover/prop-item:text-primary text-right font-mono text-sm">
6 + {{ stringFormat(key, value) }}
7 + </div>
8 + </div>
9 + </n-card>
10 +</template>
11 +
12 +<script setup lang="ts">
13 +import { NCard } from "naive-ui"
14 +import { useSettingsStore } from "@/stores/settings"
15 +import { isDate } from "@/utils"
16 +import { formatDate } from "@/utils/format"
17 +
18 +export type TransformerValue = string | Date | number | null | object
19 +export type Transformer = (val?: TransformerValue) => TransformerValue
20 +
21 +const { list, embedded, dateAutodetect, transformer, title, segmented } = defineProps<{
22 + list: object
23 + embedded?: boolean
24 + dateAutodetect?: boolean
25 + transformer?: Record<"all" | string, Transformer>
26 + title?: string
27 + segmented?: boolean
28 +}>()
29 +
30 +const dFormats = useSettingsStore().dateFormat
31 +
32 +function stringFormat(key: string, val?: string | Date | number | null) {
33 + if (transformer?.[key]) {
34 + return transformer[key](val)
35 + }
36 +
37 + if (dateAutodetect && isDate(val)) {
38 + return formatDate(val as string, dFormats.datetimesec)
39 + }
40 +
41 + if (transformer?.all) {
42 + return transformer.all(val)
43 + }
44 +
45 + return JSON.stringify(val)
46 +}
47 +</script>
frontend/src/components/common/SearchDialog.vue
+3 -3
@@ -36,7 +36,7 @@
36 <Highlighter
37 highlight-class-name="highlight"
38 :search-words="keywords"
39 - :auto-escape="true"
39 + auto-escape
40 :text-to-highlight="item.title"
41 />
42 </div>
@@ -118,7 +118,7 @@ const commandIcon = ref("⌘")
118 const scrollContent = ref<(ScrollbarInst & { $el: HTMLElement }) | null>(null)
119 const { routeCustomer, routeSocAlerts, routeAlerts, routeConnectors } = useNavigation()
120
121 -// TODO: review the groups and items
121 +// TODO-FE: review the groups and items
122 const groups = ref<Groups>([
123 {
124 name: "Applications",
@@ -249,7 +249,7 @@ function navigateItem(direction: "next" | "prev") {
249 const nextItem = items[currentIndex + 1]
250 const prevItem = items[currentIndex - 1]
251 const firstItem = items[0]
252 - const lastItem = items[items.length - 1]
252 + const lastItem = items.at(-1)
253
254 if (direction === "next") {
255 activeItem.value = (activeItem.value === null || isAtEnd) && firstItem ? firstItem.key : (nextItem?.key ?? null)
frontend/src/components/common/SegmentedPage.vue
+2 -2
@@ -8,7 +8,7 @@
8 :disabled="!enableResize || splitDisabled"
9 class="wrapper flex grow"
10 :class="[{ 'sidebar-open': sidebarOpen }, `sidebar-position-${sidebarPosition}`]"
11 - :pane1-style="pane1Style"
11 + :pane1-style
12 >
13 <template #[tplNameSide]>
14 <div v-if="sidebarAvailable" ref="sidebar" class="sidebar flex flex-col">
@@ -75,7 +75,7 @@
75 </template>
76
77 <script setup lang="ts">
78 -// TODO: refactor
78 +// TODO-FE: refactor
79 import type { SetupContext } from "vue"
80 import { onClickOutside, useWindowSize } from "@vueuse/core"
81 import { NButton, NScrollbar, NSplit } from "naive-ui"
frontend/src/components/common/VersionUpdateBanner.vue
+3 -3
@@ -42,10 +42,10 @@
42 </template>
43
44 <script setup lang="ts">
45 -// TODO: refactor
45 +// TODO-FE: refactor
46 import type { VersionCheckResponse } from "@/types/version.d"
47 import { NAlert, NButton, NCollapseTransition, NDivider } from "naive-ui"
48 -import { computed, onMounted, ref } from "vue"
48 +import { computed, onBeforeMount, ref } from "vue"
49 import Api from "@/api"
50 import Icon from "@/components/common/Icon.vue"
51 import dayjs from "@/utils/dayjs"
@@ -104,7 +104,7 @@ async function checkVersion() {
104 }
105 }
106
107 -onMounted(() => {
107 +onBeforeMount(() => {
108 checkVersion()
109 })
110 </script>
frontend/src/components/common/cards/CardEntity.vue
+6 -3
@@ -17,7 +17,7 @@
17 >
18 <div class="card-entity-wrapper flex flex-col" :class="cardEntityWrapperClass">
19 <div class="main-box flex flex-col" :class="mainBoxClass">
20 - <div v-if="$slots.header" class="header-box">
20 + <div v-if="$slots.header" class="header-box" :class="headerBoxClass">
21 <slot name="header" />
22 </div>
23
@@ -43,13 +43,14 @@
43 <slot name="mainExtra" />
44 </div>
45
46 - <div v-if="$slots.footer" class="footer-box">
46 + <div v-if="$slots.footer" class="footer-box" :class="footerBoxClass">
47 <slot name="footer" />
48 </div>
49
50 <div
51 v-if="!$slots.footer && ($slots.footerMain || $slots.footerExtra)"
52 class="footer-box flex flex-wrap items-start justify-between"
53 + :class="footerBoxClass"
54 >
55 <div>
56 <slot name="footerMain" />
@@ -79,7 +80,8 @@ const {
80 mainBoxClass,
81 headerBoxClass,
82 cardEntityClass,
82 - cardEntityWrapperClass
83 + cardEntityWrapperClass,
84 + footerBoxClass
85 } = defineProps<{
86 size?: "medium" | "small" | "large"
87 status?: "success" | "warning" | "error"
@@ -94,6 +96,7 @@ const {
96 headerBoxClass?: string
97 cardEntityClass?: string
98 cardEntityWrapperClass?: string
99 + footerBoxClass?: string
100 }>()
101 </script>
102
frontend/src/components/connectors/ConfigForm/FormTypes/CredentialsType.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules label-width="120px" label-placement="top">
3 <n-form-item label="Connector URL" path="connector_url">
4 <n-input v-model:value="form.connector_url" required type="text" />
5 </n-form-item>
frontend/src/components/connectors/ConfigForm/FormTypes/FileType.vue
+2 -2
@@ -1,10 +1,10 @@
1 <template>
2 - <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules label-width="120px" label-placement="top">
3 <n-form-item label="File" path="connector_file">
4 <n-upload
5 class="file-upload-wrap"
6 :max="1"
7 - :show-file-list="true"
7 + show-file-list
8 accept=".yaml, .YAML, .yml, .YML"
9 @change="handleChange"
10 @remove="handleChange"
frontend/src/components/connectors/ConfigForm/FormTypes/HostType.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules label-width="120px" label-placement="top">
3 <n-form-item label="Connector URL" path="connector_url">
4 <n-input v-model:value="form.connector_url" required type="text" />
5 </n-form-item>
frontend/src/components/connectors/ConfigForm/FormTypes/TokenType.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-placement="top">
2 + <n-form ref="formRef" :model="form" :rules label-width="120px" label-placement="top">
3 <n-form-item label="Connector URL" path="connector_url">
4 <n-input v-model:value="form.connector_url" required type="text" />
5 </n-form-item>
frontend/src/components/connectors/ConnectorItem.vue
+1 -1
@@ -87,7 +87,7 @@
87 <n-card style="width: 90vw; max-width: 500px">
88 <ConfigForm
89 v-if="showConfigDialog"
90 - :connector="connector"
90 + :connector
91 @loading="loadingConfiguration = $event"
92 @close="closeConfigDialog"
93 />
frontend/src/components/connectors/ConnectorsList.vue
+1 -1
@@ -13,7 +13,7 @@
13 <ConnectorItem
14 v-for="connector of connectorsList"
15 :key="connector.id"
16 - :connector="connector"
16 + :connector
17 class="item-appear item-appear-bottom item-appear-005 mb-2"
18 @verified="getConnectors()"
19 @updated="getConnectors()"
frontend/src/components/copilotAction/ActionCard.vue
+2 -2
@@ -64,7 +64,7 @@
64 :bordered="false"
65 segmented
66 >
67 - <ActionCardContent :action="action" />
67 + <ActionCardContent :action />
68 </n-modal>
69
70 <!-- Invoke Action Modal -->
@@ -84,7 +84,7 @@
84 <span class="px-1 text-base">{{ action.copilot_action_name }}</span>
85 </div>
86 </template>
87 - <InvokeActionForm :action="action" @success="handleInvokeSuccess" @close="showInvokeModal = false" />
87 + <InvokeActionForm :action @success="handleInvokeSuccess" @close="showInvokeModal = false" />
88 </n-modal>
89 </div>
90 </template>
frontend/src/components/copilotAction/InvokeActionForm.vue
+3 -3
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="formValue" :rules="rules" label-placement="left" label-width="auto">
2 + <n-form ref="formRef" :model="formValue" :rules label-placement="left" label-width="auto">
3 <!-- Technology Info Alert -->
4 <n-alert v-if="action?.technology" type="info" class="mb-4" size="small">
5 <template #header>
@@ -102,7 +102,7 @@
102
103 <div class="flex justify-end gap-2">
104 <n-button @click="emit('close')">Cancel</n-button>
105 - <n-button type="primary" :loading="loading" :disabled="!isFormValid" @click="handleInvoke">
105 + <n-button type="primary" :loading :disabled="!isFormValid" @click="handleInvoke">
106 <template #icon>
107 <Icon :name="PlayIcon" />
108 </template>
@@ -113,7 +113,7 @@
113 </template>
114
115 <script setup lang="ts">
116 -// TODO: refactor
116 +// TODO-FE: refactor
117 import type { FormInst, FormRules } from "naive-ui"
118 import type { Agent } from "@/types/agents.d"
119 import type { CopilotAction } from "@/types/copilotAction.d"
frontend/src/components/copilotSearches/AlertAssetSearches.vue new
+156
@@ -0,0 +1,156 @@
1 +<template>
2 + <div class="flex flex-col gap-4">
3 + <!-- Search Filters -->
4 + <div class="flex items-center gap-2">
5 + <n-input v-model:value="searchQuery" size="small" placeholder="Search rules..." class="grow" clearable>
6 + <template #prefix>
7 + <Icon :name="SearchIcon" />
8 + </template>
9 + </n-input>
10 +
11 + <n-select
12 + v-model:value="selectedPlatform"
13 + :options="platformOptions"
14 + size="small"
15 + placeholder="All Platforms"
16 + class="max-w-35"
17 + clearable
18 + :consistent-menu-width="false"
19 + />
20 + </div>
21 +
22 + <n-card content-class="p-0!">
23 + <!-- Rules List -->
24 + <n-spin :show="loadingRules" class="min-h-50">
25 + <n-scrollbar
26 + v-if="filteredRules.length"
27 + class="max-h-90 p-3!"
28 + trigger="none"
29 + :theme-overrides="{
30 + railInsetVerticalRight: `4px 4px 4px auto`
31 + }"
32 + >
33 + <div class="flex flex-col gap-3">
34 + <CardEntity
35 + v-for="rule in filteredRules"
36 + :key="rule.id"
37 + size="small"
38 + hoverable
39 + embedded
40 + clickable
41 + :highlighted="selectedRule?.id === rule.id"
42 + @click="selectRule(rule)"
43 + >
44 + <template #headerMain>
45 + <PlatformBadge :platform="rule.platform" class="text-default" />
46 + </template>
47 + <template #headerExtra>
48 + <SeverityBadge :severity="rule.severity" />
49 + </template>
50 + <template #mainExtra>
51 + <div class="flex flex-col gap-1">
52 + <span class="font-medium">{{ rule.name }}</span>
53 + <span class="line-clamp-2 text-sm opacity-70">{{ rule.description }}</span>
54 + </div>
55 + </template>
56 + </CardEntity>
57 + </div>
58 + </n-scrollbar>
59 +
60 + <n-empty v-else-if="!loadingRules" description="No rules found" class="py-20" />
61 + </n-spin>
62 + </n-card>
63 +
64 + <n-modal
65 + :show="!!selectedRule"
66 + :mask-closable="false"
67 + :title="selectedRule?.name"
68 + segmented
69 + preset="card"
70 + :style="{ maxWidth: 'min(550px, 90vw)', minHeight: 'min(300px, 90vh)', overflow: 'hidden' }"
71 + @close="selectedRule = null"
72 + >
73 + <ExecuteSearchForm v-if="selectedRule" :rule-summary="selectedRule" :asset />
74 + </n-modal>
75 + </div>
76 +</template>
77 +
78 +<script setup lang="ts">
79 +import type { PlatformFilter, RuleSummary } from "@/types/copilotSearches.d"
80 +import type { AlertAsset } from "@/types/incidentManagement/alerts.d"
81 +import { NCard, NEmpty, NInput, NModal, NScrollbar, NSelect, NSpin, useMessage } from "naive-ui"
82 +import { computed, onBeforeMount, ref, watch } from "vue"
83 +import Api from "@/api"
84 +import CardEntity from "@/components/common/cards/CardEntity.vue"
85 +import Icon from "@/components/common/Icon.vue"
86 +import PlatformBadge from "@/components/common/PlatformBadge.vue"
87 +import ExecuteSearchForm from "./ExecuteSearchForm.vue"
88 +import SeverityBadge from "./SeverityBadge.vue"
89 +
90 +const { asset } = defineProps<{
91 + asset: AlertAsset
92 +}>()
93 +
94 +const message = useMessage()
95 +const SearchIcon = "carbon:search"
96 +
97 +// State
98 +const loadingRules = ref(false)
99 +const rules = ref<RuleSummary[]>([])
100 +const selectedRule = ref<RuleSummary | null>(null)
101 +
102 +// Filters
103 +const selectedPlatform = ref<PlatformFilter | null>(null)
104 +const searchQuery = ref<string | null>(null)
105 +
106 +const platformOptions = [
107 + { label: "Linux", value: "linux" },
108 + { label: "Windows", value: "windows" }
109 +]
110 +
111 +// Computed
112 +const filteredRules = computed(() => {
113 + let result = rules.value
114 +
115 + if (selectedPlatform.value) {
116 + result = result.filter(r => r.platform === selectedPlatform.value)
117 + }
118 +
119 + if (searchQuery.value) {
120 + const query = searchQuery.value.toLowerCase()
121 + result = result.filter(r => r.name.toLowerCase().includes(query) || r.description.toLowerCase().includes(query))
122 + }
123 +
124 + return result
125 +})
126 +
127 +// Methods
128 +async function loadRules() {
129 + loadingRules.value = true
130 +
131 + try {
132 + const res = await Api.copilotSearches.getRules({ limit: 100 })
133 + if (res.data.success) {
134 + rules.value = res.data.rules
135 + }
136 + } catch (err: any) {
137 + message.error(err.response?.data?.message || "Failed to load rules")
138 + } finally {
139 + loadingRules.value = false
140 + }
141 +}
142 +
143 +function selectRule(rule: RuleSummary) {
144 + selectedRule.value = rule
145 +}
146 +
147 +// Lifecycle
148 +onBeforeMount(() => {
149 + loadRules()
150 +})
151 +
152 +// Watch for platform changes to reload
153 +watch(selectedPlatform, () => {
154 + selectedRule.value = null
155 +})
156 +</script>
frontend/src/components/copilotSearches/ExecuteSearchForm.vue new
+309
@@ -0,0 +1,309 @@
1 +<template>
2 + <n-spin :show="loadingRule">
3 + <div v-if="rule" class="flex flex-col gap-8">
4 + <RuleHeader v-if="showHeader" :rule-detail="rule" />
5 +
6 + <n-form
7 + ref="formRef"
8 + :model="formValue"
9 + :rules
10 + label-placement="left"
11 + label-width="auto"
12 + :disabled="executing"
13 + >
14 + <!-- Index Pattern -->
15 + <n-form-item label="Index Pattern" path="index_pattern" required>
16 + <n-input
17 + v-model:value="formValue.index_pattern"
18 + placeholder="e.g., wazuh-alerts-*"
19 + clearable
20 + :disabled="executing"
21 + />
22 + </n-form-item>
23 +
24 + <!-- Result Size -->
25 + <n-form-item label="Result Size" path="size">
26 + <n-input-number
27 + v-model:value="formValue.size"
28 + :min="1"
29 + :max="10000"
30 + placeholder="Number of results"
31 + class="w-full"
32 + clearable
33 + :disabled="executing"
34 + />
35 + </n-form-item>
36 +
37 + <!-- Parameters Section -->
38 + <div v-if="rule.parameters?.length">
39 + <n-divider>Search Parameters</n-divider>
40 +
41 + <n-form-item
42 + v-for="param in rule.parameters"
43 + :key="param.name"
44 + :label="param.name"
45 + :required="param.required"
46 + >
47 + <div class="flex w-full flex-col gap-2">
48 + <n-input-number
49 + v-if="['integer', 'int', 'long', 'number', 'numeric'].includes(param.type)"
50 + v-model:value="formValue.parameters[param.name] as number"
51 + class="w-full"
52 + :placeholder="param.default?.toString()"
53 + clearable
54 + />
55 + <n-switch
56 + v-if="['boolean', 'bool'].includes(param.type)"
57 + v-model:value="formValue.parameters[param.name] as boolean"
58 + />
59 + <n-input
60 + v-else
61 + v-model:value="formValue.parameters[param.name] as string"
62 + :placeholder="param.example?.toString() || param.default?.toString()"
63 + clearable
64 + />
65 + <div v-if="param.description" class="text-secondary text-xs">
66 + {{ param.description }}
67 + </div>
68 + </div>
69 + </n-form-item>
70 + </div>
71 +
72 + <div class="flex items-center justify-between">
73 + <div class="flex flex-wrap gap-2">
74 + <Badge v-for="mitre of ruleSummary?.mitre_attack_id?.slice(0, 3)" :key="mitre" size="small">
75 + <template #value>{{ mitre }}</template>
76 + </Badge>
77 + </div>
78 + <n-button type="primary" :loading="executing" :disabled="!isFormValid" @click="executeSearch">
79 + <template #icon>
80 + <Icon :name="PlayIcon" />
81 + </template>
82 + Execute Search
83 + </n-button>
84 + </div>
85 + </n-form>
86 +
87 + <!-- Search Results -->
88 + <n-collapse-transition :show="!!searchResults">
89 + <n-card v-if="searchResults" title="Search Results" size="small" content-class="flex flex-col gap-6">
90 + <div class="flex flex-wrap items-center gap-3">
91 + <Badge color="primary" type="splitted">
92 + <template #label>Total Hits</template>
93 + <template #value>{{ searchResults.total_hits }}</template>
94 + </Badge>
95 + <Badge type="splitted">
96 + <template #label>Returned</template>
97 + <template #value>{{ searchResults.returned_hits }}</template>
98 + </Badge>
99 + <Badge type="splitted">
100 + <template #label>Time</template>
101 + <template #value>{{ searchResults.took_ms }}ms</template>
102 + </Badge>
103 + </div>
104 +
105 + <n-collapse v-if="searchResults.hits.length">
106 + <n-collapse-item title="Results" name="results">
107 + <n-scrollbar
108 + class="max-h-64"
109 + trigger="none"
110 + :theme-overrides="{
111 + railInsetVerticalRight: `0px 0px 0px auto`
112 + }"
113 + >
114 + <div class="flex flex-col gap-2">
115 + <div
116 + v-for="hit in searchResults.hits"
117 + :key="hit.id"
118 + class="bg-secondary-color rounded-lg p-3"
119 + >
120 + <div class="mb-2 flex flex-wrap gap-2">
121 + <Badge type="splitted" size="small">
122 + <template #label>Index</template>
123 + <template #value>{{ hit.index }}</template>
124 + </Badge>
125 + <Badge type="splitted" size="small">
126 + <template #label>ID</template>
127 + <template #value>{{ hit.id }}</template>
128 + </Badge>
129 + </div>
130 + <CodeSource :code="hit.source" lang="json" />
131 + </div>
132 + </div>
133 + </n-scrollbar>
134 + </n-collapse-item>
135 +
136 + <n-collapse-item title="Query Executed" name="query">
137 + <CodeSource :code="searchResults.query_executed" lang="json" />
138 + </n-collapse-item>
139 + </n-collapse>
140 + </n-card>
141 + <n-empty v-else description="No results found" class="py-4" />
142 + </n-collapse-transition>
143 + </div>
144 + </n-spin>
145 +</template>
146 +
147 +<script setup lang="ts">
148 +import type { FormInst, FormRules } from "naive-ui"
149 +import type { ExecuteSearchResponse, RuleDetail, RuleSummary } from "@/types/copilotSearches.d"
150 +import type { AlertAsset } from "@/types/incidentManagement/alerts"
151 +import {
152 + NButton,
153 + NCard,
154 + NCollapse,
155 + NCollapseItem,
156 + NCollapseTransition,
157 + NDivider,
158 + NEmpty,
159 + NForm,
160 + NFormItem,
161 + NInput,
162 + NInputNumber,
163 + NScrollbar,
164 + NSpin,
165 + NSwitch,
166 + useMessage
167 +} from "naive-ui"
168 +import { computed, onBeforeMount, ref } from "vue"
169 +import Api from "@/api"
170 +import Badge from "@/components/common/Badge.vue"
171 +import CodeSource from "@/components/common/CodeSource.vue"
172 +import Icon from "@/components/common/Icon.vue"
173 +import RuleHeader from "./RuleHeader.vue"
174 +
175 +const props = defineProps<{
176 + ruleId?: string
177 + ruleSummary?: RuleSummary
178 + ruleDetail?: RuleDetail
179 + asset?: AlertAsset
180 + showHeader?: boolean
181 +}>()
182 +
183 +const PlayIcon = "carbon:play"
184 +
185 +const message = useMessage()
186 +const formRef = ref<FormInst | null>(null)
187 +const loadingRule = ref(false)
188 +const executing = ref(false)
189 +const rule = ref<RuleDetail | null>(null)
190 +const searchResults = ref<ExecuteSearchResponse | null>(null)
191 +
192 +const formValue = ref<{
193 + index_pattern: string
194 + size: number
195 + parameters: Record<string, string | number | boolean>
196 +}>({
197 + index_pattern: props.asset?.index_name || "wazuh-alerts-*",
198 + size: 20,
199 + parameters: {}
200 +})
201 +
202 +const rules: FormRules = {
203 + index_pattern: {
204 + required: true,
205 + message: "Index pattern is required",
206 + trigger: ["blur", "change"]
207 + }
208 +}
209 +
210 +const isFormValid = computed(() => {
211 + if (!formValue.value.index_pattern) return false
212 + if (!rule.value) return false
213 +
214 + // Check required parameters
215 + if (rule.value?.parameters?.length) {
216 + for (const param of rule.value.parameters) {
217 + if (param.required) {
218 + const value = formValue.value.parameters[param.name]
219 + if (value === undefined || value === null || value === "") {
220 + return false
221 + }
222 + }
223 + }
224 + }
225 +
226 + return true
227 +})
228 +
229 +function prefillParameters() {
230 + // Initialize parameters with defaults
231 + if (rule.value?.parameters?.length) {
232 + rule.value.parameters.forEach(param => {
233 + if (param.default !== undefined && param.default !== null) {
234 + formValue.value.parameters[param.name] = param.default
235 + }
236 + })
237 + }
238 +
239 + // Set default size from rule if available
240 + if (rule.value?.search?.size) {
241 + formValue.value.size = rule.value.search.size as number
242 + }
243 +}
244 +
245 +async function loadRule(ruleId: string) {
246 + loadingRule.value = true
247 +
248 + try {
249 + const res = await Api.copilotSearches.getRuleById(ruleId)
250 + if (res.data.success) {
251 + rule.value = res.data.rule
252 +
253 + prefillParameters()
254 + } else {
255 + message.error(res.data?.message || "Failed to load rule details")
256 + }
257 + } catch (err: any) {
258 + message.error(err.response?.data?.message || "Failed to load rule details")
259 + } finally {
260 + loadingRule.value = false
261 + }
262 +}
263 +
264 +async function executeSearch() {
265 + if (!formRef.value || !rule.value) return
266 +
267 + try {
268 + await formRef.value.validate()
269 + } catch {
270 + return
271 + }
272 +
273 + executing.value = true
274 + searchResults.value = null
275 +
276 + try {
277 + const res = await Api.copilotSearches.executeSearch({
278 + rule_id: rule.value.id,
279 + index_pattern: formValue.value.index_pattern,
280 + parameters: formValue.value.parameters,
281 + size: formValue.value.size
282 + })
283 +
284 + if (res.data.success) {
285 + searchResults.value = res.data
286 + message.success(`Search completed: ${res.data.total_hits} hits found`)
287 + } else {
288 + message.warning(res.data?.message || "Search failed")
289 + }
290 + } catch (err: any) {
291 + message.error(err.response?.data?.message || "Search execution failed")
292 + } finally {
293 + executing.value = false
294 + }
295 +}
296 +
297 +onBeforeMount(() => {
298 + if (props.ruleDetail) {
299 + rule.value = props.ruleDetail
300 + prefillParameters()
301 + } else if (props.ruleSummary) {
302 + loadRule(props.ruleSummary?.id)
303 + } else if (props.ruleId) {
304 + loadRule(props.ruleId)
305 + } else {
306 + message.error("No rule data or rule ID provided")
307 + }
308 +})
309 +</script>
frontend/src/components/copilotSearches/List.vue new
+291
@@ -0,0 +1,291 @@
1 +<template>
2 + <div class="@container flex flex-col gap-4">
3 + <n-alert type="info">
4 + CoPilot Searches provides pre-built detection queries for threat hunting in your Wazuh indexer. See
5 + <a href="https://github.com/socfortress/CoPilot-Search-Queries" target="_blank">
6 + https://github.com/socfortress/CoPilot-Search-Queries
7 + </a>
8 + for details.
9 + </n-alert>
10 +
11 + <div class="flex flex-col">
12 + <div class="flex flex-wrap items-center justify-end gap-2">
13 + <div class="flex min-w-80 grow gap-2">
14 + <n-popover overlap placement="bottom-start">
15 + <template #trigger>
16 + <div class="bg-default rounded-lg">
17 + <n-button size="small" class="cursor-help!">
18 + <template #icon>
19 + <Icon :name="InfoIcon" />
20 + </template>
21 + </n-button>
22 + </div>
23 + </template>
24 + <div class="flex flex-col gap-2">
25 + <div class="box">
26 + Total Rules:
27 + <code>{{ pagination.total }}</code>
28 + </div>
29 + <div class="box">
30 + Filtered:
31 + <code>{{ pagination.filtered }}</code>
32 + </div>
33 + </div>
34 + </n-popover>
35 +
36 + <n-input
37 + v-model:value="searchQuery"
38 + size="small"
39 + placeholder="Search rules..."
40 + class="max-w-120"
41 + clearable
42 + >
43 + <template #prefix>
44 + <Icon :name="SearchIcon" />
45 + </template>
46 + </n-input>
47 +
48 + <n-popover :show="showFilters" trigger="manual" overlap placement="right" class="px-0!">
49 + <template #trigger>
50 + <div class="bg-default rounded-lg">
51 + <n-badge :show="filtered" dot type="success" :offset="[-4, 0]">
52 + <n-button size="small" @click="showFilters = true">
53 + <template #icon>
54 + <Icon :name="FilterIcon" />
55 + </template>
56 + </n-button>
57 + </n-badge>
58 + </div>
59 + </template>
60 + <div class="divide-border flex w-50 flex-col gap-0 divide-y">
61 + <div class="flex flex-col gap-2.5 px-3 pt-1 pb-3">
62 + <n-select
63 + v-model:value="selectedPlatform"
64 + :options="platformOptions"
65 + size="small"
66 + placeholder="Platform"
67 + class="w-full"
68 + clearable
69 + :consistent-menu-width="false"
70 + />
71 +
72 + <n-select
73 + v-model:value="selectedSeverity"
74 + :options="severityOptions"
75 + clearable
76 + size="small"
77 + placeholder="Severity"
78 + class="w-full"
79 + :consistent-menu-width="false"
80 + />
81 +
82 + <n-select
83 + v-model:value="selectedStatus"
84 + :options="statusOptions"
85 + clearable
86 + size="small"
87 + placeholder="Status"
88 + class="w-full"
89 + :consistent-menu-width="false"
90 + />
91 +
92 + <n-checkbox v-model:checked="hasGraylogFilter" size="small">
93 + <span class="text-xs">Graylog Only</span>
94 + </n-checkbox>
95 + </div>
96 + <div class="flex justify-between gap-2 px-3 pt-2">
97 + <div class="flex justify-start gap-2">
98 + <n-button size="small" quaternary @click="showFilters = false">Close</n-button>
99 + </div>
100 + <div class="flex justify-end gap-2">
101 + <n-button size="small" secondary @click="resetFilters()">Reset</n-button>
102 + </div>
103 + </div>
104 + </div>
105 + </n-popover>
106 + </div>
107 +
108 + <n-button size="small" :loading="refreshing" @click="handleRefresh">
109 + <template #icon>
110 + <Icon :name="RefreshIcon" />
111 + </template>
112 + Refresh Cache
113 + </n-button>
114 +
115 + <n-pagination
116 + v-model:page="pagination.current"
117 + :page-size="pagination.size"
118 + :item-count="pagination.filtered"
119 + :page-slot="5"
120 + />
121 + </div>
122 +
123 + <n-spin :show="loading">
124 + <div class="my-3">
125 + <div
126 + v-if="list.length"
127 + class="grid grid-cols-1 gap-4 @2xl:grid-cols-2 @5xl:grid-cols-3 @6xl:grid-cols-4"
128 + >
129 + <RuleCard v-for="rule of list" :key="rule.id" :rule />
130 + </div>
131 +
132 + <template v-else>
133 + <n-empty v-if="!loading" description="No rules found" class="h-48 justify-center" />
134 + </template>
135 + </div>
136 + </n-spin>
137 +
138 + <div class="flex justify-end">
139 + <n-pagination
140 + v-if="list.length > 3"
141 + v-model:page="pagination.current"
142 + :page-size="pagination.size"
143 + :item-count="pagination.filtered"
144 + :page-slot="6"
145 + />
146 + </div>
147 + </div>
148 + </div>
149 +</template>
150 +
151 +<script setup lang="ts">
152 +import type { PlatformFilter, RuleListQuery, RuleSeverity, RuleStatus, RuleSummary } from "@/types/copilotSearches.d"
153 +import { watchDebounced } from "@vueuse/core"
154 +import axios from "axios"
155 +import {
156 + NAlert,
157 + NBadge,
158 + NButton,
159 + NCheckbox,
160 + NEmpty,
161 + NInput,
162 + NPagination,
163 + NPopover,
164 + NSelect,
165 + NSpin,
166 + useMessage
167 +} from "naive-ui"
168 +import { computed, ref } from "vue"
169 +import Api from "@/api"
170 +import Icon from "@/components/common/Icon.vue"
171 +import RuleCard from "./RuleCard.vue"
172 +
173 +const loading = ref(false)
174 +const refreshing = ref(false)
175 +const message = useMessage()
176 +const list = ref<RuleSummary[]>([])
177 +const pagination = ref({
178 + current: 1,
179 + size: 24,
180 + total: 0,
181 + filtered: 0
182 +})
183 +
184 +const selectedPlatform = ref<PlatformFilter | null>(null)
185 +const selectedSeverity = ref<RuleSeverity | null>(null)
186 +const selectedStatus = ref<RuleStatus | null>(null)
187 +const searchQuery = ref<string | null>(null)
188 +const hasGraylogFilter = ref(false)
189 +const showFilters = ref(false)
190 +
191 +const filtered = computed<boolean>(() => {
192 + return !!selectedPlatform.value || !!selectedSeverity.value || !!selectedStatus.value || !!hasGraylogFilter.value
193 +})
194 +
195 +const InfoIcon = "carbon:information"
196 +const FilterIcon = "carbon:filter-edit"
197 +const SearchIcon = "carbon:search"
198 +const RefreshIcon = "carbon:renew"
199 +
200 +const platformOptions = [
201 + { label: "Linux", value: "linux" },
202 + { label: "Windows", value: "windows" }
203 +]
204 +
205 +const severityOptions = [
206 + { label: "Low", value: "low" },
207 + { label: "Medium", value: "medium" },
208 + { label: "High", value: "high" },
209 + { label: "Critical", value: "critical" }
210 +]
211 +
212 +const statusOptions = [
213 + { label: "Production", value: "production" },
214 + { label: "Experimental", value: "experimental" },
215 + { label: "Deprecated", value: "deprecated" }
216 +]
217 +
218 +let abortController: AbortController | null = null
219 +
220 +function resetFilters() {
221 + selectedPlatform.value = null
222 + selectedSeverity.value = null
223 + selectedStatus.value = null
224 + hasGraylogFilter.value = false
225 + showFilters.value = false
226 +}
227 +
228 +function getList() {
229 + abortController?.abort()
230 + abortController = new AbortController()
231 +
232 + loading.value = true
233 +
234 + const query: RuleListQuery = {
235 + skip: (pagination.value.current - 1) * pagination.value.size,
236 + limit: pagination.value.size,
237 + platform: selectedPlatform.value || undefined,
238 + severity: selectedSeverity.value || undefined,
239 + status: selectedStatus.value || undefined,
240 + search: searchQuery.value || undefined,
241 + has_graylog: hasGraylogFilter.value ? true : undefined
242 + }
243 +
244 + Api.copilotSearches
245 + .getRules(query, abortController.signal)
246 + .then(res => {
247 + loading.value = false
248 +
249 + if (res.data.success) {
250 + list.value = res.data?.rules || []
251 + pagination.value.total = res.data?.total || 0
252 + pagination.value.filtered = res.data?.filtered || 0
253 + } else {
254 + message.warning(res.data?.message || "An error occurred. Please try again later.")
255 + }
256 + })
257 + .catch(err => {
258 + if (!axios.isCancel(err)) {
259 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
260 + loading.value = false
261 + }
262 + })
263 +}
264 +
265 +async function handleRefresh() {
266 + refreshing.value = true
267 + try {
268 + const res = await Api.copilotSearches.refreshCache()
269 + if (res.data.success) {
270 + message.success(`Cache refreshed! Loaded ${res.data.rules_loaded} rules.`)
271 + getList()
272 + } else {
273 + message.warning(res.data?.message || "Failed to refresh cache")
274 + }
275 + } catch (err: any) {
276 + message.error(err.response?.data?.message || "Failed to refresh cache")
277 + } finally {
278 + refreshing.value = false
279 + }
280 +}
281 +
282 +watchDebounced(
283 + [selectedPlatform, selectedSeverity, selectedStatus, searchQuery, hasGraylogFilter, () => pagination.value.current],
284 + getList,
285 + {
286 + deep: true,
287 + debounce: 300,
288 + immediate: true
289 + }
290 +)
291 +</script>
frontend/src/components/copilotSearches/ProvisionGraylogForm.vue new
+312
@@ -0,0 +1,312 @@
1 +<template>
2 + <n-spin :show="loadingRule">
3 + <div v-if="rule" class="flex flex-col gap-8">
4 + <!-- Rule Info -->
5 + <RuleHeader :rule-detail="rule" />
6 +
7 + <!-- Graylog Query Preview -->
8 + <div v-if="rule.graylog?.query" class="flex flex-col gap-2">
9 + <div class="text-sm font-semibold">Graylog Query</div>
10 + <CodeSource :code="rule.graylog.query" lang="sql" />
11 + </div>
12 +
13 + <!-- No Graylog Query Warning -->
14 + <n-alert v-else type="warning" title="No Graylog Query">
15 + This rule does not have a Graylog query defined. Only rules with Graylog queries can be provisioned as
16 + Graylog alerts.
17 + </n-alert>
18 +
19 + <!-- Provision Form -->
20 + <n-form
21 + v-if="rule.graylog?.query"
22 + ref="formRef"
23 + :model="formValue"
24 + :rules="formRules"
25 + label-placement="left"
26 + label-width="auto"
27 + :disabled="provisioning"
28 + >
29 + <!-- Custom Title -->
30 + <n-form-item label="Alert Title" path="custom_title">
31 + <div class="flex w-full flex-col gap-2">
32 + <n-input v-model:value="formValue.custom_title" :placeholder="defaultTitle" clearable />
33 + <div class="text-secondary text-xs">
34 + Leave empty to use the default title: "{{ defaultTitle }}"
35 + </div>
36 + </div>
37 + </n-form-item>
38 +
39 + <!-- Search Within -->
40 + <n-form-item label="Search Window" path="search_within_seconds">
41 + <div class="flex w-full flex-col gap-2">
42 + <n-input-number
43 + v-model:value="formValue.search_within_seconds"
44 + :min="60"
45 + :max="86400"
46 + :step="60"
47 + class="w-full"
48 + >
49 + <template #suffix>seconds</template>
50 + </n-input-number>
51 + <div class="text-secondary text-xs">
52 + Time window to search within ({{ formatDuration(formValue.search_within_seconds) }})
53 + </div>
54 + </div>
55 + </n-form-item>
56 +
57 + <!-- Execute Every -->
58 + <n-form-item label="Execute Every" path="execute_every_seconds">
59 + <div class="flex w-full flex-col gap-2">
60 + <n-input-number
61 + v-model:value="formValue.execute_every_seconds"
62 + :min="60"
63 + :max="86400"
64 + :step="60"
65 + class="w-full"
66 + >
67 + <template #suffix>seconds</template>
68 + </n-input-number>
69 + <div class="text-secondary text-xs">
70 + How often to run the search ({{ formatDuration(formValue.execute_every_seconds) }})
71 + </div>
72 + </div>
73 + </n-form-item>
74 +
75 + <!-- Priority -->
76 + <n-form-item label="Priority" path="priority">
77 + <n-select v-model:value="formValue.priority" :options="priorityOptions" class="w-full" />
78 + </n-form-item>
79 +
80 + <!-- Event Limit -->
81 + <n-form-item label="Event Limit" path="event_limit">
82 + <div class="flex w-full flex-col gap-2">
83 + <n-input-number v-model:value="formValue.event_limit" :min="1" :max="10000" class="w-full" />
84 + <div class="text-secondary text-xs">Maximum number of events to process per execution</div>
85 + </div>
86 + </n-form-item>
87 +
88 + <!-- Streams (Optional) -->
89 + <n-form-item label="Streams" path="streams">
90 + <div class="flex w-full flex-col gap-2">
91 + <n-dynamic-tags v-model:value="formValue.streams" />
92 + <div class="text-secondary text-xs">Optional: Limit search to specific Graylog stream IDs</div>
93 + </div>
94 + </n-form-item>
95 + </n-form>
96 +
97 + <!-- Actions -->
98 + <div class="flex justify-end gap-2">
99 + <n-button
100 + type="primary"
101 + :loading="provisioning"
102 + :disabled="!rule.graylog?.query"
103 + @click="handleProvision"
104 + >
105 + <template #icon>
106 + <Icon :name="ProvisionIcon" />
107 + </template>
108 + Provision Alert
109 + </n-button>
110 + </div>
111 + </div>
112 +
113 + <n-empty v-else-if="!loadingRule" description="Failed to load rule details" />
114 + </n-spin>
115 +</template>
116 +
117 +<script setup lang="ts">
118 +import type { FormInst, FormRules } from "naive-ui"
119 +import type { ProvisionGraylogAlertRequest, RuleDetail } from "@/types/copilotSearches.d"
120 +import {
121 + NAlert,
122 + NButton,
123 + NDynamicTags,
124 + NEmpty,
125 + NForm,
126 + NFormItem,
127 + NInput,
128 + NInputNumber,
129 + NSelect,
130 + NSpin,
131 + useMessage
132 +} from "naive-ui"
133 +import { computed, onBeforeMount, ref } from "vue"
134 +import Api from "@/api"
135 +import CodeSource from "@/components/common/CodeSource.vue"
136 +import Icon from "@/components/common/Icon.vue"
137 +import RuleHeader from "./RuleHeader.vue"
138 +
139 +const props = defineProps<{
140 + ruleId?: string
141 + ruleData?: RuleDetail
142 +}>()
143 +
144 +const emit = defineEmits<{
145 + (e: "success"): void
146 + (e: "close"): void
147 +}>()
148 +
149 +const message = useMessage()
150 +const formRef = ref<FormInst | null>(null)
151 +const loadingRule = ref(false)
152 +const provisioning = ref(false)
153 +const rule = ref<RuleDetail | null>(null)
154 +
155 +const ProvisionIcon = "carbon:add-alt"
156 +
157 +const formValue = ref<{
158 + custom_title: string | null
159 + search_within_seconds: number
160 + execute_every_seconds: number
161 + priority: 1 | 2 | 3
162 + event_limit: number
163 + streams: string[]
164 +}>({
165 + custom_title: null,
166 + search_within_seconds: 300,
167 + execute_every_seconds: 300,
168 + priority: 2,
169 + event_limit: 1000,
170 + streams: []
171 +})
172 +
173 +const formRules: FormRules = {
174 + search_within_seconds: {
175 + required: true,
176 + type: "number",
177 + min: 60,
178 + max: 86400,
179 + message: "Must be between 60 and 86400 seconds",
180 + trigger: ["blur", "change"]
181 + },
182 + execute_every_seconds: {
183 + required: true,
184 + type: "number",
185 + min: 60,
186 + max: 86400,
187 + message: "Must be between 60 and 86400 seconds",
188 + trigger: ["blur", "change"]
189 + },
190 + event_limit: {
191 + required: true,
192 + type: "number",
193 + min: 1,
194 + max: 10000,
195 + message: "Must be between 1 and 10000",
196 + trigger: ["blur", "change"]
197 + }
198 +}
199 +
200 +const priorityOptions = [
201 + { label: "Low (1)", value: 1 },
202 + { label: "Normal (2)", value: 2 },
203 + { label: "High (3)", value: 3 }
204 +]
205 +
206 +const SPACE_REGEX = / /g
207 +
208 +const defaultTitle = computed(() => {
209 + if (!rule.value) return ""
210 + return rule.value.name.toUpperCase().replace(SPACE_REGEX, " - ")
211 +})
212 +
213 +function formatDuration(seconds: number): string {
214 + if (seconds < 60) return `${seconds} seconds`
215 + if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`
216 + if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours`
217 + return `${Math.floor(seconds / 86400)} days`
218 +}
219 +
220 +function getPriorityFromSeverity(severity: string): 1 | 2 | 3 {
221 + switch (severity.toLowerCase()) {
222 + case "low":
223 + return 1
224 + case "medium":
225 + return 2
226 + case "high":
227 + case "critical":
228 + return 3
229 + default:
230 + return 2
231 + }
232 +}
233 +
234 +function prefillForm() {
235 + if (rule.value?.response?.severity) {
236 + formValue.value.priority = getPriorityFromSeverity(rule.value.response.severity)
237 + }
238 +}
239 +
240 +async function loadRule(ruleId: string) {
241 + loadingRule.value = true
242 + try {
243 + const res = await Api.copilotSearches.getRuleById(ruleId)
244 + if (res.data.success) {
245 + rule.value = res.data.rule
246 +
247 + // Set default priority based on rule severity
248 + prefillForm()
249 + } else {
250 + message.error(res.data?.message || "Failed to load rule details")
251 + }
252 + } catch (err: any) {
253 + message.error(err.response?.data?.message || "Failed to load rule details")
254 + } finally {
255 + loadingRule.value = false
256 + }
257 +}
258 +
259 +async function handleProvision() {
260 + if (!formRef.value || !rule.value) return
261 +
262 + try {
263 + await formRef.value.validate()
264 + } catch {
265 + return
266 + }
267 +
268 + provisioning.value = true
269 +
270 + try {
271 + const request: ProvisionGraylogAlertRequest = {
272 + rule_id: props.ruleId || rule.value?.id || "",
273 + search_within_seconds: formValue.value.search_within_seconds,
274 + execute_every_seconds: formValue.value.execute_every_seconds,
275 + priority: formValue.value.priority,
276 + event_limit: formValue.value.event_limit
277 + }
278 +
279 + if (formValue.value.custom_title) {
280 + request.custom_title = formValue.value.custom_title
281 + }
282 +
283 + if (formValue.value.streams.length > 0) {
284 + request.streams = formValue.value.streams
285 + }
286 +
287 + const res = await Api.copilotSearches.provisionGraylogAlert(request)
288 +
289 + if (res.data.success) {
290 + message.success(`Graylog alert "${res.data.alert_title}" created successfully!`)
291 + emit("success")
292 + } else {
293 + message.error(res.data?.message || "Failed to provision alert")
294 + }
295 + } catch (err: any) {
296 + message.error(err.response?.data?.message || "Failed to provision Graylog alert")
297 + } finally {
298 + provisioning.value = false
299 + }
300 +}
301 +
302 +onBeforeMount(() => {
303 + if (props.ruleData) {
304 + rule.value = props.ruleData
305 + prefillForm()
306 + } else if (props.ruleId) {
307 + loadRule(props.ruleId)
308 + } else {
309 + message.error("No rule data or rule ID provided")
310 + }
311 +})
312 +</script>
frontend/src/components/copilotSearches/RuleCard.vue new
+180
@@ -0,0 +1,180 @@
1 +<template>
2 + <div class="h-full">
3 + <CardEntity
4 + hoverable
5 + clickable
6 + :embedded
7 + class="@container h-full"
8 + main-box-class="grow"
9 + card-entity-wrapper-class="h-full"
10 + header-box-class="flex-nowrap! items-start"
11 + @click.stop="showDetails = true"
12 + >
13 + <template #headerMain>
14 + <div class="flex flex-wrap items-center gap-2">
15 + <Badge v-if="rule.status" :color="getStatusColor(rule.status)" size="small">
16 + <template #value>{{ rule.status }}</template>
17 + </Badge>
18 +
19 + <Badge v-if="rule.type" type="splitted" size="small">
20 + <template #label>type</template>
21 + <template #value>{{ rule.type }}</template>
22 + </Badge>
23 +
24 + <div v-if="rule.mitre_attack_id?.length" class="flex items-center gap-1">
25 + <Badge
26 + v-for="mitre of rule.mitre_attack_id.slice(0, 2)"
27 + :key="mitre"
28 + size="small"
29 + color="primary"
30 + >
31 + <template #value>{{ mitre }}</template>
32 + </Badge>
33 + <Badge v-if="rule.mitre_attack_id.length > 2" size="small">
34 + <template #value>+{{ rule.mitre_attack_id.length - 2 }}</template>
35 + </Badge>
36 + </div>
37 + </div>
38 + </template>
39 + <template #headerExtra>
40 + <div class="text-default pt-.5 flex h-full items-center gap-2">
41 + <n-tooltip v-if="rule.has_graylog_query">
42 + <template #trigger>
43 + <Icon :name="GraylogIcon" :size="16" />
44 + </template>
45 + Has Graylog Query
46 + </n-tooltip>
47 + </div>
48 + </template>
49 + <template #default>
50 + <div class="flex flex-col gap-2">
51 + <div>{{ rule.name }}</div>
52 + <p class="line-clamp-3 text-sm">
53 + {{ rule.description }}
54 + </p>
55 + </div>
56 + </template>
57 + <template #mainExtra>
58 + <div class="flex flex-wrap items-center justify-between gap-2">
59 + <SeverityBadge :severity="rule.severity" />
60 + <PlatformBadge :platform="rule.platform" />
61 + </div>
62 + </template>
63 + <template #footerExtra>
64 + <div class="flex w-full items-center justify-end gap-2">
65 + <n-tooltip v-if="rule.has_graylog_query">
66 + <template #trigger>
67 + <n-button size="small" secondary @click.stop="showProvisionModal = true">
68 + <template #icon>
69 + <Icon :name="ProvisionIcon" />
70 + </template>
71 + </n-button>
72 + </template>
73 + Provision Graylog Alert
74 + </n-tooltip>
75 + <n-button size="small" type="primary" secondary @click.stop="showExecuteModal = true">
76 + <template #icon>
77 + <Icon :name="PlayIcon" />
78 + </template>
79 + Execute
80 + </n-button>
81 + </div>
82 + </template>
83 + </CardEntity>
84 +
85 + <!-- Rule Details Modal -->
86 + <n-modal
87 + v-model:show="showDetails"
88 + preset="card"
89 + :style="{ maxWidth: 'min(750px, 90vw)', minHeight: 'min(600px, 90vh)', overflow: 'hidden' }"
90 + title="Detection Rule"
91 + :bordered="false"
92 + segmented
93 + >
94 + <RuleCardContent :rule-id="rule.id" />
95 + </n-modal>
96 +
97 + <!-- Execute Search Modal -->
98 + <n-modal
99 + v-model:show="showExecuteModal"
100 + preset="card"
101 + :style="{ maxWidth: 'min(550px, 90vw)' }"
102 + title="Execute Search"
103 + :bordered="false"
104 + display-directive="show"
105 + segmented
106 + >
107 + <ExecuteSearchForm
108 + :rule-id="rule.id"
109 + show-header
110 + @success="handleExecuteSuccess"
111 + @close="showExecuteModal = false"
112 + />
113 + </n-modal>
114 +
115 + <!-- Provision Graylog Alert Modal -->
116 + <n-modal
117 + v-model:show="showProvisionModal"
118 + preset="card"
119 + :style="{ maxWidth: 'min(550px, 90vw)' }"
120 + title="Provision Graylog Alert"
121 + :bordered="false"
122 + display-directive="show"
123 + segmented
124 + >
125 + <ProvisionGraylogForm
126 + :rule-id="rule.id"
127 + @success="handleProvisionSuccess"
128 + @close="showProvisionModal = false"
129 + />
130 + </n-modal>
131 + </div>
132 +</template>
133 +
134 +<script setup lang="ts">
135 +import type { BadgeColor } from "@/components/common/Badge.vue"
136 +import type { RuleSummary } from "@/types/copilotSearches.d"
137 +import { NButton, NModal, NTooltip, useMessage } from "naive-ui"
138 +import { ref } from "vue"
139 +import Badge from "@/components/common/Badge.vue"
140 +import CardEntity from "@/components/common/cards/CardEntity.vue"
141 +import Icon from "@/components/common/Icon.vue"
142 +import PlatformBadge from "@/components/common/PlatformBadge.vue"
143 +import ExecuteSearchForm from "./ExecuteSearchForm.vue"
144 +import ProvisionGraylogForm from "./ProvisionGraylogForm.vue"
145 +import RuleCardContent from "./RuleCardContent.vue"
146 +import SeverityBadge from "./SeverityBadge.vue"
147 +
148 +const { rule } = defineProps<{ rule: RuleSummary; embedded?: boolean }>()
149 +
150 +const showDetails = ref(false)
151 +const showExecuteModal = ref(false)
152 +const showProvisionModal = ref(false)
153 +const message = useMessage()
154 +
155 +const PlayIcon = "carbon:play"
156 +const ProvisionIcon = "carbon:add-alt"
157 +const GraylogIcon = "carbon:notification"
158 +
159 +function getStatusColor(status: string): BadgeColor | undefined {
160 + switch (status.toLowerCase()) {
161 + case "production":
162 + return "success"
163 + case "experimental":
164 + return "warning"
165 + case "deprecated":
166 + return "danger"
167 + default:
168 + return undefined
169 + }
170 +}
171 +
172 +function handleExecuteSuccess() {
173 + showExecuteModal.value = false
174 + message.success("Search executed successfully!")
175 +}
176 +
177 +function handleProvisionSuccess() {
178 + showProvisionModal.value = false
179 +}
180 +</script>
frontend/src/components/copilotSearches/RuleCardContent.vue new
+243
@@ -0,0 +1,243 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <div v-if="rule" class="flex flex-col gap-4 pb-1">
4 + <!-- Basic Information -->
5 + <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
6 + <PropsList :list="infoFields" embedded title="Information" />
7 + <PropsList :list="riskAssessmentFields" embedded title="Risk Assessment" />
8 + </div>
9 +
10 + <!-- Description -->
11 + <CardKV>
12 + <template #key>Description</template>
13 + <template #value>{{ rule.description }}</template>
14 + </CardKV>
15 +
16 + <!-- Graylog Query -->
17 + <CardKV v-if="rule.graylog?.query">
18 + <template #key>
19 + <div class="flex items-center justify-between gap-2">
20 + <div class="flex items-center gap-2">
21 + <Icon :name="GraylogIcon" :size="14" />
22 + <span>Graylog Query</span>
23 + </div>
24 + <n-button size="tiny" type="primary" secondary @click="showProvisionModal = true">
25 + <template #icon>
26 + <Icon :name="ProvisionIcon" />
27 + </template>
28 + Provision Graylog Alert
29 + </n-button>
30 + </div>
31 + </template>
32 + <template #value>
33 + <CodeSource :code="rule.graylog.query" lang="sql" />
34 + </template>
35 + </CardKV>
36 +
37 + <!-- MITRE ATT&CK -->
38 + <CardKV v-if="rule.tags.mitre_attack_id?.length">
39 + <template #key>MITRE ATT&CK Techniques</template>
40 + <template #value>
41 + <div class="flex flex-wrap gap-2">
42 + <Badge v-for="mitre of rule.tags.mitre_attack_id" :key="mitre" color="primary">
43 + <template #value>{{ mitre }}</template>
44 + </Badge>
45 + </div>
46 + </template>
47 + </CardKV>
48 +
49 + <!-- Data Sources -->
50 + <CardKV v-if="rule.data_source?.length">
51 + <template #key>Data Sources</template>
52 + <template #value>
53 + <div class="flex flex-wrap gap-2">
54 + <Badge v-for="source of rule.data_source" :key="source">
55 + <template #value>{{ source }}</template>
56 + </Badge>
57 + </div>
58 + </template>
59 + </CardKV>
60 +
61 + <!-- Parameters -->
62 + <CardKV v-if="rule.parameters?.length">
63 + <template #key>Parameters</template>
64 + <template #value>
65 + <div class="grid grid-cols-1 gap-3 py-1 lg:grid-cols-2">
66 + <CardEntity
67 + v-for="param in rule.parameters"
68 + :key="param.name"
69 + embedded
70 + size="small"
71 + class="h-full"
72 + main-box-class="grow"
73 + card-entity-wrapper-class="h-full"
74 + >
75 + <template #headerMain>
76 + <div class="text-default flex items-center gap-4">
77 + <div class="text-sm font-semibold">{{ param.name }}</div>
78 + <Badge :color="param.required ? 'danger' : 'success'" type="splitted">
79 + <template #value>
80 + <span class="text-xs">{{ param.required ? "Required" : "Optional" }}</span>
81 + </template>
82 + </Badge>
83 + </div>
84 + </template>
85 + <template #headerExtra>
86 + <Badge>
87 + <template #value>
88 + <span class="text-xs">{{ param.type }}</span>
89 + </template>
90 + </Badge>
91 + </template>
92 +
93 + <template v-if="param.description" #default>
94 + <p class="text-xs">{{ param.description }}</p>
95 + </template>
96 +
97 + <template #footer>
98 + <div class="flex flex-col gap-1">
99 + <div
100 + v-if="param.default !== null && param.default !== undefined"
101 + class="text-xs opacity-60"
102 + >
103 + <span class="font-medium">Default:</span>
104 + <code class="code-block ml-1 rounded px-1 py-0.5 text-xs">
105 + {{ param.default }}
106 + </code>
107 + </div>
108 + <div
109 + v-if="param.example !== null && param.example !== undefined"
110 + class="text-xs opacity-60"
111 + >
112 + <span class="font-medium">Example:</span>
113 + <code class="code-block ml-1 rounded px-1 py-0.5 text-xs">
114 + {{ param.example }}
115 + </code>
116 + </div>
117 + </div>
118 + </template>
119 + </CardEntity>
120 + </div>
121 + </template>
122 + </CardKV>
123 +
124 + <!-- How to Implement -->
125 + <CardKV v-if="rule.how_to_implement">
126 + <template #key>How to Implement</template>
127 + <template #value>{{ rule.how_to_implement }}</template>
128 + </CardKV>
129 +
130 + <!-- Known False Positives -->
131 + <CardKV v-if="rule.known_false_positives">
132 + <template #key>Known False Positives</template>
133 + <template #value>{{ rule.known_false_positives }}</template>
134 + </CardKV>
135 +
136 + <!-- References -->
137 + <CardKV v-if="rule.references?.length">
138 + <template #key>References</template>
139 + <template #value>
140 + <div class="flex flex-col gap-1">
141 + <a
142 + v-for="item of rule.references"
143 + :key="item"
144 + :href="item"
145 + target="_blank"
146 + rel="noopener"
147 + class="text-primary-color text-sm hover:underline"
148 + >
149 + {{ item }}
150 + </a>
151 + </div>
152 + </template>
153 + </CardKV>
154 +
155 + <!-- Analytic Stories -->
156 + <div v-if="rule.tags.analytic_story?.length" class="flex flex-wrap gap-2">
157 + <code v-for="story of rule.tags.analytic_story" :key="story">#{{ story }}</code>
158 + </div>
159 + </div>
160 +
161 + <n-empty v-else-if="!loading" description="Failed to load rule details" />
162 +
163 + <!-- Provision Graylog Alert Modal -->
164 + <n-modal
165 + v-model:show="showProvisionModal"
166 + preset="card"
167 + :style="{ maxWidth: 'min(550px, 90vw)' }"
168 + title="Provision Graylog Alert"
169 + :bordered="false"
170 + display-directive="show"
171 + segmented
172 + >
173 + <ProvisionGraylogForm
174 + v-if="rule"
175 + :rule-data="rule"
176 + @success="showProvisionModal = false"
177 + @close="showProvisionModal = false"
178 + />
179 + </n-modal>
180 + </n-spin>
181 +</template>
182 +
183 +<script setup lang="ts">
184 +import type { RuleDetail } from "@/types/copilotSearches.d"
185 +import _pick from "lodash/pick"
186 +import { NButton, NEmpty, NModal, NSpin, useMessage } from "naive-ui"
187 +import { computed, onBeforeMount, ref } from "vue"
188 +import Api from "@/api"
189 +import Badge from "@/components/common/Badge.vue"
190 +import CardEntity from "@/components/common/cards/CardEntity.vue"
191 +import CardKV from "@/components/common/cards/CardKV.vue"
192 +import CodeSource from "@/components/common/CodeSource.vue"
193 +import Icon from "@/components/common/Icon.vue"
194 +import PropsList from "@/components/common/PropsList.vue"
195 +import ProvisionGraylogForm from "./ProvisionGraylogForm.vue"
196 +
197 +const props = defineProps<{
198 + ruleId?: string
199 + ruleData?: RuleDetail
200 +}>()
201 +
202 +const loading = ref(false)
203 +const rule = ref<RuleDetail | null>(null)
204 +const showProvisionModal = ref(false)
205 +const message = useMessage()
206 +
207 +const GraylogIcon = "carbon:notification"
208 +const ProvisionIcon = "carbon:add-alt"
209 +
210 +const infoFields = computed(() => _pick(rule.value, ["author", "version", "date", "status", "type"]))
211 +const riskAssessmentFields = computed(() => ({
212 + severity: rule.value?.response.severity,
213 + risk_score: rule.value?.response.risk_score,
214 + platform: rule.value?.tags.asset_type,
215 + security_domain: rule.value?.tags.security_domain
216 +}))
217 +
218 +async function loadRule(ruleId: string) {
219 + loading.value = true
220 + try {
221 + const res = await Api.copilotSearches.getRuleById(ruleId)
222 + if (res.data.success) {
223 + rule.value = res.data.rule
224 + } else {
225 + message.error(res.data?.message || "Failed to load rule details")
226 + }
227 + } catch (err: any) {
228 + message.error(err.response?.data?.message || "Failed to load rule details")
229 + } finally {
230 + loading.value = false
231 + }
232 +}
233 +
234 +onBeforeMount(() => {
235 + if (props.ruleData) {
236 + rule.value = props.ruleData
237 + } else if (props.ruleId) {
238 + loadRule(props.ruleId)
239 + } else {
240 + message.error("No rule data or rule ID provided")
241 + }
242 +})
243 +</script>
frontend/src/components/copilotSearches/RuleHeader.vue new
+64
@@ -0,0 +1,64 @@
1 +<template>
2 + <div class="flex flex-col gap-2">
3 + <div class="flex items-center gap-2">
4 + <PlatformBadge :platform />
5 + <SeverityBadge :severity />
6 + </div>
7 + <h3 class="font-semibold">{{ name }}</h3>
8 + <p class="text-sm opacity-70">{{ description }}</p>
9 + </div>
10 +</template>
11 +
12 +<script setup lang="ts">
13 +import type { RuleDetail, RuleSummary } from "@/types/copilotSearches.d"
14 +import { useMessage } from "naive-ui"
15 +import { computed, onBeforeMount, ref } from "vue"
16 +import Api from "@/api"
17 +import PlatformBadge from "@/components/common/PlatformBadge.vue"
18 +import SeverityBadge from "./SeverityBadge.vue"
19 +
20 +const props = defineProps<{
21 + ruleId?: string
22 + ruleDetail?: RuleDetail
23 + ruleSummary?: RuleSummary
24 +}>()
25 +
26 +const message = useMessage()
27 +const loading = ref(false)
28 +const ruleDetail = ref<RuleDetail | null>(null)
29 +const ruleSummary = ref<RuleSummary | null>(null)
30 +
31 +const platform = computed(() => ruleDetail.value?.tags?.asset_type || ruleSummary.value?.platform || "unknown")
32 +const severity = computed(() => ruleDetail.value?.response?.severity || ruleSummary.value?.severity || "medium")
33 +const name = computed(() => ruleDetail.value?.name || ruleSummary.value?.name || "")
34 +const description = computed(() => ruleDetail.value?.description || ruleSummary.value?.description || "")
35 +
36 +async function loadRule(ruleId: string) {
37 + loading.value = true
38 +
39 + try {
40 + const res = await Api.copilotSearches.getRuleById(ruleId)
41 + if (res.data.success) {
42 + ruleDetail.value = res.data.rule
43 + } else {
44 + message.error(res.data?.message || "Failed to load rule details")
45 + }
46 + } catch (err: any) {
47 + message.error(err.response?.data?.message || "Failed to load rule details")
48 + } finally {
49 + loading.value = false
50 + }
51 +}
52 +
53 +onBeforeMount(() => {
54 + if (props.ruleDetail) {
55 + ruleDetail.value = props.ruleDetail
56 + } else if (props.ruleSummary) {
57 + ruleSummary.value = props.ruleSummary
58 + } else if (props.ruleId) {
59 + loadRule(props.ruleId)
60 + } else {
61 + message.error("No rule data or rule ID provided")
62 + }
63 +})
64 +</script>
frontend/src/components/copilotSearches/SeverityBadge.vue new
+47
@@ -0,0 +1,47 @@
1 +<template>
2 + <Badge :color="getSeverityColor(severity)" type="splitted" bright>
3 + <template #iconLeft>
4 + <Icon :name="getSeverityIcon(severity)" :size="14" />
5 + </template>
6 + <template #label>{{ severity }}</template>
7 + <template #value>Severity</template>
8 + </Badge>
9 +</template>
10 +
11 +<script setup lang="ts">
12 +import type { BadgeColor } from "@/components/common/Badge.vue"
13 +import Badge from "@/components/common/Badge.vue"
14 +import Icon from "@/components/common/Icon.vue"
15 +
16 +const { severity } = defineProps<{ severity: string }>()
17 +
18 +function getSeverityColor(severity: string): BadgeColor | undefined {
19 + switch (severity.toLowerCase()) {
20 + case "low":
21 + return "success"
22 + case "medium":
23 + return "warning"
24 + case "high":
25 + return "danger"
26 + case "critical":
27 + return "danger"
28 + default:
29 + return undefined
30 + }
31 +}
32 +
33 +function getSeverityIcon(severity: string): string {
34 + switch (severity.toLowerCase()) {
35 + case "low":
36 + return "carbon:warning-alt"
37 + case "medium":
38 + return "carbon:warning"
39 + case "high":
40 + return "carbon:warning-filled"
41 + case "critical":
42 + return "carbon:error-filled"
43 + default:
44 + return "carbon:warning"
45 + }
46 +}
47 +</script>
frontend/src/components/customerPortal/Form.vue
+5 -3
@@ -29,7 +29,7 @@
29 </div>
30
31 <div>
32 - <n-button type="primary" :loading="loading" @click="save()">
32 + <n-button type="primary" :loading @click="save()">
33 <template #icon>
34 <Icon :name="SaveIcon" />
35 </template>
@@ -41,7 +41,7 @@
41 </template>
42
43 <script setup lang="ts">
44 -// TODO: refactor
44 +// TODO-FE: refactor
45 import type { CustomerPortalSettingsPayload } from "@/api/endpoints/customerPortal"
46 import type { ImageCropperResult } from "@/components/common/ImageCropper.vue"
47 import type { CustomerPortalSettings } from "@/types/customerPortal"
@@ -85,6 +85,8 @@ function getDefaultModel(entity?: CustomerPortalSettings): SettingsModel {
85 }
86 }
87
88 +const DATA_URL_MIME_REGEX = /data:([^;]+);base64/
89 +
90 function getLogoMeta(logo?: string | null): { base64: string | null; mime_type: string | null } {
91 // Parse data URL format: data:image/png;base64,iVBORw0KG...
92 const parts = _split(logo, ",")
@@ -96,7 +98,7 @@ function getLogoMeta(logo?: string | null): { base64: string | null; mime_type:
98 }
99
100 const base64 = parts[1]
99 - const mimeMatch = parts[0]?.match(/data:([^;]+);base64/)
101 + const mimeMatch = parts[0]?.match(DATA_URL_MIME_REGEX)
102 const mime_type = mimeMatch ? mimeMatch[1] : null
103
104 return {
frontend/src/components/customers/CustomerAgents.vue
+5 -4
@@ -25,7 +25,7 @@
25 </template>
26
27 <script setup lang="ts">
28 -// TODO: refactor
28 +// TODO-FE: refactor
29 import type { Agent } from "@/types/agents.d"
30 import type { Customer } from "@/types/customers.d"
31 import { NButton, NEmpty, NSpin, useMessage } from "naive-ui"
@@ -41,6 +41,7 @@ const props = defineProps<{
41 const { customer } = toRefs(props)
42
43 const DownloadIcon = "carbon:download"
44 +const QUOTE_ESCAPE_REGEX = /"/g
45
46 const loading = ref(false)
47 const router = useRouter()
@@ -48,7 +49,7 @@ const message = useMessage()
49 const list = ref<Agent[] | []>([])
50
51 const sortedList = computed(() => {
51 - return [...list.value].sort((a, b) => {
52 + return list.value.toSorted((a, b) => {
53 return a.hostname.toLowerCase().localeCompare(b.hostname.toLowerCase())
54 })
55 })
@@ -77,9 +78,9 @@ function exportToCSV() {
78 return [
79 agent.customer_code || "",
80 agent.agent_id,
80 - `"${agent.hostname.replace(/"/g, '""')}"`, // Escape quotes in hostname
81 + `"${agent.hostname.replace(QUOTE_ESCAPE_REGEX, '""')}"`, // Escape quotes in hostname
82 agent.ip_address,
82 - `"${agent.os.replace(/"/g, '""')}"`, // Escape quotes in OS
83 + `"${agent.os.replace(QUOTE_ESCAPE_REGEX, '""')}"`, // Escape quotes in OS
84 agent.wazuh_last_seen,
85 agent.velociraptor_last_seen || ""
86 ].join(",")
frontend/src/components/customers/CustomerCreationButton.vue
+1 -5
@@ -47,11 +47,7 @@
47 display-directive="show"
48 >
49 <n-drawer-content title="Add Customer" closable :native-scrollbar="false">
50 - <CustomerForm
51 - :reset-on-submit="true"
52 - @mounted="customerFormCTX = $event"
53 - @submitted="emit('submitted')"
54 - />
50 + <CustomerForm reset-on-submit @mounted="customerFormCTX = $event" @submitted="emit('submitted')" />
51 </n-drawer-content>
52 </n-drawer>
53 </div>
frontend/src/components/customers/CustomerForm.vue
+7 -7
@@ -1,9 +1,9 @@
1 <template>
2 <n-spin :show="loading" class="customer-form">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 - <div v-for="(val, key) of fieldsMeta" :key="key" class="grow">
6 + <div v-for="(val, key) of fieldsMeta" :key class="grow">
7 <n-form-item :label="val.label" :path="key" class="grow">
8 <n-input
9 v-model:value.trim="form[key]"
@@ -21,9 +21,7 @@
21 </div>
22 <div class="flex gap-4">
23 <n-button :disabled="loading" @click="reset()">Reset</n-button>
24 - <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">
25 - Submit
26 - </n-button>
24 + <n-button type="primary" :disabled="!isValid" :loading @click="validate()">Submit</n-button>
25 </div>
26 </div>
27 </div>
@@ -32,7 +30,7 @@
30 </template>
31
32 <script setup lang="ts">
35 -// TODO: refactor
33 +// TODO-FE: refactor
34 import type { FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui"
35 import type { Customer } from "@/types/customers.d"
36 import _get from "lodash/get"
@@ -66,6 +64,8 @@ const message = useMessage()
64 const form = ref<Customer>(getClearForm())
65 const formRef = ref<FormInst | null>(null)
66
67 +const CUSTOMER_CODE_REGEX = /^[a-z]+$/
68 +
69 const rules: FormRules = {
70 customer_code: {
71 required: true,
@@ -74,7 +74,7 @@ const rules: FormRules = {
74 validator: (_rule: FormItemRule, value: string) => {
75 if (value !== value.toLowerCase()) {
76 return new Error("Code must be all lowercase")
77 - } else if (!/^[a-z]+$/.test(value)) {
77 + } else if (!CUSTOMER_CODE_REGEX.test(value)) {
78 return new Error("Code must not contain spaces or special characters")
79 } else {
80 return true
frontend/src/components/customers/CustomerInfo.vue
+2 -2
@@ -1,7 +1,7 @@
1 <template>
2 <div class="customer-info">
3 <div v-if="editing" class="p-7 pt-4">
4 - <CustomerForm :customer="customer" :lock-code="true" @submitted="submitted">
4 + <CustomerForm :customer lock-code @submitted="submitted">
5 <template #additionalActions>
6 <n-button @click="editing = false">Close</n-button>
7 </template>
@@ -24,7 +24,7 @@
24 </div>
25
26 <div class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
27 - <CardKV v-for="(value, key) of customer" :key="key">
27 + <CardKV v-for="(value, key) of customer" :key>
28 <template #key>
29 {{ key }}
30 </template>
frontend/src/components/customers/CustomerItem.vue
+2 -2
@@ -155,7 +155,7 @@
155 </n-tab-pane>
156 <n-tab-pane name="Provision" tab="Provision" display-directive="show:lazy">
157 <CustomerProvision
158 - :customer-meta="customerMeta"
158 + :customer-meta
159 :customer-code="customer.customer_code"
160 :customer-name="customer.customer_name"
161 @delete="customerMeta = null"
@@ -266,7 +266,7 @@
266 </template>
267
268 <script setup lang="ts">
269 -// TODO: refactor
269 +// TODO-FE: refactor
270 import type { Customer, CustomerMeta } from "@/types/customers.d"
271 import _toSafeInteger from "lodash/toSafeInteger"
272 import { NAvatar, NButton, NModal, NPopover, NScrollbar, NTabPane, NTabs, useMessage } from "naive-ui"
frontend/src/components/customers/CustomerMetaForm.vue
+4 -6
@@ -1,9 +1,9 @@
1 <template>
2 <n-spin :show="loading" class="customer-meta-form">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 - <div v-for="(_, key) of form" :key="key" class="grow">
6 + <div v-for="(_, key) of form" :key class="grow">
7 <n-form-item :label="fieldsMeta[key]?.label ?? ''" :path="key" class="grow">
8 <n-input
9 v-model:value.trim="form[key]"
@@ -20,9 +20,7 @@
20 </div>
21 <div class="flex gap-4">
22 <n-button :disabled="loading" @click="reset()">Reset</n-button>
23 - <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">
24 - Submit
25 - </n-button>
23 + <n-button type="primary" :disabled="!isValid" :loading @click="validate()">Submit</n-button>
24 </div>
25 </div>
26 </div>
@@ -31,7 +29,7 @@
29 </template>
30
31 <script setup lang="ts">
34 -// TODO: refactor
32 +// TODO-FE: refactor
33 import type { FormInst, FormRules, FormValidationError } from "naive-ui"
34 import type { CustomerMeta } from "@/types/customers.d"
35 import _get from "lodash/get"
frontend/src/components/customers/CustomersList.vue
+1 -1
@@ -37,7 +37,7 @@
37 </template>
38
39 <script setup lang="ts">
40 -// TODO: refactor
40 +// TODO-FE: refactor
41 import type { Customer } from "@/types/customers.d"
42 import { NEmpty, NSelect, NSpin, useMessage } from "naive-ui"
43 import { computed, nextTick, onBeforeMount, ref, toRefs, watch } from "vue"
frontend/src/components/customers/healthcheck/CustomerHealthcheckItem.vue
+1 -1
@@ -82,7 +82,7 @@
82 segmented
83 >
84 <div class="grid-auto-fit-200 grid gap-2 px-7 py-6">
85 - <CardKV v-for="(value, key) of healthData" :key="key">
85 + <CardKV v-for="(value, key) of healthData" :key>
86 <template #key>
87 {{ key }}
88 </template>
frontend/src/components/customers/healthcheck/CustomerHealthcheckList.vue
+3 -3
@@ -29,7 +29,7 @@
29 v-for="item of healthyList"
30 :key="item.id"
31 :health-data="item"
32 - :source="source"
32 + :source
33 type="healthy"
34 embedded
35 class="item-appear item-appear-bottom item-appear-005"
@@ -51,7 +51,7 @@
51 v-for="item of unhealthyList"
52 :key="item.id"
53 :health-data="item"
54 - :source="source"
54 + :source
55 type="unhealthy"
56 embedded
57 class="item-appear item-appear-bottom item-appear-005"
@@ -65,7 +65,7 @@
65 </template>
66
67 <script setup lang="ts">
68 -// TODO: refactor
68 +// TODO-FE: refactor
69 import type { CustomerAgentsHealthcheckQuery } from "@/api/endpoints/customers"
70 import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/customers.d"
71 import { watchDebounced } from "@vueuse/core"
frontend/src/components/customers/integrations/CustomerIntegrationActions.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <div class="contents">
3 - <n-button v-if="isDeployEnabled" :loading="loading" type="success" :size secondary @click.stop="provision()">
3 + <n-button v-if="isDeployEnabled" :loading type="success" :size secondary @click.stop="provision()">
4 <template #icon>
5 <Icon :name="DeployIcon" />
6 </template>
frontend/src/components/customers/integrations/CustomerIntegrationForm.vue
+4 -10
@@ -3,7 +3,7 @@
3 <div>
4 <n-scrollbar x-scrollable trigger="none">
5 <div class="px-7 pt-4 pb-2">
6 - <n-steps :current="current" size="small" :status="currentStatus">
6 + <n-steps :current size="small" :status="currentStatus">
7 <n-step title="Choose Integration" />
8 <n-step title="Set Auth Keys">
9 <template #icon>
@@ -24,7 +24,7 @@
24 embedded
25 hide-totals
26 selectable
27 - :disabled-ids-list="disabledIdsList"
27 + :disabled-ids-list
28 class="px-7"
29 />
30 </n-scrollbar>
@@ -65,13 +65,7 @@
65 </template>
66 Next
67 </n-button>
68 - <n-button
69 - v-if="isSubmitEnabled"
70 - type="primary"
71 - :disabled="!isSubmitValid"
72 - :loading="loading"
73 - @click="submit()"
74 - >
68 + <n-button v-if="isSubmitEnabled" type="primary" :disabled="!isSubmitValid" :loading @click="submit()">
69 Submit
70 </n-button>
71 </div>
@@ -80,7 +74,7 @@
74 </template>
75
76 <script setup lang="ts">
83 -// TODO: refactor
77 +// TODO-FE: refactor
78 import type { StepsProps } from "naive-ui"
79 import type { NewIntegration } from "@/api/endpoints/integrations"
80 import type { ServiceItemData } from "@/components/services/types"
frontend/src/components/customers/integrations/CustomerIntegrations.vue
+3 -3
@@ -3,8 +3,8 @@
3 <transition name="form-fade" mode="out-in">
4 <div v-if="showForm">
5 <CustomerIntegrationForm
6 - :customer-code="customerCode"
7 - :customer-name="customerName"
6 + :customer-code
7 + :customer-name
8 :disabled-ids-list="disabledIds"
9 @submitted="refreshList()"
10 @close="closeForm()"
@@ -26,7 +26,7 @@
26 <CustomerIntegrationItem
27 v-for="integration of list"
28 :key="integration.id"
29 - :integration="integration"
29 + :integration
30 embedded
31 class="item-appear item-appear-bottom item-appear-005 mb-2"
32 @deployed="refreshList()"
frontend/src/components/customers/metadata/CustomerIntegrationMetaDetails.vue
+1 -1
@@ -23,7 +23,7 @@
23 >
24 <!-- Read-only View (when not editing) -->
25 <div class="grid-auto-fit-200 grid gap-2">
26 - <CardKV v-for="(value, key) of integrationData" :key="key">
26 + <CardKV v-for="(value, key) of integrationData" :key>
27 <template #key>
28 {{ getMetaFieldLabel(`${key}`) }}
29 </template>
frontend/src/components/customers/metadata/CustomerIntegrationMetaForm.vue
+1 -1
@@ -32,7 +32,7 @@
32
33 <n-button v-if="showResetButton" @click="reset()">Reset</n-button>
34 </div>
35 - <n-button type="primary" :loading="loading" @click="save()">
35 + <n-button type="primary" :loading @click="save()">
36 <template #icon>
37 <Icon :name="SaveIcon" />
38 </template>
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorActions.vue
+5 -12
@@ -4,7 +4,7 @@
4 v-if="networkConnector.deployed"
5 :loading="loadingDecommission"
6 type="error"
7 - :size="size"
7 + :size
8 secondary
9 @click.stop="decommissionNetworkConnector()"
10 >
@@ -18,7 +18,7 @@
18 v-if="isFortinet && !networkConnector.deployed"
19 :loading="loadingFortinetProvision"
20 type="success"
21 - :size="size"
21 + :size
22 secondary
23 @click.stop="showFortinetForm = true"
24 >
@@ -32,7 +32,7 @@
32 v-if="isSonicwall && !networkConnector.deployed"
33 :loading="loadingSonicwallProvision"
34 type="success"
35 - :size="size"
35 + :size
36 secondary
37 @click.stop="showSonicwallForm = true"
38 >
@@ -46,7 +46,7 @@
46 v-if="isSentinelOne && !networkConnector.deployed"
47 :loading="loadingSentinelOneProvision"
48 type="success"
49 - :size="size"
49 + :size
50 secondary
51 @click.stop="showSentinelOneForm = true"
52 >
@@ -56,14 +56,7 @@
56 Deploy
57 </n-button>
58
59 - <n-button
60 - v-if="!hideDeleteButton"
61 - :size="size"
62 - type="error"
63 - ghost
64 - :loading="loadingDelete"
65 - @click.stop="handleDelete"
66 - >
59 + <n-button v-if="!hideDeleteButton" :size type="error" ghost :loading="loadingDelete" @click.stop="handleDelete">
60 <template #icon>
61 <Icon :name="DeleteIcon" :size="15" />
62 </template>
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectorForm.vue
+4 -10
@@ -3,7 +3,7 @@
3 <div>
4 <n-scrollbar x-scrollable trigger="none">
5 <div class="px-7 pt-4 pb-2">
6 - <n-steps :current="current" size="small" :status="currentStatus">
6 + <n-steps :current size="small" :status="currentStatus">
7 <n-step title="Choose Network Connector" />
8 <n-step title="Set Auth Keys">
9 <template #icon>
@@ -24,7 +24,7 @@
24 embedded
25 hide-totals
26 selectable
27 - :disabled-ids-list="disabledIdsList"
27 + :disabled-ids-list
28 class="px-7"
29 />
30 </n-scrollbar>
@@ -65,13 +65,7 @@
65 </template>
66 Next
67 </n-button>
68 - <n-button
69 - v-if="isSubmitEnabled"
70 - type="primary"
71 - :disabled="!isSubmitValid"
72 - :loading="loading"
73 - @click="submit()"
74 - >
68 + <n-button v-if="isSubmitEnabled" type="primary" :disabled="!isSubmitValid" :loading @click="submit()">
69 Submit
70 </n-button>
71 </div>
@@ -80,7 +74,7 @@
74 </template>
75
76 <script setup lang="ts">
83 -// TODO: refactor
77 +// TODO-FE: refactor
78 import type { StepsProps } from "naive-ui"
79 import type { NewNetworkConnector } from "@/api/endpoints/networkConnectors"
80 import type { ServiceItemData } from "@/components/services/types"
frontend/src/components/customers/networkConnectors/CustomerNetworkConnectors.vue
+3 -3
@@ -3,8 +3,8 @@
3 <transition name="form-fade" mode="out-in">
4 <div v-if="showForm">
5 <CustomerNetworkConnectorForm
6 - :customer-code="customerCode"
7 - :customer-name="customerName"
6 + :customer-code
7 + :customer-name
8 :disabled-ids-list="disabledIds"
9 @submitted="refreshList()"
10 @close="closeForm()"
@@ -26,7 +26,7 @@
26 <CustomerNetworkConnectorItem
27 v-for="networkConnector of list"
28 :key="networkConnector.id"
29 - :network-connector="networkConnector"
29 + :network-connector
30 embedded
31 class="item-appear item-appear-bottom item-appear-005 mb-2"
32 @deployed="refreshList()"
frontend/src/components/customers/networkConnectors/provisions/FortinetForm.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form class="flex flex-col gap-5" :model="options" :rules="rules">
2 + <n-form class="flex flex-col gap-5" :model="options" :rules>
3 <n-form-item label="Protocol" label-placement="left" :show-feedback="false" size="small" path="protocol">
4 <n-select v-model:value="options.protocol" :options="protocolOptions" />
5 </n-form-item>
frontend/src/components/customers/networkConnectors/provisions/SentinelOneForm.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form :model="model" label-placement="left" label-width="auto">
2 + <n-form :model label-placement="left" label-width="auto">
3 <n-form-item label="Protocol" required>
4 <n-radio-group v-model:value="model.protocol">
5 <n-radio-button value="tcp">TCP</n-radio-button>
frontend/src/components/customers/networkConnectors/provisions/SonicwallForm.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form :model="model" label-placement="left" label-width="auto">
2 + <n-form :model label-placement="left" label-width="auto">
3 <n-form-item label="Protocol" required>
4 <n-radio-group v-model:value="model.protocol">
5 <n-radio-button value="tcp">TCP</n-radio-button>
frontend/src/components/customers/notifications/CustomerNotificationsWorkflowsForm.vue
+2 -2
@@ -2,7 +2,7 @@
2 <div class="customer-notifications-workflows-form flex grow flex-col justify-between">
3 <div class="form-box">
4 <n-spin v-model:show="loading">
5 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
5 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
6 <n-form-item label="Enabled" path="enabled">
7 <n-switch v-model:value="form.enabled" />
8 </n-form-item>
@@ -22,7 +22,7 @@
22 </div>
23 <div class="flex gap-3">
24 <n-button secondary :disabled="loading" @click="reset()">Reset</n-button>
25 - <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">Submit</n-button>
25 + <n-button type="primary" :disabled="!isValid" :loading @click="validate()">Submit</n-button>
26 </div>
27 </div>
28 </div>
frontend/src/components/customers/provision/CustomerDefaultSettingsButton.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button size="small" secondary :loading="loading" @click="showForm = true">
2 + <n-button size="small" secondary :loading @click="showForm = true">
3 <template #icon>
4 <Icon :name="SettingsIcon" :size="14" />
5 </template>
frontend/src/components/customers/provision/CustomerDefaultSettingsForm.vue
+2 -2
@@ -1,9 +1,9 @@
1 <template>
2 <n-spin :show="loading" class="customer-provisioning-default-settings-form">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-4">
5 <div class="flex flex-wrap gap-4">
6 - <div v-for="(_, key) of form" :key="key" class="grow">
6 + <div v-for="(_, key) of form" :key class="grow">
7 <n-form-item :label="fieldsMeta[key].label" :path="key" class="grow">
8 <n-input
9 v-model:value.trim="form[key]"
frontend/src/components/customers/provision/CustomerProvision.vue
+3 -7
@@ -2,11 +2,7 @@
2 <div class="customer-provision">
3 <transition name="form-fade" mode="out-in">
4 <div v-if="editing">
5 - <CustomerProvisionWizard
6 - :customer-name="customerNameSanitized"
7 - :customer-code="customerCode"
8 - @submitted="submitted"
9 - >
5 + <CustomerProvisionWizard :customer-name="customerNameSanitized" :customer-code @submitted="submitted">
6 <template #additionalActions>
7 <n-button @click="editing = false">Close</n-button>
8 </template>
@@ -31,7 +27,7 @@
27 </div>
28
29 <div class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
34 - <CardKV v-for="(value, key) of customerMeta" :key="key">
30 + <CardKV v-for="(value, key) of customerMeta" :key>
31 <template #key>
32 {{ key }}
33 </template>
@@ -46,7 +42,7 @@
42 </template>
43
44 <script setup lang="ts">
49 -// TODO: refactor
45 +// TODO-FE: refactor
46 import type { CustomerMeta } from "@/types/customers.d"
47 import { NButton, useDialog, useMessage } from "naive-ui"
48 import { computed, h, ref, toRefs } from "vue"
frontend/src/components/customers/provision/CustomerProvisionWizard.vue
+3 -5
@@ -22,7 +22,7 @@
22 </div>
23 </n-scrollbar>
24
25 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules" class="form-container mt-4">
25 + <n-form ref="formRef" :label-width="80" :model="form" :rules class="form-container mt-4">
26 <Transition :name="`slide-form-${slideFormDirection}`">
27 <div v-if="current === 1" class="flex flex-col gap-3 px-7">
28 <div class="flex flex-wrap gap-3">
@@ -268,9 +268,7 @@
268 </template>
269 Next
270 </n-button>
271 - <n-button v-if="isSubmitEnabled" type="primary" :loading="loading" @click="validate(submit)">
272 - Submit
273 - </n-button>
271 + <n-button v-if="isSubmitEnabled" type="primary" :loading @click="validate(submit)">Submit</n-button>
272 </div>
273 </div>
274 </div>
@@ -278,7 +276,7 @@
276 </template>
277
278 <script setup lang="ts">
281 -// TODO: refactor
279 +// TODO-FE: refactor
280 import type { FormInst, FormItemRule, FormRules, FormValidationError, StepsProps } from "naive-ui"
281 import type { CustomerMeta, CustomerProvision, CustomerProvisioningDefaultSettings } from "@/types/customers.d"
282 import _uniqBy from "lodash/uniqBy"
frontend/src/components/githubAudit/GitHubAuditButton.vue
+5 -4
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button :size="size" :type="type" @click="goToGitHubAudit">
2 + <n-button :size :type :secondary @click="goToGitHubAudit">
3 <template #icon>
4 <Icon :name="GitHubIcon" />
5 </template>
@@ -8,14 +8,15 @@
8 </template>
9
10 <script setup lang="ts">
11 -// TODO: refactor
11 +import type { Size, Type } from "naive-ui/es/button/src/interface"
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?: "tiny" | "small" | "medium" | "large"
18 - type?: "default" | "primary" | "info" | "success" | "warning" | "error"
17 + size?: Size
18 + type?: Type
19 + secondary?: boolean
20 }>()
21
22 const GitHubIcon = "carbon:logo-github"
frontend/src/components/githubAudit/GitHubAuditCard.vue
+1 -1
@@ -67,7 +67,7 @@
67 </template>
68
69 <script setup lang="ts">
70 -// TODO: refactor
70 +// TODO-FE: refactor
71 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
72 import { NButton, NCard, NDivider, NIcon, NProgress, NTag, useMessage } from "naive-ui"
73 import { computed, ref } from "vue"
frontend/src/components/githubAudit/GitHubAuditConfigForm.vue
+4 -4
@@ -1,7 +1,7 @@
1 <template>
2 <n-drawer v-model:show="showDrawer" :width="600" placement="right">
3 <n-drawer-content :title="isEdit ? 'Edit Configuration' : 'New GitHub Audit Configuration'" closable>
4 - <n-form ref="formRef" :model="formData" :rules="rules" label-placement="top">
4 + <n-form ref="formRef" :model="formData" :rules label-placement="top">
5 <n-divider title-placement="left">Basic Settings</n-divider>
6
7 <n-form-item label="Customer" path="customer_code">
@@ -140,7 +140,7 @@
140 </template>
141
142 <script setup lang="ts">
143 -// TODO: refactor
143 +// TODO-FE: refactor
144 import type { FormInst, FormRules } from "naive-ui"
145 import type { GitHubAuditConfig, GitHubAuditConfigCreate, GitHubAuditConfigUpdate } from "@/types/githubAudit.d"
146 import {
@@ -163,7 +163,7 @@ import {
163 NText,
164 useMessage
165 } from "naive-ui"
166 -import { computed, onMounted, reactive, ref, watch } from "vue"
166 +import { computed, onBeforeMount, reactive, ref, watch } from "vue"
167 import Api from "@/api"
168
169 const props = defineProps<{
@@ -283,7 +283,7 @@ async function handleSubmit() {
283 }
284 }
285
286 -onMounted(async () => {
286 +onBeforeMount(async () => {
287 try {
288 const response = await Api.customers.getCustomers()
289 if (response.data.customers) {
frontend/src/components/githubAudit/GitHubAuditDetail.vue
+3 -3
@@ -100,14 +100,14 @@
100 <GitHubAuditReportCard
101 v-for="report in reports"
102 :key="report.id"
103 - :report="report"
103 + :report
104 @click="openReportDetail"
105 />
106
107 <n-pagination
108 v-if="totalReports > pageSize"
109 v-model:page="currentPage"
110 - :page-size="pageSize"
110 + :page-size
111 :item-count="totalReports"
112 @update:page="loadReports"
113 />
@@ -177,7 +177,7 @@
177 </template>
178
179 <script setup lang="ts">
180 -// TODO: refactor
180 +// TODO-FE: refactor
181 import type {
182 GitHubAuditCheckExclusion,
183 GitHubAuditConfig,
frontend/src/components/githubAudit/GitHubAuditExclusionForm.vue
+5 -5
@@ -1,6 +1,6 @@
1 <template>
2 <n-modal v-model:show="showModal" preset="dialog" title="Add Exclusion" style="width: 500px">
3 - <n-form ref="formRef" :model="formData" :rules="rules" label-placement="top">
3 + <n-form ref="formRef" :model="formData" :rules label-placement="top">
4 <n-form-item label="Check to Exclude" path="check_id">
5 <n-select
6 v-model:value="formData.check_id"
@@ -43,11 +43,11 @@
43 </template>
44
45 <script setup lang="ts">
46 -// TODO: refactor
46 +// TODO-FE: refactor
47 import type { FormInst, FormRules } from "naive-ui"
48 import type { GitHubAuditExclusionCreate } from "@/types/githubAudit.d"
49 import { NButton, NDatePicker, NForm, NFormItem, NInput, NModal, NSelect, useMessage } from "naive-ui"
50 -import { computed, onMounted, reactive, ref } from "vue"
50 +import { computed, onBeforeMount, reactive, ref } from "vue"
51 import Api from "@/api"
52
53 const props = defineProps<{
@@ -77,7 +77,7 @@ const formData = reactive<GitHubAuditExclusionCreate>({
77 reason: "",
78 approved_by: null,
79 expires_at: null,
80 - created_by: "current_user" // TODO: Get from auth context
80 + created_by: "current_user" // TODO-FE: Get from auth context
81 })
82
83 const rules: FormRules = {
@@ -116,7 +116,7 @@ async function handleSubmit() {
116 }
117 }
118
119 -onMounted(async () => {
119 +onBeforeMount(async () => {
120 try {
121 const response = await Api.githubAudit.getAvailableChecks()
122 checkOptions.value = response.data.checks.map(check => ({
frontend/src/components/githubAudit/GitHubAuditFilters.vue
+4 -4
@@ -55,7 +55,7 @@
55 <n-grid v-else :cols="2" :x-gap="16" :y-gap="16">
56 <n-gi v-for="config in configs" :key="config.id">
57 <GitHubAuditCard
58 - :config="config"
58 + :config
59 @click="openDetail(config)"
60 @edit="openEditForm"
61 @audit-complete="loadConfigs"
@@ -83,10 +83,10 @@
83 </template>
84
85 <script setup lang="ts">
86 -// TODO: refactor
86 +// TODO-FE: refactor
87 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
88 import { NButton, NCard, NEmpty, NGi, NGrid, NInput, NSelect, NSpin, useMessage } from "naive-ui"
89 -import { onMounted, ref } from "vue"
89 +import { onBeforeMount, ref } from "vue"
90 import Api from "@/api"
91 import Icon from "@/components/common/Icon.vue"
92 import GitHubAuditCard from "./GitHubAuditCard.vue"
@@ -170,7 +170,7 @@ function onConfigSaved() {
170 loadConfigs()
171 }
172
173 -onMounted(() => {
173 +onBeforeMount(() => {
174 loadCustomers()
175 loadConfigs()
176 })
frontend/src/components/githubAudit/GitHubAuditGradeBadge.vue
+1 -1
@@ -5,7 +5,7 @@
5 </template>
6
7 <script setup lang="ts">
8 -// TODO: refactor
8 +// TODO-FE: refactor
9 import { NTag } from "naive-ui"
10 import { computed } from "vue"
11
frontend/src/components/githubAudit/GitHubAuditInfo.vue
+1 -1
@@ -216,7 +216,7 @@
216 </template>
217
218 <script setup lang="ts">
219 -// TODO: refactor
219 +// TODO-FE: refactor
220 import {
221 NAlert,
222 NCard,
frontend/src/components/githubAudit/GitHubAuditList.vue
+4 -4
@@ -63,7 +63,7 @@
63 <n-grid v-else :cols="2" :x-gap="16" :y-gap="16">
64 <n-gi v-for="config in configs" :key="config.id">
65 <GitHubAuditCard
66 - :config="config"
66 + :config
67 @click="openDetail(config)"
68 @edit="openEditForm"
69 @audit-complete="loadConfigs"
@@ -93,10 +93,10 @@
93 </template>
94
95 <script setup lang="ts">
96 -// TODO: refactor
96 +// TODO-FE: refactor
97 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
98 import { NButton, NCard, NEmpty, NGi, NGrid, NIcon, NInput, NSelect, NSpace, NSpin, useMessage } from "naive-ui"
99 -import { onMounted, ref } from "vue"
99 +import { onBeforeMount, ref } from "vue"
100 import Api from "@/api"
101 import Icon from "@/components/common/Icon.vue"
102 import GitHubAuditCard from "./GitHubAuditCard.vue"
@@ -199,7 +199,7 @@ function onConfigSaved() {
199 loadConfigs()
200 }
201
202 -onMounted(() => {
202 +onBeforeMount(() => {
203 loadCustomers()
204 loadConfigs()
205 })
frontend/src/components/githubAudit/GitHubAuditReportCard.vue
+1 -1
@@ -31,7 +31,7 @@
31 </template>
32
33 <script setup lang="ts">
34 -// TODO: refactor
34 +// TODO-FE: refactor
35 import type { GitHubAuditReportSummary } from "@/types/githubAudit.d"
36 import { NCard, NTag } from "naive-ui"
37 import { computed } from "vue"
frontend/src/components/githubAudit/GitHubAuditReportDetail.vue
+1 -1
@@ -181,7 +181,7 @@
181 </template>
182
183 <script setup lang="ts">
184 -// TODO: refactor
184 +// TODO-FE: refactor
185 import type { GitHubAuditReport } from "@/types/githubAudit.d"
186 import {
187 NAlert,
frontend/src/components/githubAudit/GitHubAuditStats.vue
+1 -1
@@ -22,7 +22,7 @@
22 </template>
23
24 <script setup lang="ts">
25 -// TODO: refactor
25 +// TODO-FE: refactor
26 import { NGi, NGrid, NStatistic } from "naive-ui"
27 import { computed } from "vue"
28
frontend/src/components/graylog/Alerts/List.vue
+6 -6
@@ -28,9 +28,9 @@
28 v-model:page="currentPage"
29 v-model:page-size="pageSize"
30 :item-count="total"
31 - :page-slot="pageSlot"
32 - :show-size-picker="showSizePicker"
33 - :page-sizes="pageSizes"
31 + :page-slot
32 + :show-size-picker
33 + :page-sizes
34 :simple="simpleMode"
35 />
36 <n-select v-if="!compactMode" v-model:value="timerange" size="small" :options="timeOptions" class="w-32!" />
@@ -55,7 +55,7 @@
55 <AlertsEventItem
56 v-for="alertsEvent of alertsEvents"
57 :key="alertsEvent.event.id"
58 - :alerts-event="alertsEvent"
58 + :alerts-event
59 @click-event="gotoEventsPage($event)"
60 />
61 </template>
@@ -67,7 +67,7 @@
67 <n-pagination
68 v-if="alertsEvents.length > 3"
69 v-model:page="currentPage"
70 - :page-size="pageSize"
70 + :page-size
71 :item-count="total"
72 :page-slot="6"
73 />
@@ -76,7 +76,7 @@
76 </template>
77
78 <script setup lang="ts">
79 -// TODO: refactor
79 +// TODO-FE: refactor
80 import type { AlertsEventElement, AlertsQuery } from "@/types/graylog/alerts.d"
81 import { useResizeObserver } from "@vueuse/core"
82 import { NButton, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
frontend/src/components/graylog/Events/List.vue
+2 -7
@@ -31,12 +31,7 @@
31 </div>
32 <div class="my-3 flex min-h-52 flex-col gap-2">
33 <template v-if="itemsPaginated.length">
34 - <EventItem
35 - v-for="event of itemsPaginated"
36 - :key="event.id"
37 - :event="event"
38 - :highlight="event.id === highlight"
39 - />
34 + <EventItem v-for="event of itemsPaginated" :key="event.id" :event :highlight="event.id === highlight" />
35 </template>
36 <template v-else>
37 <n-empty v-if="!loading" description="No items found" class="h-48 justify-center" />
@@ -69,7 +64,7 @@ const total = ref(0)
64 const loading = ref(false)
65 const events = ref<EventDefinition[]>([])
66 const priorities = computed<SelectMixedOption[]>(() =>
72 - [...new Set(events.value.map(o => o.priority))].map(o => ({
67 + Array.from(new Set(events.value.map(o => o.priority)), o => ({
68 label: `Priority ${o.toString()}`,
69 value: o
70 }))
frontend/src/components/graylog/Inputs/Item.vue
+2 -2
@@ -45,13 +45,13 @@
45 </template>
46 Details
47 </n-button>
48 - <n-button v-if="isRunning" :loading="loading" type="warning" size="small" secondary @click="stop()">
48 + <n-button v-if="isRunning" :loading type="warning" size="small" secondary @click="stop()">
49 <template #icon>
50 <Icon :name="StopIcon" />
51 </template>
52 Stop input
53 </n-button>
54 - <n-button v-else :loading="loading" type="success" secondary size="small" @click="start()">
54 + <n-button v-else :loading type="success" secondary size="small" @click="start()">
55 <template #icon>
56 <Icon :name="StartIcon" />
57 </template>
frontend/src/components/graylog/Inputs/List.vue
+1 -1
@@ -40,7 +40,7 @@
40 v-for="input of itemsFiltered"
41 :key="input.id"
42 embedded
43 - :input="input"
43 + :input
44 @updated="getData('running')"
45 />
46 </template>
frontend/src/components/graylog/Messages/List.vue
+2 -2
@@ -20,7 +20,7 @@
20 </div>
21 </n-popover>
22 </div>
23 - <n-pagination v-model:page="currentPage" :page-size="pageSize" :item-count="total" :page-slot="5" />
23 + <n-pagination v-model:page="currentPage" :page-size :item-count="total" :page-slot="5" />
24 </div>
25 <div class="my-3 flex min-h-52 flex-col gap-2">
26 <template v-if="messages.length">
@@ -34,7 +34,7 @@
34 <n-pagination
35 v-if="messages.length > 3"
36 v-model:page="currentPage"
37 - :page-size="pageSize"
37 + :page-size
38 :item-count="total"
39 :page-slot="6"
40 />
frontend/src/components/graylog/Metrics/List.vue
+1 -1
@@ -32,7 +32,7 @@
32 </template>
33
34 <script setup lang="ts">
35 -// TODO: refactor
35 +// TODO-FE: refactor
36 import type { ThroughputMetric } from "@/types/graylog/metrics.d"
37 import _groupBy from "lodash/groupBy"
38 import _map from "lodash/map"
frontend/src/components/graylog/Metrics/UncommittedEntries.vue
+1 -1
@@ -6,7 +6,7 @@
6 <Icon v-if="isWarning" :name="DangerIcon" />
7 <span>Uncommitted Journal Entries</span>
8 </div>
9 - <apexchart height="70" :options="options" :series="series"></apexchart>
9 + <apexchart height="70" :options :series></apexchart>
10 </div>
11 <div class="value flex flex-col justify-center" :class="{ warning: isWarning }">
12 <span>{{ value }}</span>
frontend/src/components/graylog/MonitoringAlerts/CustomAlertButton.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button size="small" type="primary" secondary :loading="loading" @click="showForm = true">
2 + <n-button size="small" type="primary" secondary :loading @click="showForm = true">
3 <div class="flex items-center gap-2">
4 <Icon :name="DangerIcon" :size="18" />
5 <span class="xs:block hidden">Custom Alert</span>
frontend/src/components/graylog/MonitoringAlerts/CustomAlertForm.vue
+5 -3
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-2">
5 <div class="flex gap-4">
6 <n-form-item label="Priority" path="alert_priority" class="w-28">
@@ -320,11 +320,13 @@ const isValid = computed(() => {
320 return valid
321 })
322
323 +const INTEGER_STRING_REGEX = /^\d*$/
324 +
325 function validatorNumber(fieldName: string, defaultMessage?: string) {
326 return (_rule: FormItemRule, value: string) => {
327 if (!value) {
328 return new Error(defaultMessage || `${fieldName} is required`)
327 - } else if (!/^\d*$/.test(value)) {
329 + } else if (!INTEGER_STRING_REGEX.test(value)) {
330 return new Error(`${fieldName} should be an integer`)
331 } else if (Number(value) < 1) {
332 return new Error(`${fieldName} should be above 1`)
@@ -356,7 +358,7 @@ function addCustomFiled() {
358 form.value.custom_fields.push({
359 name: "",
360 value: "",
359 - key: new Date().getTime()
361 + key: Date.now()
362 })
363 }
364
frontend/src/components/graylog/MonitoringAlerts/Item.vue
+1 -1
@@ -76,7 +76,7 @@
76 </template>
77
78 <script setup lang="ts">
79 -// TODO: refactor
79 +// TODO-FE: refactor
80 import type { FormRules, FormValidationError } from "naive-ui"
81 import type { ProvisionsMonitoringAlertParams } from "@/api/endpoints/monitoringAlerts"
82 import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
frontend/src/components/graylog/MonitoringAlerts/List.vue
+6 -6
@@ -28,9 +28,9 @@
28 <n-pagination
29 v-model:page="currentPage"
30 v-model:page-size="pageSize"
31 - :page-slot="pageSlot"
32 - :show-size-picker="showSizePicker"
33 - :page-sizes="pageSizes"
31 + :page-slot
32 + :show-size-picker
33 + :page-sizes
34 :item-count="total"
35 :simple="simpleMode"
36 />
@@ -41,7 +41,7 @@
41 <MonitoringAlert
42 v-for="alert of itemsPaginated"
43 :key="alert.name"
44 - :alert="alert"
44 + :alert
45 :is-enabled="isEnabled(alert)"
46 @provisioned="getData()"
47 />
@@ -55,7 +55,7 @@
55 <n-pagination
56 v-if="itemsPaginated.length > 3"
57 v-model:page="currentPage"
58 - :page-size="pageSize"
58 + :page-size
59 :item-count="total"
60 :page-slot="6"
61 />
@@ -64,7 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 -// TODO: refactor
67 +// TODO-FE: refactor
68 import type { EventDefinition } from "@/types/graylog/event-definition.d"
69 import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
70 import { NButton, NEmpty, NPagination, NPopover, NSpin, useMessage } from "naive-ui"
frontend/src/components/graylog/Pipelines/PipeDetails.vue
+1 -1
@@ -41,7 +41,7 @@
41 </template>
42
43 <script setup lang="ts">
44 -// TODO: refactor
44 +// TODO-FE: refactor
45 import type { RuleExtended } from "./RulesSmallList.vue"
46 import type { PipelineFull, PipelineFullStage } from "@/types/graylog/pipelines.d"
47 import { NButton, NPopover, NScrollbar, NTimeline, NTimelineItem } from "naive-ui"
frontend/src/components/graylog/Pipelines/PipeList.vue
+1 -1
@@ -46,7 +46,7 @@
46 </template>
47
48 <script setup lang="ts">
49 -// TODO: refactor
49 +// TODO-FE: refactor
50 import type { PipelineFull } from "@/types/graylog/pipelines.d"
51 import { NButton, NCard, NCollapse, NCollapseItem, NEmpty, NModal, NSpin, useMessage } from "naive-ui"
52 import { onBeforeMount, ref, watch } from "vue"
frontend/src/components/graylog/Pipelines/RulesList.vue
+1 -1
@@ -2,7 +2,7 @@
2 <n-spin :show="loading" class="rules-list">
3 <n-scrollbar ref="scrollContent">
4 <div class="list flex min-h-52 flex-col gap-2">
5 - <Rule v-for="rule of rules" :key="rule.id" :rule="rule" :highlight="highlight === rule.id" embedded />
5 + <Rule v-for="rule of rules" :key="rule.id" :rule :highlight="highlight === rule.id" embedded />
6 </div>
7 </n-scrollbar>
8 </n-spin>
frontend/src/components/graylog/Streams/Item.vue
+2 -2
@@ -49,7 +49,7 @@
49 </n-button>
50 <n-button
51 v-if="stream.is_editable && !stream.disabled"
52 - :loading="loading"
52 + :loading
53 type="warning"
54 secondary
55 size="small"
@@ -62,7 +62,7 @@
62 </n-button>
63 <n-button
64 v-if="stream.is_editable && stream.disabled"
65 - :loading="loading"
65 + :loading
66 type="success"
67 secondary
68 size="small"
frontend/src/components/graylog/Streams/List.vue
+6 -6
@@ -27,9 +27,9 @@
27 <n-pagination
28 v-model:page="currentPage"
29 v-model:page-size="pageSize"
30 - :page-slot="pageSlot"
31 - :show-size-picker="showSizePicker"
32 - :page-sizes="pageSizes"
30 + :page-slot
31 + :show-size-picker
32 + :page-sizes
33 :item-count="total"
34 :simple="simpleMode"
35 />
@@ -72,7 +72,7 @@
72 </div>
73 <div class="my-3 flex min-h-52 flex-col gap-2">
74 <template v-if="itemsPaginated.length">
75 - <StreamItem v-for="stream of itemsPaginated" :key="stream.id" :stream="stream" />
75 + <StreamItem v-for="stream of itemsPaginated" :key="stream.id" :stream />
76 </template>
77 <template v-else>
78 <n-empty v-if="!loading" description="No items found" class="h-48 justify-center" />
@@ -82,7 +82,7 @@
82 <n-pagination
83 v-if="itemsPaginated.length > 3"
84 v-model:page="currentPage"
85 - :page-size="pageSize"
85 + :page-size
86 :item-count="total"
87 :page-slot="6"
88 />
@@ -91,7 +91,7 @@
91 </template>
92
93 <script setup lang="ts">
94 -// TODO: refactor
94 +// TODO-FE: refactor
95 import type { Stream } from "@/types/graylog/stream.d"
96 import { useResizeObserver } from "@vueuse/core"
97 import { NButton, NDivider, NEmpty, NPagination, NPopover, NSelect, NSpin, useMessage } from "naive-ui"
frontend/src/components/healthcheck/HealthcheckItem.vue
+3 -1
@@ -52,8 +52,10 @@ import dayjs from "@/utils/dayjs"
52
53 const { alert } = defineProps<{ alert: InfluxDBAlert }>()
54
55 +const NEWLINE_REGEX = /\r?\n/g
56 +
57 const formattedMessage = computed(() => {
56 - return alert.message.replace(/\r?\n/g, " <span class='mx-1'>•</span> ")
58 + return alert.message.replace(NEWLINE_REGEX, " <span class='mx-1'>•</span> ")
59 })
60
61 const statusType = computed(() => {
frontend/src/components/healthcheck/HealthcheckList.vue
+6 -6
@@ -55,9 +55,9 @@
55 <n-pagination
56 v-model:page="currentPage"
57 v-model:page-size="pageSize"
58 - :page-slot="pageSlot"
59 - :show-size-picker="showSizePicker"
60 - :page-sizes="pageSizes"
58 + :page-slot
59 + :show-size-picker
60 + :page-sizes
61 :item-count="total"
62 :simple="simpleMode"
63 />
@@ -68,7 +68,7 @@
68 <HealthcheckItem
69 v-for="alert of itemsPaginated"
70 :key="(alert.check_id || '') + alert.time"
71 - :alert="alert"
71 + :alert
72 class="item-appear item-appear-bottom item-appear-005"
73 />
74 </template>
@@ -81,7 +81,7 @@
81 <n-pagination
82 v-if="itemsPaginated.length > 3"
83 v-model:page="currentPage"
84 - :page-size="pageSize"
84 + :page-size
85 :item-count="total"
86 :page-slot="6"
87 />
@@ -90,7 +90,7 @@
90 </template>
91
92 <script setup lang="ts">
93 -// TODO: refactor
93 +// TODO-FE: refactor
94 import type { InfluxDBAlert, InfluxDBAlertResponse } from "@/types/healthchecks.d"
95 import { useResizeObserver } from "@vueuse/core"
96 import _orderBy from "lodash/orderBy"
frontend/src/components/incidentManagement/alerts/AlertAsset.vue
+13 -3
@@ -47,7 +47,7 @@
47 v-model:show="showDetails"
48 preset="card"
49 content-class="p-0!"
50 - :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
50 + :style="{ maxWidth: 'min(825px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
51 :bordered="false"
52 :title="assetNameTruncated"
53 segmented
@@ -61,7 +61,7 @@
61 })()
62 "
63 />
64 - <n-spin :show="!licenseChecked" content-class="flex flex-wrap justify-end gap-3 p-6" :size="18">
64 + <n-spin :show="!licenseChecked" content-class="flex flex-wrap gap-3 px-6 py-4" :size="18">
65 <AIVelociraptorArtifactRecommendationButton
66 :index-id="asset.index_id"
67 :index-name="asset.index_name"
@@ -135,6 +135,16 @@
135 </div>
136 </div>
137 </n-tab-pane>
138 + <n-tab-pane
139 + v-if="isWazuhSource"
140 + name="CoPilot Searches"
141 + tab="CoPilot Searches"
142 + display-directive="show:lazy"
143 + >
144 + <div class="p-7 pt-4">
145 + <AlertAssetSearches :asset />
146 + </div>
147 + </n-tab-pane>
148 <n-tab-pane
149 v-if="isWazuhSource"
150 name="Artifact Collection"
@@ -199,7 +209,7 @@ const { asset, embedded, badge } = defineProps<{ asset: AlertAsset; embedded?: b
209
210 const AlertAssetInfo = defineAsyncComponent(() => import("./AlertAssetInfo.vue"))
211 const AlertDetailTimeline = defineAsyncComponent(() => import("./AlertDetailTimeline.vue"))
202 -// const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
212 +const AlertAssetSearches = defineAsyncComponent(() => import("@/components/copilotSearches/AlertAssetSearches.vue"))
213 const AIAnalystButton = defineAsyncComponent(() => import("@/components/threatIntel/AIAnalystButton.vue"))
214 const AIWazuhExclusionRuleButton = defineAsyncComponent(
215 () => import("@/components/threatIntel/AIWazuhExclusionRuleButton.vue")
frontend/src/components/incidentManagement/alerts/AlertAssetInfo.vue
+2 -2
@@ -1,6 +1,6 @@
1 <template>
2 <div class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
3 - <CardKV v-for="(value, key) of asset" :key="key">
3 + <CardKV v-for="(value, key) of asset" :key>
4 <template #key>
5 {{ key }}
6 </template>
@@ -53,7 +53,7 @@
53 <n-tabs type="line" animated :tabs-padding="24">
54 <n-tab-pane name="Info" tab="Info" display-directive="show">
55 <div class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
56 - <CardKV v-for="(value, key) of alertDetailsInfo" :key="key">
56 + <CardKV v-for="(value, key) of alertDetailsInfo" :key>
57 <template #key>
58 {{ key }}
59 </template>
frontend/src/components/incidentManagement/alerts/AlertDetailTimelineItem.vue
+1 -1
@@ -16,7 +16,7 @@
16 <n-tabs type="line" animated :tabs-padding="24">
17 <n-tab-pane name="Info" tab="Info" display-directive="show">
18 <div class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
19 - <CardKV v-for="(value, key) of timelineDetailsInfo" :key="key">
19 + <CardKV v-for="(value, key) of timelineDetailsInfo" :key>
20 <template #key>
21 {{ key }}
22 </template>
frontend/src/components/incidentManagement/alerts/AlertIoCsForm.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="submitting" class="flex min-h-48 grow flex-col" content-class="flex grow flex-col gap-4">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-3">
5 <div>
6 <n-form-item label="Description" path="ioc_description">
frontend/src/components/incidentManagement/alerts/AlertItem.vue
+1 -1
@@ -12,7 +12,7 @@
12 <template v-if="alert" #headerMain>
13 <div class="flex items-center gap-4">
14 <div v-if="selectable">
15 - <n-checkbox :checked="checked" @click.stop="emit('check', !checked)" />
15 + <n-checkbox :checked @click.stop="emit('check', !checked)" />
16 </div>
17 <div
18 class="flex cursor-pointer items-center gap-2"
frontend/src/components/incidentManagement/alerts/AlertMergeCaseButton.vue
+1 -1
@@ -110,7 +110,7 @@ function getCasesList() {
110 loadingCases.value = true
111
112 Api.incidentManagement.cases
113 - .getCasesList()
113 + .getCasesList(undefined, { page: 1, pageSize: 9999 })
114 .then(res => {
115 if (res.data.success) {
116 linkableCases.value = _orderBy(res.data?.cases || [], ["id"], ["desc"])
frontend/src/components/incidentManagement/alerts/AlertStatusSwitch.vue
+1 -1
@@ -8,7 +8,7 @@
8 scrollable
9 to="body"
10 >
11 - <slot :loading="loading" />
11 + <slot :loading />
12 </n-popselect>
13 </template>
14
frontend/src/components/incidentManagement/alerts/AlertTags.vue
+1 -8
@@ -6,14 +6,7 @@
6
7 <n-dynamic-tags size="small" :value="[]" @create="newAlertTag">
8 <template #trigger="{ activate, disabled }">
9 - <n-button
10 - size="tiny"
11 - type="primary"
12 - dashed
13 - :loading="creatingTag"
14 - :disabled="disabled"
15 - @click="activate()"
16 - >
9 + <n-button size="tiny" type="primary" dashed :loading="creatingTag" :disabled @click="activate()">
10 <template #icon>
11 <Icon :name="AddIcon" />
12 </template>
frontend/src/components/incidentManagement/alerts/AlertsFilters.vue
+1 -1
@@ -153,7 +153,7 @@
153 </template>
154
155 <script setup lang="ts">
156 -// TODO: refactor
156 +// TODO-FE: refactor
157 import type { Ref } from "vue"
158 import type { AlertsListFilter } from "./types.d"
159 import type { AlertsFilterTypes, AlertsListFilterValue } from "@/api/endpoints/incidentManagement/alerts"
frontend/src/components/incidentManagement/alerts/AlertsList.vue
+115 -103
@@ -1,116 +1,128 @@
1 <template>
2 - <div class="alerts-list">
3 - <div ref="header" class="flex items-center justify-end gap-2">
4 - <div class="info flex grow gap-2 lg:hidden!">
5 - <n-popover overlap placement="left">
6 - <template #trigger>
7 - <div class="bg-default rounded-lg">
8 - <n-button size="small" class="cursor-help!">
9 - <template #icon>
10 - <Icon :name="InfoIcon" />
11 - </template>
12 - </n-button>
2 + <div class="alerts-list @container">
3 + <div ref="header" class="flex items-center justify-between gap-2">
4 + <div class="flex items-center gap-2">
5 + <div class="flex grow gap-2 @6xl:hidden!">
6 + <n-popover overlap placement="left">
7 + <template #trigger>
8 + <div class="bg-default rounded-lg">
9 + <n-button size="small" class="cursor-help!">
10 + <template #icon>
11 + <Icon :name="InfoIcon" />
12 + </template>
13 + </n-button>
14 + </div>
15 + </template>
16 + <div class="flex flex-col gap-2">
17 + <div class="box">
18 + Total :
19 + <code>
20 + <span v-if="totalFiltered === total">{{ total }}</span>
21 + <span v-else>
22 + <span>{{ totalFiltered }}</span>
23 + <span class="opacity-50">/</span>
24 + <span class="opacity-50">{{ total }}</span>
25 + </span>
26 + </code>
27 + </div>
28 + <div class="box text-error">
29 + Open :
30 + <code>{{ statusOpenTotal }}</code>
31 + </div>
32 + <div class="box text-warning">
33 + In Progress :
34 + <code>{{ statusInProgressTotal }}</code>
35 + </div>
36 + <div class="box text-success">
37 + Close :
38 + <code>{{ statusCloseTotal }}</code>
39 + </div>
40 </div>
14 - </template>
15 - <div class="flex flex-col gap-2">
16 - <div class="box">
17 - Total :
18 - <code>
41 + </n-popover>
42 + </div>
43 + <div class="hidden grow items-center gap-1 text-sm @6xl:flex">
44 + <n-button quaternary size="small" @click="filtersCTX?.setFilter([{ type: 'status', value: null }])">
45 + <div class="flex items-center gap-2">
46 + <span>Total</span>
47 + <code class="py-1">
48 <span v-if="totalFiltered === total">{{ total }}</span>
20 - <span v-else>
49 + <span v-else class="flex gap-1">
50 <span>{{ totalFiltered }}</span>
51 <span class="opacity-50">/</span>
52 <span class="opacity-50">{{ total }}</span>
53 </span>
54 </code>
55 </div>
27 - <div class="box text-error">
28 - Open :
29 - <code>{{ statusOpenTotal }}</code>
56 + </n-button>
57 + <span>/</span>
58 + <n-button
59 + quaternary
60 + size="small"
61 + @click="filtersCTX?.setFilter([{ type: 'status', value: 'OPEN' }])"
62 + >
63 + <div class="flex items-center gap-2">
64 + <span>Open</span>
65 + <code class="text-error py-1">{{ statusOpenTotal }}</code>
66 </div>
31 - <div class="box text-warning">
32 - In Progress :
33 - <code>{{ statusInProgressTotal }}</code>
67 + </n-button>
68 + <span>/</span>
69 + <n-button
70 + quaternary
71 + size="small"
72 + @click="filtersCTX?.setFilter([{ type: 'status', value: 'IN_PROGRESS' }])"
73 + >
74 + <div class="flex items-center gap-2">
75 + <span>In Progress</span>
76 + <code class="text-warning py-1">{{ statusInProgressTotal }}</code>
77 </div>
35 - <div class="box text-success">
36 - Close :
37 - <code>{{ statusCloseTotal }}</code>
78 + </n-button>
79 + <span>/</span>
80 + <n-button
81 + quaternary
82 + size="small"
83 + @click="filtersCTX?.setFilter([{ type: 'status', value: 'CLOSED' }])"
84 + >
85 + <div class="flex items-center gap-2">
86 + <span>Close</span>
87 + <code class="text-success py-1">{{ statusCloseTotal }}</code>
88 </div>
39 - </div>
40 - </n-popover>
41 - </div>
42 - <div class="info hidden grow items-center gap-1 text-sm lg:flex">
43 - <n-button quaternary size="small" @click="filtersCTX?.setFilter([{ type: 'status', value: null }])">
44 - <div class="flex items-center gap-2">
45 - <span>Total</span>
46 - <code class="py-1">
47 - <span v-if="totalFiltered === total">{{ total }}</span>
48 - <span v-else class="flex gap-1">
49 - <span>{{ totalFiltered }}</span>
50 - <span class="opacity-50">/</span>
51 - <span class="opacity-50">{{ total }}</span>
52 - </span>
53 - </code>
54 - </div>
55 - </n-button>
56 - <span>/</span>
57 - <n-button quaternary size="small" @click="filtersCTX?.setFilter([{ type: 'status', value: 'OPEN' }])">
58 - <div class="flex items-center gap-2">
59 - <span>Open</span>
60 - <code class="text-error py-1">{{ statusOpenTotal }}</code>
61 - </div>
62 - </n-button>
63 - <span>/</span>
64 - <n-button
65 - quaternary
66 - size="small"
67 - @click="filtersCTX?.setFilter([{ type: 'status', value: 'IN_PROGRESS' }])"
68 - >
89 + </n-button>
90 + </div>
91 + <n-button size="small" ghost type="error" @click="showDeleteByTitleModal = true">
92 <div class="flex items-center gap-2">
70 - <span>In Progress</span>
71 - <code class="text-warning py-1">{{ statusInProgressTotal }}</code>
72 - </div>
73 - </n-button>
74 - <span>/</span>
75 - <n-button quaternary size="small" @click="filtersCTX?.setFilter([{ type: 'status', value: 'CLOSED' }])">
76 - <div class="flex items-center gap-2">
77 - <span>Close</span>
78 - <code class="text-success py-1">{{ statusCloseTotal }}</code>
93 + <Icon :name="TrashIcon" />
94 + <span class="hidden @3xl:inline">Bulk Delete</span>
95 </div>
96 </n-button>
97 </div>
82 - <n-pagination
83 - v-model:page="currentPage"
84 - v-model:page-size="pageSize"
85 - :page-slot="pageSlot"
86 - :show-size-picker="showSizePicker"
87 - :page-sizes="pageSizes"
88 - :item-count="totalFiltered"
89 - :simple="simpleMode"
90 - />
91 - <n-select
92 - v-model:value="sort"
93 - size="small"
94 - :options="sortOptions"
95 - :show-checkmark="false"
96 - class="max-w-20"
97 - :disabled="loading"
98 - />
98
100 - <n-badge v-if="showFilters" :show="filtered" dot type="success" :offset="[-4, 0]">
101 - <n-button size="small" secondary @click="showFiltersView = !showFiltersView">
102 - <template #icon>
103 - <Icon :name="FilterIcon" />
104 - </template>
105 - </n-button>
106 - </n-badge>
99 + <div class="flex items-center justify-end gap-2 whitespace-nowrap">
100 + <n-pagination
101 + v-model:page="currentPage"
102 + v-model:page-size="pageSize"
103 + :page-slot
104 + :show-size-picker
105 + :page-sizes
106 + :item-count="totalFiltered"
107 + :simple="simpleMode"
108 + />
109 + <n-select
110 + v-model:value="sort"
111 + size="small"
112 + :options="sortOptions"
113 + :show-checkmark="false"
114 + class="max-w-20"
115 + :disabled="loading"
116 + />
117
108 - <n-button size="small" secondary @click="showDeleteByTitleModal = true">
109 - <template #icon>
110 - <Icon :name="TrashIcon" />
111 - </template>
112 - <span class="hidden sm:inline">Bulk Delete</span>
113 - </n-button>
118 + <n-badge v-if="showFilters" :show="filtered" dot type="success" :offset="[-4, 0]">
119 + <n-button size="small" secondary @click="showFiltersView = !showFiltersView">
120 + <template #icon>
121 + <Icon :name="FilterIcon" />
122 + </template>
123 + </n-button>
124 + </n-badge>
125 + </div>
126 </div>
127
128 <CollapseKeepAlive v-if="showFilters" :show="showFiltersView" embedded arrow="top-right">
@@ -249,7 +261,7 @@
261 <n-pagination
262 v-if="alertsList.length > 3"
263 v-model:page="currentPage"
252 - :page-size="pageSize"
264 + :page-size
265 :item-count="totalFiltered"
266 :page-slot="6"
267 />
@@ -258,7 +270,7 @@
270 </template>
271
272 <script setup lang="ts">
261 -// TODO: refactor
273 +// TODO-FE: refactor
274 import type { AlertsListFilter } from "./types.d"
275 import type { AlertsQuery } from "@/api/endpoints/incidentManagement/alerts"
276 import type { Alert } from "@/types/incidentManagement/alerts.d"
@@ -366,7 +378,7 @@ watch(
378 () => {
379 if (
380 alertsList.value.length &&
369 - !alertsList.value.find(o => o.id.toString() === highlight) &&
381 + !alertsList.value.some(o => o.id.toString() === highlight) &&
382 currentPage.value < totalFiltered.value &&
383 !highlightedItemFound.value
384 ) {
@@ -375,7 +387,7 @@ watch(
387 })
388 }
389
378 - if (alertsList.value.find(o => o.id.toString() === highlight)) {
390 + if (alertsList.value.some(o => o.id.toString() === highlight)) {
391 highlightedItemFound.value = true
392 }
393 },
@@ -399,13 +411,13 @@ function updateAlert(updatedAlert: Alert) {
411 }
412
413 function isChecked(alert: Alert) {
402 - return !!checkedAlerts.value.find(o => o.id === alert.id)
414 + return checkedAlerts.value.some(o => o.id === alert.id)
415 }
416
417 function toggleCheck(alert: Alert) {
418 const alertIndex = checkedAlerts.value.findIndex(o => o.id === alert.id)
419
408 - if (checkedAlerts.value.find(o => o.id === alert.id)) {
420 + if (checkedAlerts.value.some(o => o.id === alert.id)) {
421 checkedAlerts.value.splice(alertIndex, 1)
422 } else {
423 checkedAlerts.value.push(alert)
@@ -427,7 +439,7 @@ function setALlChecked() {
439 function removeChecked(alert: Alert) {
440 const alertIndex = checkedAlerts.value.findIndex(o => o.id === alert.id)
441
430 - if (checkedAlerts.value.find(o => o.id === alert.id)) {
442 + if (checkedAlerts.value.some(o => o.id === alert.id)) {
443 checkedAlerts.value.splice(alertIndex, 1)
444 }
445 }
@@ -495,7 +507,7 @@ function getAvailableUsers() {
507
508 function getCases() {
509 Api.incidentManagement.cases
498 - .getCasesList()
510 + .getCasesList(undefined, { page: 1, pageSize: 9999 })
511 .then(res => {
512 if (res.data.success) {
513 linkableCases.value = _orderBy(res.data?.cases || [], ["id"], ["desc"])
frontend/src/components/incidentManagement/cases/CaseCreationButton.vue
+1 -1
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button :size type="primary" :loading="loading" @click="showForm = true">
2 + <n-button :size type="primary" :loading @click="showForm = true">
3 <template v-if="showIcon" #icon>
4 <Icon :name="NewCaseIcon" :size="14" />
5 </template>
frontend/src/components/incidentManagement/cases/CaseCreationForm.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="customer-provisioning-default-settings-form">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-3">
5 <div>
6 <n-form-item label="Name" path="case_name">
frontend/src/components/incidentManagement/cases/CaseDataStore.vue
+1 -1
@@ -46,7 +46,7 @@
46 <CaseDataStoreItem
47 v-for="dataStoreFile of dataStore"
48 :key="dataStoreFile.id"
49 - :data-store-file="dataStoreFile"
49 + :data-store-file
50 embedded
51 @deleted="deleteDataStoreFile(dataStoreFile)"
52 />
frontend/src/components/incidentManagement/cases/CaseReportButton.vue
+1 -1
@@ -16,7 +16,7 @@
16 segmented
17 >
18 <n-spin :show="exporting" class="creation-report-form">
19 - <n-form ref="formRef" :model="form" :rules="rules">
19 + <n-form ref="formRef" :model="form" :rules>
20 <div class="flex flex-col gap-2">
21 <n-form-item label="Template" path="template_name">
22 <CaseReportTemplateSelect v-model:value="form.template_name" />
frontend/src/components/incidentManagement/cases/CaseStatusSwitch.vue
+1 -1
@@ -8,7 +8,7 @@
8 scrollable
9 to="body"
10 >
11 - <slot :loading="loading" />
11 + <slot :loading />
12 </n-popselect>
13 </template>
14
frontend/src/components/incidentManagement/cases/CasesList.vue
+47 -47
@@ -29,17 +29,13 @@
29 Close :
30 <code>{{ statusCloseTotal }}</code>
31 </div>
32 - <div class="box text-secondary">
33 - N/D :
34 - <code>{{ statusUndefinedTotal }}</code>
35 - </div>
32 </div>
33 </n-popover>
34
35 <n-popover v-if="showMobileMenu" overlap placement="left" display-directive="show">
36 <template #trigger>
37 <div class="bg-default rounded-lg">
42 - <n-button size="small" class="!cursor-pointer">
38 + <n-button size="small">
39 <template #icon>
40 <Icon :name="MenuIcon" />
41 </template>
@@ -64,12 +60,20 @@
60 <n-pagination
61 v-model:page="currentPage"
62 v-model:page-size="pageSize"
67 - :page-slot="pageSlot"
68 - :show-size-picker="showSizePicker"
69 - :page-sizes="pageSizes"
63 + :page-slot
64 + :show-size-picker
65 + :page-sizes
66 :item-count="total"
67 :simple="simpleMode"
68 />
69 + <n-select
70 + v-model:value="sort"
71 + size="small"
72 + :options="sortOptions"
73 + :show-checkmark="false"
74 + class="max-w-20"
75 + :disabled="loading"
76 + />
77 <n-popover v-if="!hideFilters" :show="showFilters" trigger="manual" overlap placement="right" class="px-0!">
78 <template #trigger>
79 <div class="bg-default rounded-lg">
@@ -150,7 +154,7 @@
154 <div class="my-3 flex min-h-52 flex-col gap-2">
155 <template v-if="casesList.length">
156 <CaseItem
153 - v-for="item of itemsPaginated"
157 + v-for="item of casesList"
158 :key="item.id"
159 :case-data="item"
160 :highlight="highlight === item.id.toString()"
@@ -168,9 +172,9 @@
172 </n-spin>
173 <div class="footer flex justify-end">
174 <n-pagination
171 - v-if="itemsPaginated.length > 3"
175 + v-if="casesList.length > 3"
176 v-model:page="currentPage"
173 - :page-size="pageSize"
177 + :page-size
178 :item-count="total"
179 :page-slot="6"
180 />
@@ -179,13 +183,12 @@
183 </template>
184
185 <script setup lang="ts">
182 -// TODO: refactor
186 +// TODO-FE: refactor
187 import type { CasesFilter, CasesFilterTypes } from "@/api/endpoints/incidentManagement/cases"
188 import type { Customer } from "@/types/customers.d"
189 import type { Case, CaseStatus } from "@/types/incidentManagement/cases.d"
190 import { useResizeObserver } from "@vueuse/core"
191 import _cloneDeep from "lodash/cloneDeep"
188 -import _orderBy from "lodash/orderBy"
192 import {
193 NBadge,
194 NButton,
@@ -220,7 +223,6 @@ const casesList = ref<Case[]>([])
223 const availableUsers = ref<string[]>([])
224 const customersList = ref<Customer[]>([])
225
223 -const pageSize = ref(25)
226 const currentPage = ref(1)
227 const simpleMode = ref(false)
228 const caseCreationButtonShowIcon = ref(false)
@@ -228,38 +230,24 @@ const caseExportButtonShowIcon = ref(false)
230 const showMobileMenu = ref(false)
231 const showSizePicker = ref(true)
232 const pageSizes = [10, 25, 50, 100]
233 +const pageSize = ref(pageSizes[1])
234 const header = ref()
235 const pageSlot = ref(8)
236 +const sort = ref<"asc" | "desc">("desc")
237 +const sortOptions = [
238 + { label: "Desc", value: "desc" },
239 + { label: "Asc", value: "asc" }
240 +]
241
234 -const itemsPaginated = computed(() => {
235 - const from = (currentPage.value - 1) * pageSize.value
236 - const to = currentPage.value * pageSize.value
237 -
238 - const list = _orderBy(casesList.value, ["id"], ["desc"])
239 -
240 - return list.slice(from, to)
241 -})
242 +const total = ref(0)
243 +const statusOpenTotal = ref(0)
244 +const statusInProgressTotal = ref(0)
245 +const statusCloseTotal = ref(0)
246
247 const FilterIcon = "carbon:filter-edit"
248 const MenuIcon = "carbon:overflow-menu-horizontal"
249 const InfoIcon = "carbon:information"
250
247 -const total = computed<number>(() => {
248 - return casesList.value.length || 0
249 -})
250 -const statusOpenTotal = computed<number>(() => {
251 - return casesList.value.filter(o => o.case_status === "OPEN").length || 0
252 -})
253 -const statusInProgressTotal = computed<number>(() => {
254 - return casesList.value.filter(o => o.case_status === "IN_PROGRESS").length || 0
255 -})
256 -const statusCloseTotal = computed<number>(() => {
257 - return casesList.value.filter(o => o.case_status === "CLOSED").length || 0
258 -})
259 -const statusUndefinedTotal = computed<number>(() => {
260 - return casesList.value.filter(o => o.case_status === null).length || 0
261 -})
262 -
251 const filters = ref<Partial<CasesListFilter>>({})
252 const lastFilters = ref<Partial<CasesListFilter>>({})
253
@@ -288,8 +276,14 @@ const customersOptions = computed(() =>
276 const highlightedItemFound = ref(!highlight.value)
277 const highlightedItemOpened = ref(!highlight.value)
278
279 +watch([currentPage, sort], () => {
280 + getData()
281 +})
282 +
283 watch(pageSize, () => {
292 - if (currentPage.value !== 1) {
284 + if (currentPage.value === 1) {
285 + getData()
286 + } else {
287 currentPage.value = 1
288 }
289 })
@@ -310,14 +304,12 @@ watch(
304 )
305
306 watch(
313 - itemsPaginated,
307 + casesList,
308 () => {
315 - const totalPages = Math.ceil(casesList.value.length / pageSize.value)
316 -
309 if (
318 - itemsPaginated.value.length &&
319 - !itemsPaginated.value.find(o => o.id.toString() === highlight.value) &&
320 - currentPage.value < totalPages &&
310 + casesList.value.length &&
311 + !casesList.value.some(o => o.id.toString() === highlight.value) &&
312 + currentPage.value < Math.ceil(total.value / pageSize.value) &&
313 !highlightedItemFound.value
314 ) {
315 nextTick(() => {
@@ -325,7 +317,7 @@ watch(
317 })
318 }
319
328 - if (itemsPaginated.value.find(o => o.id.toString() === highlight.value)) {
320 + if (casesList.value.some(o => o.id.toString() === highlight.value)) {
321 highlightedItemFound.value = true
322 }
323 },
@@ -361,10 +353,18 @@ function getData() {
353 }
354
355 Api.incidentManagement.cases
364 - .getCasesList(query)
356 + .getCasesList(query, {
357 + page: currentPage.value,
358 + pageSize: pageSize.value,
359 + order: sort.value
360 + })
361 .then(res => {
362 if (res.data.success) {
363 casesList.value = res.data?.cases || []
364 + total.value = res.data.total || 0
365 + statusOpenTotal.value = res.data.open || 0
366 + statusInProgressTotal.value = res.data.in_progress || 0
367 + statusCloseTotal.value = res.data.closed || 0
368 } else {
369 message.warning(res.data?.message || "An error occurred. Please try again later.")
370 }
frontend/src/components/incidentManagement/exclusionRules/ExclusionRuleForm.vue
+3 -5
@@ -94,9 +94,7 @@
94 </div>
95 <div class="flex gap-4">
96 <n-button :disabled="loading" @click="reset()">Reset</n-button>
97 - <n-button type="primary" :disabled="!isValid" :loading="loading" @click="validate()">
98 - Submit
99 - </n-button>
97 + <n-button type="primary" :disabled="!isValid" :loading @click="validate()">Submit</n-button>
98 </div>
99 </div>
100 </div>
@@ -257,7 +255,7 @@ function getDefaultModel(entity?: Partial<ExclusionRule>): Model {
255 title: entity?.title || "",
256 field_matches: entity?.field_matches
257 ? Object.entries(entity.field_matches).map(o => ({ key: o[0], value: o[1], id: o[0] }))
260 - : [{ id: `${new Date().getTime()}`, key: null, value: null }],
258 + : [{ id: `${Date.now()}`, key: null, value: null }],
259 customer_code: entity?.customer_code || undefined,
260 enabled: entity?.enabled || false
261 }
@@ -272,7 +270,7 @@ function reset(force?: boolean) {
270
271 function addField() {
272 model.value.field_matches.push({
275 - id: `${new Date().getTime()}`,
273 + id: `${Date.now()}`,
274 key: null,
275 value: null
276 })
frontend/src/components/incidentManagement/exclusionRules/ExclusionRulesList.vue
+5 -5
@@ -54,9 +54,9 @@
54 <n-pagination
55 v-model:page="currentPage"
56 v-model:page-size="pageSize"
57 - :page-slot="pageSlot"
58 - :show-size-picker="showSizePicker"
59 - :page-sizes="pageSizes"
57 + :page-slot
58 + :show-size-picker
59 + :page-sizes
60 :item-count="total"
61 :simple="simpleMode"
62 />
@@ -84,7 +84,7 @@
84 <n-pagination
85 v-if="list.length > 3"
86 v-model:page="currentPage"
87 - :page-size="pageSize"
87 + :page-size
88 :item-count="total"
89 :page-slot="6"
90 />
@@ -93,7 +93,7 @@
93 </template>
94
95 <script setup lang="ts">
96 -// TODO: refactor
96 +// TODO-FE: refactor
97 import type { ExclusionRulesQuery } from "@/api/endpoints/incidentManagement/exclusionRules"
98 import type { ExclusionRule } from "@/types/incidentManagement/exclusionRules.d"
99 import { useResizeObserver } from "@vueuse/core"
frontend/src/components/incidentManagement/sources/SourceConfigurationForm.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="creation-report-form">
3 - <n-form ref="formRef" :model="form" :rules="rules">
3 + <n-form ref="formRef" :model="form" :rules>
4 <div class="flex flex-col gap-8">
5 <div class="flex flex-col gap-2">
6 <n-form-item v-if="showIndexNameField" label="Index name" path="index_name">
frontend/src/components/incidentManagement/sources/SourceConfigurationWizard.vue
+1 -1
@@ -4,7 +4,7 @@
4 <div class="flex grow flex-col">
5 <n-scrollbar x-scrollable trigger="none">
6 <div class="p-7 pt-4">
7 - <n-steps :current="current" size="small" :status="currentStatus">
7 + <n-steps :current size="small" :status="currentStatus">
8 <n-step title="Source" />
9 <n-step title="Configuration" />
10 </n-steps>
frontend/src/components/indices/ClusterHealth.vue
+3 -1
@@ -67,8 +67,10 @@ const propsOrder = ref<Array<keyof ClusterHealth>>([
67 "unassigned_shards"
68 ])
69
70 +const UNDERSCORE_REGEX = /_/g
71 +
72 function sanitizeLabel(label: string) {
71 - return label.replace(/_/g, " ")
73 + return label.replace(UNDERSCORE_REGEX, " ")
74 }
75
76 function getClusterHealth() {
frontend/src/components/indices/CustomerIndicesSize.vue
+2 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-card title="Storage per Customer" :bordered="bordered">
2 + <n-card title="Storage per Customer" :bordered>
3 <n-spin :show="loading">
4 <div v-if="customerSizes && customerSizes.length > 0" class="customer-list">
5 <div v-for="customer in customerSizes" :key="customer.customer" class="customer-item">
@@ -50,7 +50,7 @@
50 </template>
51
52 <script lang="ts" setup>
53 -// TODO: refactor
53 +// TODO-FE: refactor
54 import { NCard, NEmpty, NPopover, NProgress, NScrollbar, NSpin, NTag, NText, useMessage, useThemeVars } from "naive-ui"
55 import { computed, onBeforeMount, ref } from "vue"
56 import Api from "@/api"
frontend/src/components/indices/Marquee.vue
+1 -1
@@ -42,7 +42,7 @@
42 </template>
43
44 <script setup lang="ts">
45 -// TODO: refactor
45 +// TODO-FE: refactor
46 import type { IndexStats } from "@/types/indices.d"
47 import { NCard, NEmpty, NSpin, useMessage } from "naive-ui"
48 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
frontend/src/components/indices/TopIndices.vue
+1 -1
@@ -9,7 +9,7 @@
9 </template>
10
11 <script setup lang="ts">
12 -// TODO: refactor
12 +// TODO-FE: refactor
13 import type { ECharts } from "echarts/core"
14 import type { IndexStats } from "@/types/indices.d"
15 import bytes from "bytes"
frontend/src/components/integrations/IntegrationsList.vue
+6 -6
@@ -2,12 +2,12 @@
2 <ServicesList
3 v-model:selected="selected"
4 type="integration"
5 - :embedded="embedded"
6 - :hide-totals="hideTotals"
7 - :selectable="selectable"
8 - :disabled-ids-list="disabledIdsList"
9 - :loading="loading"
10 - :list="list"
5 + :embedded
6 + :hide-totals
7 + :selectable
8 + :disabled-ids-list
9 + :loading
10 + :list
11 />
12 </template>
13
frontend/src/components/license/LicenseCheckoutWizard.vue
+2 -2
@@ -11,7 +11,7 @@
11 <SubscriptionCard
12 v-for="subscription of availableSubscriptions"
13 :key="subscription.id"
14 - :subscription="subscription"
14 + :subscription
15 selectable
16 embedded
17 class="item-appear item-appear-bottom item-appear-005 cursor-pointer"
@@ -34,7 +34,7 @@
34 <SubscriptionCard :subscription="selectedSubscription" embedded hide-details />
35 <div class="checkout-form item-appear item-appear-bottom item-appear-005 mt-8">
36 <n-spin :show="loadingLicense || loadingSession">
37 - <n-form :label-width="80" :model="checkoutForm" :rules="rules">
37 + <n-form :label-width="80" :model="checkoutForm" :rules>
38 <div class="flex flex-col gap-1">
39 <n-form-item label="Company Name" path="company_name">
40 <n-input
frontend/src/components/license/LicenseDetails.vue
+1 -7
@@ -54,13 +54,7 @@
54 </template>
55 <template #value>
56 <div class="flex flex-wrap gap-2">
57 - <Badge
58 - v-for="(value, key) of license.customer"
59 - :key="key"
60 - type="splitted"
61 - color="primary"
62 - fluid
63 - >
57 + <Badge v-for="(value, key) of license.customer" :key type="splitted" color="primary" fluid>
58 <template #label>
59 {{ sanitizeKey(key) }}
60 </template>
frontend/src/components/license/LicenseFeatures.vue
+2 -2
@@ -22,8 +22,8 @@
22 <SubscriptionCard
23 v-for="subscription of activeSubscriptions"
24 :key="subscription.id"
25 - :subscription="subscription"
26 - :license-data="licenseData"
25 + :subscription
26 + :license-data
27 embedded
28 show-delete-on-dialog
29 @deleted="load()"
frontend/src/components/license/deprecated/LicenseEditor.vue
+1 -1
@@ -76,7 +76,7 @@
76 </div>
77
78 <div v-if="creationEnabled" class="create-box flex flex-col gap-2">
79 - <n-form ref="formRef" :label-width="80" :model="creationForm" :rules="rules">
79 + <n-form ref="formRef" :label-width="80" :model="creationForm" :rules>
80 <div class="grid-auto-fit-200 grid gap-2">
81 <n-form-item label="Name" path="name">
82 <n-input v-model:value.trim="creationForm.name" placeholder="Input name..." clearable />
frontend/src/components/logs/LogItem.vue
+1 -1
@@ -62,7 +62,7 @@
62 </template>
63
64 <script setup lang="ts">
65 -// TODO: refactor
65 +// TODO-FE: refactor
66 import type { Log } from "@/types/logs.d"
67 import type { User } from "@/types/user.d"
68 import { computed } from "vue"
frontend/src/components/logs/LogsFilters.vue
+1 -1
@@ -56,7 +56,7 @@
56 </template>
57
58 <script setup lang="ts">
59 -// TODO: refactor
59 +// TODO-FE: refactor
60 import type { LogsQueryEventType, LogsQueryTimeRange, LogsQueryTypes, LogsQueryValues } from "@/types/logs.d"
61 import type { User } from "@/types/user.d"
62 import _cloneDeep from "lodash/cloneDeep"
frontend/src/components/logs/LogsList.vue
+7 -7
@@ -38,9 +38,9 @@
38 <n-pagination
39 v-model:page="currentPage"
40 v-model:page-size="pageSize"
41 - :page-slot="pageSlot"
42 - :show-size-picker="showSizePicker"
43 - :page-sizes="pageSizes"
41 + :page-slot
42 + :show-size-picker
43 + :page-sizes
44 :item-count="total"
45 :simple="simpleMode"
46 />
@@ -61,7 +61,7 @@
61 v-model:value="filterValue"
62 v-model:filtered="filtered"
63 :users="usersList"
64 - :loading-users="loadingUsers"
64 + :loading-users
65 @submit="getData()"
66 @close="showFilters = false"
67 />
@@ -73,7 +73,7 @@
73 <LogItem
74 v-for="log of itemsPaginated"
75 :key="log.id"
76 - :log="log"
76 + :log
77 :users="usersList"
78 class="item-appear item-appear-bottom item-appear-005"
79 />
@@ -87,7 +87,7 @@
87 <n-pagination
88 v-if="itemsPaginated.length > 3"
89 v-model:page="currentPage"
90 - :page-size="pageSize"
90 + :page-size
91 :item-count="total"
92 :page-slot="6"
93 />
@@ -116,7 +116,7 @@
116 </template>
117
118 <script setup lang="ts">
119 -// TODO: refactor
119 +// TODO-FE: refactor
120 import type { Log, LogsQuery, LogsQueryTimeRange, LogsQueryTypes, LogsQueryValues } from "@/types/logs.d"
121 import type { User } from "@/types/user.d"
122 import { useResizeObserver } from "@vueuse/core"
frontend/src/components/mitre/AtomicTests/List.vue
+5 -5
@@ -34,9 +34,9 @@
34 v-model:page="currentPage"
35 v-model:page-size="pageSize"
36 :item-count="total"
37 - :page-slot="pageSlot"
38 - :show-size-picker="showSizePicker"
39 - :page-sizes="pageSizes"
37 + :page-slot
38 + :show-size-picker
39 + :page-sizes
40 :simple="simpleMode"
41 />
42 </div>
@@ -55,7 +55,7 @@
55 <n-pagination
56 v-if="list.length > 3"
57 v-model:page="currentPage"
58 - :page-size="pageSize"
58 + :page-size
59 :item-count="total"
60 :page-slot="6"
61 />
@@ -65,7 +65,7 @@
65 </template>
66
67 <script setup lang="ts">
68 -// TODO: refactor
68 +// TODO-FE: refactor
69 import type { MitreAtomicOsCategory, MitreAtomicTestsQuery } from "@/api/endpoints/wazuh/mitre"
70 import type { MitreAtomicTest } from "@/types/mitre.d"
71 import { useResizeObserver, watchDebounced } from "@vueuse/core"
frontend/src/components/mitre/AttackSimulator/ParametersList.vue
+1 -1
@@ -76,7 +76,7 @@ async function getList() {
76 list.value = _uniq(fullList, "name")
77 emit("loaded", list.value)
78 } catch (err: any) {
79 - // TODO: remove any
79 + // TODO-FE: remove any
80 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
81 } finally {
82 loading.value = false
frontend/src/components/mitre/AttackSimulator/SimulatorWizard.vue
+1 -1
@@ -3,7 +3,7 @@
3 <div>
4 <n-scrollbar x-scrollable trigger="none">
5 <div class="px-7 pt-4 pb-2">
6 - <n-steps :current="current" size="small" :status="currentStatus">
6 + <n-steps :current size="small" :status="currentStatus">
7 <n-step title="Attack" />
8 <n-step title="Endpoint agent" />
9 <n-step title="Simulate" />
frontend/src/components/mitre/Group/GroupCard.vue
+1 -1
@@ -64,7 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 -// TODO: refactor
67 +// TODO-FE: refactor
68 import type { MitreGroupDetails } from "@/types/mitre.d"
69 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
70 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
frontend/src/components/mitre/Mitigation/MitigationCard.vue
+1 -1
@@ -55,7 +55,7 @@
55 </template>
56
57 <script setup lang="ts">
58 -// TODO: refactor
58 +// TODO-FE: refactor
59 import type { MitreMitigationDetails } from "@/types/mitre.d"
60 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
61 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
frontend/src/components/mitre/Software/SoftwareCard.vue
+1 -1
@@ -64,7 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 -// TODO: refactor
67 +// TODO-FE: refactor
68 import type { MitreSoftwareDetails } from "@/types/mitre.d"
69 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
70 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
frontend/src/components/mitre/Tactic/TacticCard.vue
+1 -1
@@ -55,7 +55,7 @@
55 </template>
56
57 <script setup lang="ts">
58 -// TODO: refactor
58 +// TODO-FE: refactor
59 import type { MitreTacticDetails } from "@/types/mitre.d"
60 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
61 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
frontend/src/components/mitre/Technique/TechniqueCard.vue
+1 -1
@@ -92,7 +92,7 @@
92 </template>
93
94 <script setup lang="ts">
95 -// TODO: refactor
95 +// TODO-FE: refactor
96 import type { MitreTechniqueDetails } from "@/types/mitre.d"
97 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
98 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
frontend/src/components/mitre/TechniqueAlert/TechniqueAlertOverview.vue
+1 -1
@@ -57,7 +57,7 @@
57 </template>
58
59 <script setup lang="ts">
60 -// TODO: refactor
60 +// TODO-FE: refactor
61 import type { MitreTechniqueDetails } from "@/types/mitre.d"
62 import { NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
63 import { onBeforeMount, ref } from "vue"
frontend/src/components/mitre/TechniqueEvents/List.vue
+5 -5
@@ -27,9 +27,9 @@
27 v-model:page="currentPage"
28 v-model:page-size="pageSize"
29 :item-count="total"
30 - :page-slot="pageSlot"
31 - :show-size-picker="showSizePicker"
32 - :page-sizes="pageSizes"
30 + :page-slot
31 + :show-size-picker
32 + :page-sizes
33 :simple="simpleMode"
34 />
35 </div>
@@ -54,7 +54,7 @@
54 <n-pagination
55 v-if="alertsList.length > 3"
56 v-model:page="currentPage"
57 - :page-size="pageSize"
57 + :page-size
58 :item-count="total"
59 :page-slot="6"
60 />
@@ -64,7 +64,7 @@
64 </template>
65
66 <script setup lang="ts">
67 -// TODO: refactor
67 +// TODO-FE: refactor
68 import type { MitreEventsQuery, MitreTechniquesAlertsQueryTimeRange } from "@/api/endpoints/wazuh/mitre"
69 import type { MitreEventDetails } from "@/types/mitre.d"
70 import { useResizeObserver, watchDebounced } from "@vueuse/core"
frontend/src/components/mitre/TechniqueEvents/TechniqueEventCard.vue
+4 -4
@@ -109,7 +109,7 @@
109 <n-tabs type="line" animated :tabs-padding="24">
110 <n-tab-pane name="Agent" tab="Agent" display-directive="show">
111 <div v-if="agentProperties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
112 - <CardKV v-for="(value, key) of agentProperties" :key="key">
112 + <CardKV v-for="(value, key) of agentProperties" :key>
113 <template #key>
114 {{ key }}
115 </template>
@@ -151,7 +151,7 @@
151 display-directive="show"
152 >
153 <div v-if="tabCard.properties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
154 - <CardKV v-for="(value, key) of tabCard.properties" :key="key">
154 + <CardKV v-for="(value, key) of tabCard.properties" :key>
155 <template #key>
156 {{ key }}
157 </template>
@@ -172,7 +172,7 @@
172 </CardKV>
173 </div>
174 <div v-if="dnsProperties" class="grid-auto-fit-200 grid gap-2 p-7 pt-2">
175 - <CardKV v-for="(value, key) of dnsProperties" :key="key">
175 + <CardKV v-for="(value, key) of dnsProperties" :key>
176 <template #key>
177 {{ key }}
178 </template>
@@ -193,7 +193,7 @@
193 </CardKV>
194 </div>
195 <div v-if="gl2Properties" class="grid-auto-fit-200 grid gap-2 p-7 pt-2">
196 - <CardKV v-for="(value, key) of gl2Properties" :key="key">
196 + <CardKV v-for="(value, key) of gl2Properties" :key>
197 <template #key>
198 {{ key }}
199 </template>
frontend/src/components/mitre/TechniquesAlerts/List.vue
+2 -2
@@ -132,13 +132,13 @@ const areAllTacticsSelected = computed(() => {
132 const hasNoCountAlerts = computed(() => !!techniquesList.value.filter(o => !o.count).length)
133
134 function isTacticSelected(id: string) {
135 - return !!selectedTactics.value.find(o => o === id)
135 + return selectedTactics.value.includes(id)
136 }
137
138 function toggleTacticSelect(id: string) {
139 const index = selectedTactics.value.findIndex(o => o === id)
140
141 - if (selectedTactics.value.find(o => o === id)) {
141 + if (selectedTactics.value.includes(id)) {
142 selectedTactics.value.splice(index, 1)
143 } else {
144 selectedTactics.value.push(id)
frontend/src/components/monitoringAlerts/Item.vue
+1 -1
@@ -35,7 +35,7 @@
35 <template #footerExtra>
36 <AlertActions
37 class="flex flex-wrap gap-3"
38 - :alert="alert"
38 + :alert
39 size="small"
40 @deleted="emit('deleted')"
41 @invoked="emit('invoked')"
frontend/src/components/monitoringAlerts/ItemActions.vue
+2 -2
@@ -1,12 +1,12 @@
1 <template>
2 <div class="contents">
3 - <n-button :loading="loadingInvoke" type="success" secondary :size="size" @click.stop="invoke()">
3 + <n-button :loading="loadingInvoke" type="success" secondary :size @click.stop="invoke()">
4 <template #icon>
5 <Icon :name="InvokeIcon" />
6 </template>
7 Invoke
8 </n-button>
9 - <n-button :loading="loadingDelete" :size="size" type="error" secondary @click.stop="handleDelete()">
9 + <n-button :loading="loadingDelete" :size type="error" secondary @click.stop="handleDelete()">
10 <template #icon>
11 <Icon :name="DeleteIcon" />
12 </template>
frontend/src/components/monitoringAlerts/List.vue
+6 -6
@@ -37,9 +37,9 @@
37 <n-pagination
38 v-model:page="currentPage"
39 v-model:page-size="pageSize"
40 - :page-slot="pageSlot"
41 - :show-size-picker="showSizePicker"
42 - :page-sizes="pageSizes"
40 + :page-slot
41 + :show-size-picker
42 + :page-sizes
43 :item-count="total"
44 :simple="simpleMode"
45 />
@@ -50,7 +50,7 @@
50 <Alert
51 v-for="alert of itemsPaginated"
52 :key="alert.id"
53 - :alert="alert"
53 + :alert
54 class="item-appear item-appear-bottom item-appear-005"
55 @deleted="getData()"
56 @invoked="getData()"
@@ -65,7 +65,7 @@
65 <n-pagination
66 v-if="itemsPaginated.length > 3"
67 v-model:page="currentPage"
68 - :page-size="pageSize"
68 + :page-size
69 :item-count="total"
70 :page-slot="6"
71 />
@@ -74,7 +74,7 @@
74 </template>
75
76 <script setup lang="ts">
77 -// TODO: refactor
77 +// TODO-FE: refactor
78 import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
79 import { useResizeObserver } from "@vueuse/core"
80 import { NButton, NEmpty, NPagination, NPopover, NSpin, useDialog, useMessage } from "naive-ui"
frontend/src/components/networkConnectors/NetworkConnectorsList.vue
+6 -6
@@ -2,12 +2,12 @@
2 <ServicesList
3 v-model:selected="selected"
4 type="network-connector"
5 - :embedded="embedded"
6 - :hide-totals="hideTotals"
7 - :selectable="selectable"
8 - :disabled-ids-list="disabledIdsList"
9 - :loading="loading"
10 - :list="list"
5 + :embedded
6 + :hide-totals
7 + :selectable
8 + :disabled-ids-list
9 + :loading
10 + :list
11 />
12 </template>
13
frontend/src/components/overview/IncidentCases.vue
+10 -22
@@ -16,7 +16,6 @@
16
17 <script setup lang="ts">
18 import type { ItemProps } from "@/components/common/cards/CardStatsBars.vue"
19 -import type { Case } from "@/types/incidentManagement/cases"
19 import { NSpin, useMessage } from "naive-ui"
20 import { computed, onBeforeMount, ref } from "vue"
21 import Api from "@/api"
@@ -28,44 +27,33 @@ const CasesIcon = "carbon:ibm-secure-infrastructure-on-vpc-for-regulated-industr
27 const { routeIncidentManagementCases } = useNavigation()
28 const message = useMessage()
29 const loading = ref(false)
31 -const casesList = ref<Case[]>([])
30
33 -const total = computed<number>(() => {
34 - return casesList.value.length || 0
35 -})
36 -const openedCount = computed<number>(() => {
37 - return casesList.value.filter(o => o.case_status === "OPEN").length || 0
38 -})
39 -const inProgressCount = computed<number>(() => {
40 - return casesList.value.filter(o => o.case_status === "IN_PROGRESS").length || 0
41 -})
42 -const closedCount = computed<number>(() => {
43 - return casesList.value.filter(o => o.case_status === "CLOSED").length || 0
44 -})
45 -const undefinedCount = computed<number>(() => {
46 - return casesList.value.filter(o => o.case_status === null).length || 0
47 -})
31 +const total = ref(0)
32 +const openedCount = ref(0)
33 +const inProgressCount = ref(0)
34 +const closedCount = ref(0)
35
36 const values = computed<ItemProps[]>(() => [
37 { value: total.value, label: "Total", isTotal: true },
38 { value: openedCount.value, label: "Open", status: "error" },
39 { value: inProgressCount.value, label: "In Progress", status: "warning" },
53 - { value: closedCount.value, label: "Closed", status: "success" },
54 - { value: undefinedCount.value, label: "N/D", status: "muted" }
40 + { value: closedCount.value, label: "Closed", status: "success" }
41 ])
42
43 function getData() {
44 loading.value = true
45
46 Api.incidentManagement.cases
61 - .getCasesList()
47 + .getCasesList(undefined, { page: 1, pageSize: 1 })
48 .then(res => {
49 if (res.data.success) {
64 - casesList.value = res.data?.cases || []
50 + total.value = res.data.total || 0
51 + openedCount.value = res.data.open || 0
52 + inProgressCount.value = res.data.in_progress || 0
53 + closedCount.value = res.data.closed || 0
54 } else {
55 message.warning(res.data?.message || "An error occurred. Please try again later.")
56 }
68 - loading.value = false
57 })
58 .catch(err => {
59 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
frontend/src/components/overview/SocAlertsCard.vue
+1 -1
@@ -3,7 +3,7 @@
3 <CardStats
4 title="SOC Alerts"
5 :value="total"
6 - :vertical="vertical"
6 + :vertical
7 hovered
8 class="h-full cursor-pointer"
9 @click="routeSocAlerts().navigate()"
frontend/src/components/patchTuesday/PatchTuesdayCard.vue
+1 -1
@@ -72,7 +72,7 @@
72 </template>
73
74 <script setup lang="ts">
75 -// TODO: refactor
75 +// TODO-FE: refactor
76 import type { PatchTuesdayItem } from "@/types/patchTuesday.d"
77 import { NCard, NTag } from "naive-ui"
78 import Icon from "@/components/common/Icon.vue"
frontend/src/components/patchTuesday/PatchTuesdayDetail.vue
+1 -1
@@ -162,7 +162,7 @@
162 </template>
163
164 <script setup lang="ts">
165 -// TODO: refactor
165 +// TODO-FE: refactor
166 import type { PatchTuesdayItem } from "@/types/patchTuesday.d"
167 import { NAlert, NButton, NDescriptions, NDescriptionsItem, NDivider, NEmpty, NTag } from "naive-ui"
168 import Icon from "@/components/common/Icon.vue"
frontend/src/components/patchTuesday/PatchTuesdayFilters.vue
+2 -2
@@ -7,7 +7,7 @@
7 <n-select
8 :value="filters.cycle"
9 :options="cycleOptions"
10 - :loading="loading"
10 + :loading
11 placeholder="Select cycle"
12 style="min-width: 140px"
13 @update:value="updateFilter('cycle', $event)"
@@ -96,7 +96,7 @@
96 </template>
97
98 <script setup lang="ts">
99 -// TODO: refactor
99 +// TODO-FE: refactor
100 import type { PatchTuesdayFilters } from "./types"
101 import { NButton, NCard, NInput, NSelect, NSwitch, NTooltip } from "naive-ui"
102 import { computed } from "vue"
frontend/src/components/patchTuesday/PatchTuesdayList.vue
+8 -8
@@ -7,7 +7,7 @@
7 <h1 class="text-2xl font-bold">Microsoft Patch Tuesday</h1>
8 </div>
9 <div class="flex items-center gap-2">
10 - <n-button :loading="loading" :disabled="loading" type="primary" secondary @click="fetchData">
10 + <n-button :loading :disabled="loading" type="primary" secondary @click="fetchData">
11 <template #icon>
12 <Icon :name="RefreshIcon" />
13 </template>
@@ -17,14 +17,14 @@
17 </div>
18
19 <!-- Stats Cards -->
20 - <PatchTuesdayStats :summary="summary" :loading="loading" class="mb-4" />
20 + <PatchTuesdayStats :summary :loading class="mb-4" />
21
22 <!-- Filters -->
23 <PatchTuesdayFilters
24 v-model:filters="filters"
25 :cycles="availableCycles"
26 :families="availableFamilies"
27 - :loading="loading"
27 + :loading
28 class="mb-4"
29 @update:filters="handleFiltersChange"
30 />
@@ -35,7 +35,7 @@
35 <PatchTuesdayCard
36 v-for="item in paginatedItems"
37 :key="`${item.cve}-${item.affected.product}`"
38 - :item="item"
38 + :item
39 @click="openItemDetail(item)"
40 />
41 </div>
@@ -49,7 +49,7 @@
49
50 <!-- Pagination -->
51 <div v-if="filteredItems.length > pageSize" class="mt-4 flex justify-center">
52 - <n-pagination v-model:page="currentPage" :page-count="totalPages" :page-size="pageSize" show-quick-jumper />
52 + <n-pagination v-model:page="currentPage" :page-count="totalPages" :page-size show-quick-jumper />
53 </div>
54
55 <!-- Detail Drawer -->
@@ -62,11 +62,11 @@
62 </template>
63
64 <script setup lang="ts">
65 -// TODO: refactor
65 +// TODO-FE: refactor
66 import type { PatchTuesdayFilters as FiltersType } from "./types"
67 import type { PatchTuesdayItem, PatchTuesdaySummary } from "@/types/patchTuesday.d"
68 import { NButton, NDrawer, NDrawerContent, NEmpty, NPagination, NSpin, useMessage } from "naive-ui"
69 -import { computed, onMounted, ref, watch } from "vue"
69 +import { computed, onBeforeMount, ref, watch } from "vue"
70 import patchTuesdayApi from "@/api/endpoints/patchTuesday"
71 import Icon from "@/components/common/Icon.vue"
72 import PatchTuesdayCard from "./PatchTuesdayCard.vue"
@@ -211,7 +211,7 @@ watch(
211 )
212
213 // Lifecycle
214 -onMounted(async () => {
214 +onBeforeMount(async () => {
215 await fetchCycles()
216 })
217 </script>
frontend/src/components/patchTuesday/PatchTuesdayPriorityBadge.vue
+1 -1
@@ -5,7 +5,7 @@
5 </template>
6
7 <script setup lang="ts">
8 -// TODO: refactor
8 +// TODO-FE: refactor
9 import { NTag } from "naive-ui"
10 import { computed } from "vue"
11 import { PriorityLevel } from "@/types/patchTuesday.d"
frontend/src/components/patchTuesday/PatchTuesdayStats.vue
+1 -1
@@ -101,7 +101,7 @@
101 </template>
102
103 <script setup lang="ts">
104 -// TODO: refactor
104 +// TODO-FE: refactor
105 import type { PatchTuesdaySummary } from "@/types/patchTuesday.d"
106 import { NCard, NGrid, NGridItem, NSpace } from "naive-ui"
107 import Icon from "@/components/common/Icon.vue"
frontend/src/components/patchTuesday/types.d.ts
+1 -1
@@ -1,6 +1,6 @@
1 import type { PatchTuesdayItem, PatchTuesdaySummary, PriorityLevel } from "@/types/patchTuesday.d"
2
3 -// TODO: refactor
3 +// TODO-FE: refactor
4 export interface PatchTuesdayFilters {
5 cycle: string
6 priority: PriorityLevel | null
frontend/src/components/profile/ProfileSettings.vue
+1 -1
@@ -10,7 +10,7 @@
10 <n-form-item label="Time Format" path="hours24" class="basis-1/3">
11 <n-radio-group v-model:value="formValue.hours24" name="radiogroup">
12 <div class="flex flex-wrap gap-3">
13 - <n-radio :value="true" :label="`24 Hours [ ${h24} ]`" />
13 + <n-radio value :label="`24 Hours [ ${h24} ]`" />
14 <n-radio :value="false" :label="`12 Hours [ ${h12} ]`" />
15 </div>
16 </n-radio-group>
frontend/src/components/reportCreation/Panels.vue
+8 -5
@@ -124,13 +124,13 @@
124 </n-button>
125
126 <div v-if="panelsReady" class="flex items-center gap-2">
127 - <n-button type="success" :loading="loading" @click="print()">
127 + <n-button type="success" :loading @click="print()">
128 <template #icon>
129 <Icon :name="PrintIcon" />
130 </template>
131 Print Report
132 </n-button>
133 - <n-button type="success" :loading="loading" @click="openSettings()">
133 + <n-button type="success" :loading @click="openSettings()">
134 <template #icon>
135 <Icon :name="SettingsIcon" />
136 </template>
@@ -302,7 +302,7 @@ function openSettings() {
302
303 function addRow() {
304 rows.value.push({
305 - id: new Date().getTime(),
305 + id: Date.now(),
306 height: 1,
307 panels: []
308 })
@@ -349,6 +349,9 @@ function setOrg(org: Org) {
349 }
350 }
351
352 +const NUMBER_REGEX = /\d+/
353 +const LETTER_REGEX = /[a-z]/i
354 +
355 function print() {
356 if (!timerange.value) {
357 return
@@ -356,8 +359,8 @@ function print() {
359
360 loadingPrint.value = true
361
359 - const timeValue = Number.parseInt(timerange.value.match(/\d+/)?.[0] || "1")
360 - const timeUnit = (timerange.value.match(/[a-z]/i)?.[0] || "h").toLocaleLowerCase()
362 + const timeValue = Number.parseInt(timerange.value.match(NUMBER_REGEX)?.[0] || "1")
363 + const timeUnit = (timerange.value.match(LETTER_REGEX)?.[0] || "h").toLocaleLowerCase()
364 const timerangeText = `Last ${timeValue} ${timeUnit === "d" ? "Day" : timeUnit === "h" ? "Hour" : "minute"}${
365 timeValue > 1 ? "s" : ""
366 }`
frontend/src/components/sca/GenerateReportForm.vue
+3 -3
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="formValue" :rules="rules" label-placement="top">
2 + <n-form ref="formRef" :model="formValue" :rules label-placement="top">
3 <n-form-item label="Customer" path="customer_code">
4 <n-select
5 v-model:value="formValue.customer_code"
@@ -52,7 +52,7 @@
52
53 <n-space justify="end" class="mt-4">
54 <n-button @click="$emit('cancel')">Cancel</n-button>
55 - <n-button type="primary" :loading="loading" @click="handleGenerate">
55 + <n-button type="primary" :loading @click="handleGenerate">
56 <template #icon>
57 <Icon :name="GenerateIcon" />
58 </template>
@@ -63,7 +63,7 @@
63 </template>
64
65 <script setup lang="ts">
66 -// TODO: refactor
66 +// TODO-FE: refactor
67 import type { FormInst, FormRules } from "naive-ui"
68 import type { SCAReportGenerateRequest } from "@/types/sca.d"
69 import { NButton, NDivider, NForm, NFormItem, NInput, NInputNumber, NSelect, NSpace } from "naive-ui"
frontend/src/components/sca/List.vue
+1 -1
@@ -61,7 +61,7 @@
61 </template>
62
63 <script setup lang="ts">
64 -// TODO: refactor
64 +// TODO-FE: refactor
65 import type { ScaOverviewFilter, ScaOverviewFilterTypes } from "./types.d"
66 import type { AgentScaOverviewItem, ScaOverviewQuery } from "@/types/sca.d"
67 import { useResizeObserver, useStorage, watchDebounced } from "@vueuse/core"
frontend/src/components/sca/ListFilters.vue
+1 -1
@@ -128,7 +128,7 @@
128 </template>
129
130 <script setup lang="ts">
131 -// TODO: refactor
131 +// TODO-FE: refactor
132 import type { ScaOverviewFilter, ScaOverviewFilterTypes } from "./types.d"
133 import type { Agent } from "@/types/agents.d"
134 import type { Customer } from "@/types/customers.d"
frontend/src/components/sca/SCAReports.vue
+7 -13
@@ -25,7 +25,7 @@
25 class="flex-1"
26 @update:value="loadReports"
27 />
28 - <n-button :loading="loading" @click="loadReports">
28 + <n-button :loading @click="loadReports">
29 <template #icon>
30 <Icon :name="RefreshIcon" />
31 </template>
@@ -36,13 +36,7 @@
36
37 <!-- Reports Table -->
38 <n-card>
39 - <n-data-table
40 - :columns="columns"
41 - :data="reports"
42 - :loading="loading"
43 - :pagination="pagination"
44 - :row-key="(row: SCAReport) => row.id"
45 - />
39 + <n-data-table :columns :data="reports" :loading :pagination :row-key="(row: SCAReport) => row.id" />
40 </n-card>
41
42 <!-- Generate Report Modal -->
@@ -51,10 +45,10 @@
45 preset="card"
46 title="Generate SCA Report"
47 style="width: 600px; max-width: 90vw"
54 - :closable="true"
48 + closable
49 >
50 <GenerateReportForm
57 - :customers="customers"
51 + :customers
52 :loading="generating"
53 @generate="handleGenerateReport"
54 @cancel="showGenerateModal = false"
@@ -78,12 +72,12 @@
72 </template>
73
74 <script setup lang="ts">
81 -// TODO: refactor
75 +// TODO-FE: refactor
76 import type { DataTableColumns } from "naive-ui"
77 import type { Customer } from "@/types/customers"
78 import type { SCAReport, SCAReportGenerateRequest } from "@/types/sca.d"
79 import { NButton, NCard, NDataTable, NModal, NSelect, NSpace, NTag, NTooltip, useMessage } from "naive-ui"
86 -import { computed, h, onMounted, ref } from "vue"
80 +import { computed, h, onBeforeMount, ref } from "vue"
81 import Api from "@/api"
82 import Icon from "@/components/common/Icon.vue"
83 import { useSettingsStore } from "@/stores/settings"
@@ -325,7 +319,7 @@ async function confirmDelete() {
319 }
320 }
321
328 -onMounted(() => {
322 +onBeforeMount(() => {
323 loadReports()
324 loadCustomers()
325 })
frontend/src/components/sca/ScaStats.vue
+13 -15
@@ -265,20 +265,19 @@ const stats = computed((): Record<LevelKey, number> => {
265 })
266
267 // Calculate top policies by compliance score
268 +interface PolicyAggregate {
269 + policy_id: string
270 + policy_name: string
271 + score: number
272 + agentCount: number
273 + total_checks: number
274 + pass: number
275 + fail: number
276 +}
277 +
278 const topPoliciesByScore = computed(() => {
279 // Group policies and calculate averages
270 - const policyMap = new Map<
271 - string,
272 - {
273 - policy_id: string
274 - policy_name: string
275 - score: number
276 - agentCount: number
277 - total_checks: number
278 - pass: number
279 - fail: number
280 - }
281 - >()
280 + const policyMap = new Map<string, PolicyAggregate>()
281
282 list.value.forEach(item => {
283 const key = item.policy_id
@@ -305,9 +304,8 @@ const topPoliciesByScore = computed(() => {
304 })
305
306 // Convert to array and sort by score
308 - return Array.from(policyMap.values())
309 - .sort((a, b) => b.score - a.score)
310 - .slice(0, 5)
307 + // eslint-disable-next-line e18e/prefer-array-to-sorted
308 + return [...policyMap.values()].sort((a: PolicyAggregate, b: PolicyAggregate) => b.score - a.score).slice(0, 5)
309 })
310
311 function getPercentage(count: number): number {
frontend/src/components/sca/StreamingList.vue
+5 -5
@@ -9,8 +9,8 @@
9 type="line"
10 :percentage="progress.percent_complete"
11 :status="streamError ? 'error' : streamComplete ? 'success' : 'default'"
12 - :show-indicator="true"
13 - class="flex-grow"
12 + show-indicator
13 + class="grow"
14 />
15 <n-button
16 v-if="!isStreaming && !streamComplete"
@@ -140,9 +140,9 @@
140 <!-- Results Table -->
141 <n-data-table
142 v-else
143 - :columns="columns"
143 + :columns
144 :data="paginatedResults"
145 - :pagination="pagination"
145 + :pagination
146 :loading="isConnecting"
147 :row-key="(row: AgentScaOverviewItem) => `${row.agent_id}-${row.policy_id}`"
148 striped
@@ -159,7 +159,7 @@
159 </template>
160
161 <script setup lang="ts">
162 -// TODO: refactor
162 +// TODO-FE: refactor
163 import type { DataTableColumns } from "naive-ui"
164 import type { AgentScaOverviewItem, ScaOverviewQuery, ScaStreamComplete, ScaStreamProgress } from "@/types/sca.d"
165 import {
frontend/src/components/scheduler/Item.vue
+1 -1
@@ -37,7 +37,7 @@
37 </template>
38 <template #footerExtra>
39 <div class="flex flex-row flex-wrap gap-3">
40 - <JobActions :job="job" size="small" />
40 + <JobActions :job size="small" />
41 </div>
42 </template>
43 </CardEntity>
frontend/src/components/scheduler/JobActions.vue
+2 -2
@@ -39,7 +39,7 @@
39 :bordered="false"
40 segmented
41 >
42 - <JobForm :job="job" @updated="update($event)" />
42 + <JobForm :job @updated="update($event)" />
43 </n-modal>
44 </template>
45
@@ -97,7 +97,7 @@ function run() {
97 .jobAction(job.value.id, "run")
98 .then(res => {
99 if (res.data.success) {
100 - // TODO: check timezone with Taylor
100 + // TODO-FE: check timezone with Taylor
101 job.value.last_success = new Date()
102 message.success(res.data?.message || "Job executed successfully.")
103 } else {
frontend/src/components/scheduler/JobForm.vue
+4 -2
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="job-form">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-1">
5 <n-form-item label="Time interval (minutes)" path="time_interval" class="grow">
6 <n-input-number
@@ -73,11 +73,13 @@ const isValid = computed(() => {
73 return valid
74 })
75
76 +const INTEGER_STRING_REGEX = /^\d*$/
77 +
78 function validatorNumber(fieldName: string, defaultMessage?: string) {
79 return (_rule: FormItemRule, value: string) => {
80 if (!value) {
81 return new Error(defaultMessage || `${fieldName} is required`)
80 - } else if (!/^\d*$/.test(value)) {
82 + } else if (!INTEGER_STRING_REGEX.test(value)) {
83 return new Error(`${fieldName} should be an integer`)
84 } else if (Number(value) < 1) {
85 return new Error(`${fieldName} should be above 1`)
frontend/src/components/scheduler/List.vue
+1 -1
@@ -3,7 +3,7 @@
3 <n-spin :show="loading" class="min-h-48">
4 <div class="min-h-52">
5 <template v-if="jobs.length">
6 - <JobCard v-for="job of jobs" :key="job.id" :job="job" class="mb-2" />
6 + <JobCard v-for="job of jobs" :key="job.id" :job class="mb-2" />
7 </template>
8 <template v-else>
9 <n-empty v-if="!loading" description="No items found" class="h-48 justify-center" />
frontend/src/components/services/List.vue
+2 -2
@@ -12,9 +12,9 @@
12 <ServiceItem
13 v-for="item of list"
14 :key="item.id"
15 - :type="type"
15 + :type
16 :data="item"
17 - :embedded="embedded"
17 + :embedded
18 :selectable="isSelectable(item)"
19 :disabled="isDisabled(item)"
20 :checked="selected?.id === item.id"
frontend/src/components/sigma/QueriesList.vue
+5 -5
@@ -46,9 +46,9 @@
46 <n-pagination
47 v-model:page="currentPage"
48 v-model:page-size="pageSize"
49 - :page-slot="pageSlot"
50 - :show-size-picker="showSizePicker"
51 - :page-sizes="pageSizes"
49 + :page-slot
50 + :show-size-picker
51 + :page-sizes
52 :item-count="total"
53 :simple="simpleMode"
54 />
@@ -102,7 +102,7 @@
102 <QueryItem
103 v-for="query of itemsPaginated"
104 :key="query.id"
105 - :query="query"
105 + :query
106 class="item-appear item-appear-bottom item-appear-005"
107 @deleted="deleteQueryItem"
108 @updated="updateQueryItem"
@@ -118,7 +118,7 @@
118 <n-pagination
119 v-if="itemsPaginated.length > 3"
120 v-model:page="currentPage"
121 - :page-size="pageSize"
121 + :page-size
122 :item-count="total"
123 :page-slot="6"
124 />
frontend/src/components/sigma/actionsProviders/QueryActiveAllForm.vue
+3 -3
@@ -31,17 +31,17 @@ const emit = defineEmits<{
31 const loading = defineModel<boolean | undefined>("loading", { default: false })
32
33 const show = ref(false)
34 -const lastShow = ref(new Date().getTime())
34 +const lastShow = ref(Date.now())
35 const message = useMessage()
36
37 function togglePopup() {
38 - if (new Date().getTime() - lastShow.value > 500) {
38 + if (Date.now() - lastShow.value > 500) {
39 show.value = !show.value
40 }
41 }
42
43 function closePopup() {
44 - lastShow.value = new Date().getTime()
44 + lastShow.value = Date.now()
45 show.value = false
46 }
47
frontend/src/components/sigma/actionsProviders/QueryActiveForm.vue
+4 -4
@@ -21,7 +21,7 @@
21 </template>
22
23 <script setup lang="ts">
24 -// TODO: refactor
24 +// TODO-FE: refactor
25 import type { SigmaQuery } from "@/types/sigma.d"
26 import { NButton, NPopover, NSwitch, useMessage } from "naive-ui"
27 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
@@ -40,7 +40,7 @@ const { query } = toRefs(props)
40 const loading = defineModel<boolean | undefined>("loading", { default: false })
41
42 const show = ref(false)
43 -const lastShow = ref(new Date().getTime())
43 +const lastShow = ref(Date.now())
44 const message = useMessage()
45 const model = ref<{ active: boolean }>({ active: false })
46 const active = ref<boolean>(false)
@@ -53,13 +53,13 @@ watch(show, val => {
53 })
54
55 function togglePopup() {
56 - if (new Date().getTime() - lastShow.value > 500) {
56 + if (Date.now() - lastShow.value > 500) {
57 show.value = !show.value
58 }
59 }
60
61 function closePopup() {
62 - lastShow.value = new Date().getTime()
62 + lastShow.value = Date.now()
63 show.value = false
64 }
65
frontend/src/components/sigma/actionsProviders/QueryDeleteAll.vue
+3 -3
@@ -29,17 +29,17 @@ const emit = defineEmits<{
29 const loading = defineModel<boolean | undefined>("loading", { default: false })
30
31 const show = ref(false)
32 -const lastShow = ref(new Date().getTime())
32 +const lastShow = ref(Date.now())
33 const message = useMessage()
34
35 function togglePopup() {
36 - if (new Date().getTime() - lastShow.value > 500) {
36 + if (Date.now() - lastShow.value > 500) {
37 show.value = !show.value
38 }
39 }
40
41 function closePopup() {
42 - lastShow.value = new Date().getTime()
42 + lastShow.value = Date.now()
43 show.value = false
44 }
45
frontend/src/components/sigma/actionsProviders/QueryDeleteOne.vue
+3 -3
@@ -45,17 +45,17 @@ const loading = defineModel<boolean | undefined>("loading", { default: false })
45
46 const TrashIcon = "carbon:trash-can"
47 const show = ref(false)
48 -const lastShow = ref(new Date().getTime())
48 +const lastShow = ref(Date.now())
49 const message = useMessage()
50
51 function togglePopup() {
52 - if (new Date().getTime() - lastShow.value > 500) {
52 + if (Date.now() - lastShow.value > 500) {
53 show.value = !show.value
54 }
55 }
56
57 function closePopup() {
58 - lastShow.value = new Date().getTime()
58 + lastShow.value = Date.now()
59 show.value = false
60 }
61
frontend/src/components/sigma/actionsProviders/QueryDownloadAll.vue
+3 -3
@@ -27,17 +27,17 @@ const emit = defineEmits<{
27 const loading = defineModel<boolean | undefined>("loading", { default: false })
28
29 const show = ref(false)
30 -const lastShow = ref(new Date().getTime())
30 +const lastShow = ref(Date.now())
31 const message = useMessage()
32
33 function togglePopup() {
34 - if (new Date().getTime() - lastShow.value > 500) {
34 + if (Date.now() - lastShow.value > 500) {
35 show.value = !show.value
36 }
37 }
38
39 function closePopup() {
40 - lastShow.value = new Date().getTime()
40 + lastShow.value = Date.now()
41 show.value = false
42 }
43
frontend/src/components/sigma/actionsProviders/QueryTimeIntervalForm.vue
+9 -6
@@ -42,7 +42,7 @@
42 </template>
43
44 <script setup lang="ts">
45 -// TODO: refactor
45 +// TODO-FE: refactor
46 import type { SigmaQuery, SigmaTimeInterval, SigmaTimeIntervalUnit } from "@/types/sigma.d"
47 import { NButton, NInputGroup, NInputNumber, NPopover, NSelect, useMessage } from "naive-ui"
48 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
@@ -61,7 +61,7 @@ const { query } = toRefs(props)
61 const loading = defineModel<boolean | undefined>("loading", { default: false })
62
63 const show = ref(false)
64 -const lastShow = ref(new Date().getTime())
64 +const lastShow = ref(Date.now())
65 const message = useMessage()
66 const model = ref<{ time: number; unit: SigmaTimeIntervalUnit }>({ time: 1, unit: "m" })
67 const unitOptions = [
@@ -85,22 +85,25 @@ watch(show, val => {
85 })
86
87 function togglePopup() {
88 - if (new Date().getTime() - lastShow.value > 500) {
88 + if (Date.now() - lastShow.value > 500) {
89 show.value = !show.value
90 }
91 }
92
93 function closePopup() {
94 - lastShow.value = new Date().getTime()
94 + lastShow.value = Date.now()
95 show.value = false
96 }
97
98 +const NUMBER_REGEX = /\d+/
99 +const LETTER_REGEX = /[a-z]/i
100 +
101 function setModel() {
102 if (query.value.time_interval) {
103 timeUnit.value = (
101 - query.value.time_interval.match(/[a-z]/i)?.[0] || "m"
104 + query.value.time_interval.match(LETTER_REGEX)?.[0] || "m"
105 ).toLocaleLowerCase() as SigmaTimeIntervalUnit
103 - timeValue.value = Number.parseInt(query.value.time_interval.match(/\d+/)?.[0] || "1")
106 + timeValue.value = Number.parseInt(query.value.time_interval.match(NUMBER_REGEX)?.[0] || "1")
107
108 model.value.unit = timeUnit.value
109 model.value.time = timeValue.value
frontend/src/components/sigma/actionsProviders/QueryUploadDB.vue
+3 -3
@@ -37,19 +37,19 @@ const emit = defineEmits<{
37 const loading = defineModel<boolean | undefined>("loading", { default: false })
38
39 const show = ref(false)
40 -const lastShow = ref(new Date().getTime())
40 +const lastShow = ref(Date.now())
41 const message = useMessage()
42 const ruleLevels = ref<SigmaRuleLevels[]>([])
43 const isValid = computed(() => !!ruleLevels.value.length)
44
45 function togglePopup() {
46 - if (new Date().getTime() - lastShow.value > 500) {
46 + if (Date.now() - lastShow.value > 500) {
47 show.value = !show.value
48 }
49 }
50
51 function closePopup() {
52 - lastShow.value = new Date().getTime()
52 + lastShow.value = Date.now()
53 show.value = false
54 }
55
frontend/src/components/sigma/actionsProviders/QueryUploadFile.vue
+3 -3
@@ -46,20 +46,20 @@ const loading = defineModel<boolean | undefined>("loading", { default: false })
46 const UploadIcon = "carbon:document-add"
47
48 const show = ref(false)
49 -const lastShow = ref(new Date().getTime())
49 +const lastShow = ref(Date.now())
50 const message = useMessage()
51 const fileList = ref<UploadFileInfo[]>([])
52 const yamlFile = computed<File | null>(() => fileList.value?.[0]?.file || null)
53 const isValid = computed(() => fileList.value.length)
54
55 function togglePopup() {
56 - if (new Date().getTime() - lastShow.value > 500) {
56 + if (Date.now() - lastShow.value > 500) {
57 show.value = !show.value
58 }
59 }
60
61 function closePopup() {
62 - lastShow.value = new Date().getTime()
62 + lastShow.value = Date.now()
63 show.value = false
64 }
65
frontend/src/components/snapshots/CreateSnapshotForm.vue
+3 -3
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="formData" :rules="rules" label-placement="left" label-width="160px">
2 + <n-form ref="formRef" :model="formData" :rules label-placement="left" label-width="160px">
3 <n-form-item label="Snapshot Name" path="snapshot">
4 <n-input v-model:value="formData.snapshot" placeholder="Enter snapshot name" />
5 </n-form-item>
@@ -33,13 +33,13 @@
33
34 <div class="mt-4 flex justify-end gap-2">
35 <n-button @click="$emit('cancel')">Cancel</n-button>
36 - <n-button type="primary" :loading="loading" @click="handleSubmit">Create Snapshot</n-button>
36 + <n-button type="primary" :loading @click="handleSubmit">Create Snapshot</n-button>
37 </div>
38 </n-form>
39 </template>
40
41 <script setup lang="ts">
42 -// TODO: refactor
42 +// TODO-FE: refactor
43 import type { FormInst, FormRules } from "naive-ui"
44 import type { CreateSnapshotRequest } from "@/types/snapshots.d"
45 import { NButton, NForm, NFormItem, NInput, NSwitch, useMessage } from "naive-ui"
frontend/src/components/snapshots/RestoreSnapshotForm.vue
+2 -2
@@ -53,13 +53,13 @@
53
54 <div class="mt-4 flex justify-end gap-2">
55 <n-button @click="$emit('cancel')">Cancel</n-button>
56 - <n-button type="primary" :loading="loading" @click="handleSubmit">Restore Snapshot</n-button>
56 + <n-button type="primary" :loading @click="handleSubmit">Restore Snapshot</n-button>
57 </div>
58 </n-form>
59 </template>
60
61 <script setup lang="ts">
62 -// TODO: refactor
62 +// TODO-FE: refactor
63 import type { FormRules } from "naive-ui"
64 import type { RestoreSnapshotRequest, SnapshotInfo } from "@/types/snapshots.d"
65 import { NAlert, NButton, NForm, NFormItem, NInput, NSwitch, NTag, useMessage } from "naive-ui"
frontend/src/components/snapshots/SnapshotList.vue
+4 -4
@@ -22,7 +22,7 @@
22 <n-spin :show="loading">
23 <n-card>
24 <n-data-table
25 - :columns="columns"
25 + :columns
26 :data="snapshots"
27 :bordered="false"
28 :single-line="false"
@@ -57,11 +57,11 @@
57 </template>
58
59 <script setup lang="ts">
60 -// TODO: refactor
60 +// TODO-FE: refactor
61 import type { DataTableColumns, SelectOption } from "naive-ui"
62 import type { SnapshotInfo, SnapshotRepository } from "@/types/snapshots.d"
63 import { NButton, NCard, NDataTable, NEmpty, NModal, NSelect, NSpin, NTag, useMessage } from "naive-ui"
64 -import { computed, h, onMounted, ref } from "vue"
64 +import { computed, h, onBeforeMount, ref } from "vue"
65 import Api from "@/api"
66 import Icon from "@/components/common/Icon.vue"
67 import CreateSnapshotForm from "./CreateSnapshotForm.vue"
@@ -198,7 +198,7 @@ function onSnapshotRestored() {
198 message.success("Snapshot restoration initiated")
199 }
200
201 -onMounted(() => {
201 +onBeforeMount(() => {
202 fetchRepositories()
203 })
204 </script>
frontend/src/components/snapshots/SnapshotRepositories.vue
+6 -12
@@ -1,6 +1,6 @@
1 <template>
2 <div class="flex flex-col gap-4">
3 - <n-alert type="info" :show-icon="true">
3 + <n-alert type="info" show-icon>
4 <template #header>Repository Registration Required</template>
5 Snapshot repositories must be manually registered in your Wazuh Indexer cluster.
6 <n-a
@@ -14,7 +14,7 @@
14
15 <div class="flex items-center justify-between">
16 <h2 class="text-lg font-semibold">Snapshot Repositories</h2>
17 - <n-button type="primary" :loading="loading" @click="fetchRepositories">
17 + <n-button type="primary" :loading @click="fetchRepositories">
18 <template #icon>
19 <Icon :name="RefreshIcon" :size="16" />
20 </template>
@@ -24,13 +24,7 @@
24
25 <n-spin :show="loading">
26 <n-card>
27 - <n-data-table
28 - :columns="columns"
29 - :data="repositories"
30 - :bordered="false"
31 - :single-line="false"
32 - size="small"
33 - />
27 + <n-data-table :columns :data="repositories" :bordered="false" :single-line="false" size="small" />
28 </n-card>
29 </n-spin>
30
@@ -39,11 +33,11 @@
33 </template>
34
35 <script setup lang="ts">
42 -// TODO: refactor
36 +// TODO-FE: refactor
37 import type { DataTableColumns } from "naive-ui"
38 import type { SnapshotRepository } from "@/types/snapshots.d"
39 import { NA, NAlert, NButton, NCard, NDataTable, NEmpty, NSpin, useMessage } from "naive-ui"
46 -import { h, onMounted, ref } from "vue"
40 +import { h, onBeforeMount, ref } from "vue"
41 import Api from "@/api"
42 import Icon from "@/components/common/Icon.vue"
43
@@ -89,7 +83,7 @@ async function fetchRepositories() {
83 }
84 }
85
92 -onMounted(() => {
86 +onBeforeMount(() => {
87 fetchRepositories()
88 })
89 </script>
frontend/src/components/snapshots/SnapshotScheduleForm.vue
+5 -5
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="formData" :rules="rules" label-placement="left" label-width="160px">
2 + <n-form ref="formRef" :model="formData" :rules label-placement="left" label-width="160px">
3 <n-form-item label="Name" path="name">
4 <n-input v-model:value="formData.name" placeholder="Enter a friendly name for this schedule" />
5 </n-form-item>
@@ -54,7 +54,7 @@
54
55 <div class="mt-4 flex justify-end gap-2">
56 <n-button @click="$emit('cancel')">Cancel</n-button>
57 - <n-button type="primary" :loading="loading" @click="handleSubmit">
57 + <n-button type="primary" :loading @click="handleSubmit">
58 {{ isEditing ? "Update Schedule" : "Create Schedule" }}
59 </n-button>
60 </div>
@@ -62,11 +62,11 @@
62 </template>
63
64 <script setup lang="ts">
65 -// TODO: refactor
65 +// TODO-FE: refactor
66 import type { FormInst, FormRules, SelectOption } from "naive-ui"
67 import type { SnapshotRepository, SnapshotScheduleCreate, SnapshotScheduleResponse } from "@/types/snapshots.d"
68 import { NButton, NForm, NFormItem, NInput, NInputNumber, NSelect, NSwitch, useMessage } from "naive-ui"
69 -import { computed, onMounted, ref, watch } from "vue"
69 +import { computed, onBeforeMount, ref, watch } from "vue"
70 import Api from "@/api"
71
72 const props = defineProps<{
@@ -183,7 +183,7 @@ async function handleSubmit() {
183 }
184 }
185
186 -onMounted(() => {
186 +onBeforeMount(() => {
187 fetchRepositories()
188 })
189 </script>
frontend/src/components/snapshots/SnapshotSchedules.vue
+5 -5
@@ -3,7 +3,7 @@
3 <div class="flex items-center justify-between">
4 <h2 class="text-lg font-semibold">Scheduled Snapshots</h2>
5 <div class="flex items-center gap-2">
6 - <n-button :loading="loading" @click="fetchSchedules">
6 + <n-button :loading @click="fetchSchedules">
7 <template #icon>
8 <Icon :name="RefreshIcon" :size="16" />
9 </template>
@@ -21,7 +21,7 @@
21 <n-spin :show="loading">
22 <n-card>
23 <n-data-table
24 - :columns="columns"
24 + :columns
25 :data="schedules"
26 :bordered="false"
27 :single-line="false"
@@ -50,11 +50,11 @@
50 </template>
51
52 <script setup lang="ts">
53 -// TODO: refactor
53 +// TODO-FE: refactor
54 import type { DataTableColumns } from "naive-ui"
55 import type { SnapshotScheduleResponse } from "@/types/snapshots.d"
56 import { NButton, NCard, NDataTable, NEmpty, NModal, NPopconfirm, NSpin, NSwitch, NTag, useMessage } from "naive-ui"
57 -import { h, onMounted, ref } from "vue"
57 +import { h, onBeforeMount, ref } from "vue"
58 import Api from "@/api"
59 import Icon from "@/components/common/Icon.vue"
60 import SnapshotScheduleForm from "./SnapshotScheduleForm.vue"
@@ -223,7 +223,7 @@ function onScheduleUpdated() {
223 message.success("Schedule updated successfully")
224 }
225
226 -onMounted(() => {
226 +onBeforeMount(() => {
227 fetchSchedules()
228 })
229 </script>
frontend/src/components/soc/SocAlerts/SocAlertAssets/SocAlertAssetsItem.vue
+4 -3
@@ -71,7 +71,7 @@
71 <n-tabs type="line" animated :tabs-padding="24">
72 <n-tab-pane name="Info" tab="Info" display-directive="show">
73 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
74 - <CardKV v-for="(value, key) of properties" :key="key">
74 + <CardKV v-for="(value, key) of properties" :key>
75 <template #key>
76 {{ key }}
77 </template>
@@ -96,7 +96,7 @@
96 </n-tab-pane>
97 <n-tab-pane name="Type" tab="Type" display-directive="show">
98 <div v-if="assetType" class="grid-auto-fit-250 grid gap-2 p-7 pt-4">
99 - <CardKV v-for="(value, key) of assetType" :key="key">
99 + <CardKV v-for="(value, key) of assetType" :key>
100 <template #key>
101 {{ key }}
102 </template>
@@ -164,6 +164,7 @@ const { routeAgent } = useNavigation()
164 const showDetails = ref(false)
165
166 const dFormats = useSettingsStore().dateFormat
167 +const ASSET_NEWLINE_REGEX = /\n/g
168
169 const excerpt = computed(() => {
170 const text = asset.asset_description
@@ -175,7 +176,7 @@ const excerpt = computed(() => {
176 const descriptionFull = computed(() => {
177 const text = asset.asset_description
178
178 - return text.replace(/\n/g, "<br>") || "Empty"
179 + return text.replace(ASSET_NEWLINE_REGEX, "<br>") || "Empty"
180 })
181
182 const properties = computed(() => {
frontend/src/components/soc/SocAlerts/SocAlertAssets/SocAlertAssetsList.vue
+1 -1
@@ -2,7 +2,7 @@
2 <div class="soc-assets-list">
3 <n-spin :show="loadingAssets" class="min-h-14">
4 <div v-if="assetsList?.length" class="flex flex-col gap-2 p-7">
5 - <SocAlertAssetsItem v-for="asset of assetsList" :key="asset.asset_id" :asset="asset" />
5 + <SocAlertAssetsItem v-for="asset of assetsList" :key="asset.asset_id" :asset />
6 </div>
7 <template v-else>
8 <n-empty v-if="!loadingAssets" description="No items found" class="h-48 justify-center" />
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemActions.vue
+4 -11
@@ -1,6 +1,6 @@
1 <template>
2 <div class="contents">
3 - <n-button v-if="existCase" type="success" secondary :size="size" @click.stop="openSocCase()">
3 + <n-button v-if="existCase" type="success" secondary :size @click.stop="openSocCase()">
4 <template #icon>
5 <Icon :name="ViewIcon" />
6 </template>
@@ -11,7 +11,7 @@
11 :loading="loadingCaseCreation"
12 type="warning"
13 secondary
14 - :size="size"
14 + :size
15 @click.stop="createCase()"
16 >
17 <template #icon>
@@ -22,7 +22,7 @@
22 <n-button
23 v-if="alertId"
24 :loading="loadingAlertDelete"
25 - :size="size"
25 + :size
26 type="error"
27 secondary
28 @click.stop="handleDelete()"
@@ -43,14 +43,7 @@
43 segmented
44 >
45 <div class="flex h-full w-full items-center justify-center">
46 - <SocCaseItem
47 - v-if="caseId"
48 - :case-id="caseId"
49 - embedded
50 - hide-soc-alert-link
51 - hide-soc-case-action
52 - class="w-full"
53 - />
46 + <SocCaseItem v-if="caseId" :case-id embedded hide-soc-alert-link hide-soc-case-action class="w-full" />
47 </div>
48 </n-modal>
49 </div>
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemBadges.vue
+1 -1
@@ -53,7 +53,7 @@
53 </template>
54 </Badge>
55
56 - <SocAssignUser v-slot="{ loading }" :alert="alert" :users="users" @updated="emit('updated', $event)">
56 + <SocAssignUser v-slot="{ loading }" :alert :users @updated="emit('updated', $event)">
57 <Badge type="active" class="cursor-pointer">
58 <template #iconLeft>
59 <n-spin :size="16" :show="loading">
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemContext.vue
+1 -1
@@ -7,7 +7,7 @@
7 </n-input>
8
9 <div class="grid-auto-fit-200 grid gap-2">
10 - <CardKV v-for="{ value, key } of contextFiltered" :key="key">
10 + <CardKV v-for="{ value, key } of contextFiltered" :key>
11 <template #key>
12 {{ key }}
13 </template>
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemDetails.vue
+4 -9
@@ -1,7 +1,7 @@
1 <template>
2 <n-tabs v-if="alert" type="line" animated :tabs-padding="24">
3 <n-tab-pane name="Context" tab="Context" display-directive="show:lazy">
4 - <SocAlertItemContext :alert="alert" class="p-7 pt-4" />
4 + <SocAlertItemContext :alert class="p-7 pt-4" />
5 </n-tab-pane>
6 <n-tab-pane name="Note" tab="Note" display-directive="show:lazy">
7 <div class="p-7 pt-4">
@@ -10,7 +10,7 @@
10 </n-tab-pane>
11 <n-tab-pane name="Customer" tab="Customer" display-directive="show:lazy">
12 <div class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
13 - <CardKV v-for="(value, key) of alert.customer" :key="key">
13 + <CardKV v-for="(value, key) of alert.customer" :key>
14 <template #key>
15 {{ key }}
16 </template>
@@ -49,12 +49,7 @@
49 <CardKV>
50 <template #key>user_login</template>
51 <template #value>
52 - <SocAssignUser
53 - v-slot="{ loading }"
54 - :alert="alert"
55 - :users="users"
56 - @updated="emit('updated', $event)"
57 - >
52 + <SocAssignUser v-slot="{ loading }" :alert :users @updated="emit('updated', $event)">
53 <div class="text-primary flex cursor-pointer items-center gap-2">
54 <n-spin :size="16" :show="loading">
55 <Icon :name="EditIcon" :size="16" />
@@ -81,7 +76,7 @@
76 </n-tab-pane>
77 <n-tab-pane name="History" tab="History" display-directive="show:lazy">
78 <div class="p-7 pt-4">
84 - <SocAlertItemTimeline :alert="alert" />
79 + <SocAlertItemTimeline :alert />
80 </div>
81 </n-tab-pane>
82 <n-tab-pane name="Details" tab="Details" display-directive="show:lazy">
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemTime.vue
+1 -1
@@ -9,7 +9,7 @@
9 </div>
10 </template>
11 <div class="flex flex-col px-1 py-2">
12 - <SocAlertItemTimeline :alert="alert" />
12 + <SocAlertItemTimeline :alert />
13 </div>
14 </n-popover>
15 </template>
frontend/src/components/soc/SocAlerts/SocAlertsBookmarks.vue
+1 -1
@@ -17,7 +17,7 @@
17 :key="alert.alert_id"
18 :alert-data="alert"
19 class="item-appear item-appear-bottom item-appear-005 mb-2"
20 - :is-bookmark="true"
20 + is-bookmark
21 :users="usersList"
22 @bookmark="bookmark()"
23 @deleted="itemDeleted(alert.alert_id)"
frontend/src/components/soc/SocAlerts/SocAlertsFullList.vue
+8 -8
@@ -10,7 +10,7 @@
10 >
11 <template #1>
12 <SocAlertsBookmarks
13 - :users-list="usersList"
13 + :users-list
14 @bookmark="reloadAlerts()"
15 @deleted="itemDeleted($event)"
16 @loaded="bookmarksList = $event"
@@ -19,9 +19,9 @@
19 </template>
20 <template #2>
21 <SocAlertsList
22 - :highlight="highlight"
23 - :bookmarks-list="bookmarksList"
24 - :users-list="usersList"
22 + :highlight
23 + :bookmarks-list
24 + :users-list
25 @bookmark="reloadBookmarks()"
26 @deleted="reloadBookmarks()"
27 @mounted="socAlertsCTX = $event"
@@ -38,9 +38,9 @@
38
39 <template v-else>
40 <SocAlertsList
41 - :highlight="highlight"
42 - :bookmarks-list="bookmarksList"
43 - :users-list="usersList"
41 + :highlight
42 + :bookmarks-list
43 + :users-list
44 @bookmark="reloadBookmarks()"
45 @deleted="reloadBookmarks()"
46 @mounted="socAlertsCTX = $event"
@@ -64,7 +64,7 @@
64 >
65 <n-drawer-content title="Alerts list" closable :native-scrollbar="false">
66 <SocAlertsBookmarks
67 - :users-list="usersList"
67 + :users-list
68 @bookmark="reloadAlerts()"
69 @deleted="itemDeleted($event)"
70 @loaded="bookmarksList = $event"
frontend/src/components/soc/SocAlerts/SocAlertsList.vue
+1 -1
@@ -55,7 +55,7 @@
55 v-model:page="page"
56 v-model:page-size="pageSize"
57 v-model:sort="sort"
58 - :page-sizes="pageSizes"
58 + :page-sizes
59 :show-page-sizes="!compactMode"
60 :show-sort="!smallDeviceMode"
61 />
frontend/src/components/soc/SocCases/SocCaseAssetLink.vue
+1 -1
@@ -45,7 +45,7 @@
45 <SocCaseItem
46 v-if="socCase"
47 :case-data="socCase"
48 - :embedded="true"
48 + embedded
49 class="-mt-4 py-2"
50 @deleted="getSocCase(link.case_id)"
51 />
frontend/src/components/soc/SocCases/SocCaseAssetsItem.vue
+5 -7
@@ -66,7 +66,7 @@
66 <n-tabs type="line" animated :tabs-padding="24">
67 <n-tab-pane name="Info" tab="Info" display-directive="show">
68 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
69 - <CardKV v-for="(value, key) of properties" :key="key">
69 + <CardKV v-for="(value, key) of properties" :key>
70 <template #key>
71 {{ key }}
72 </template>
@@ -96,11 +96,7 @@
96 </n-tab-pane>
97 <n-tab-pane name="Link" tab="Link" display-directive="show:lazy">
98 <div v-if="asset.link?.length" class="flex flex-col gap-2 px-4">
99 - <SocCaseAssetLink
100 - v-for="link of asset.link"
101 - :key="`${link.case_id}-${link.asset_id}`"
102 - :link="link"
103 - />
99 + <SocCaseAssetLink v-for="link of asset.link" :key="`${link.case_id}-${link.asset_id}`" :link />
100 </div>
101 <template v-else>
102 <n-empty description="No items found" class="h-48 justify-center" />
@@ -139,10 +135,12 @@ const excerpt = computed(() => {
135 return truncated + (truncated !== text ? "..." : "")
136 })
137
138 +const NEWLINE_REGEX = /\n/g
139 +
140 const descriptionFull = computed(() => {
141 const text = asset.asset_description
142
145 - return text.replace(/\n/g, "<br>") || "Empty"
143 + return text.replace(NEWLINE_REGEX, "<br>") || "Empty"
144 })
145
146 const tags = computed<{ key: string; value?: string }[]>(() => {
frontend/src/components/soc/SocCases/SocCaseAssetsList.vue
+1 -1
@@ -12,7 +12,7 @@
12 </div>
13 </div>
14 <div v-if="assetsList?.length" class="flex flex-col gap-2 p-7">
15 - <SocCaseAssetsItem v-for="asset of assetsList" :key="asset.asset_id" :asset="asset" />
15 + <SocCaseAssetsItem v-for="asset of assetsList" :key="asset.asset_id" :asset />
16 </div>
17 <template v-else>
18 <n-empty v-if="!loadingAssets" description="No items found" class="h-48 justify-center" />
frontend/src/components/soc/SocCases/SocCaseItem.vue
+1 -1
@@ -154,7 +154,7 @@
154 </div>
155 </div>
156 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
157 - <CardKV v-for="(value, key) of properties" :key="key">
157 + <CardKV v-for="(value, key) of properties" :key>
158 <template #key>
159 {{ key }}
160 </template>
frontend/src/components/soc/SocCases/SocCaseItemActions.vue
+3 -3
@@ -3,7 +3,7 @@
3 <n-button
4 v-if="isCaseClosed"
5 :loading="loadingCaseReopen"
6 - :size="size"
6 + :size
7 type="warning"
8 secondary
9 @click.stop="reopenCase()"
@@ -13,13 +13,13 @@
13 </template>
14 Reopen
15 </n-button>
16 - <n-button v-else :loading="loadingCaseClose" type="success" secondary :size="size" @click.stop="closeCase()">
16 + <n-button v-else :loading="loadingCaseClose" type="success" secondary :size @click.stop="closeCase()">
17 <template #icon>
18 <Icon :name="CloseIcon" />
19 </template>
20 Close
21 </n-button>
22 - <n-button :loading="loadingCaseDelete" :size="size" type="error" secondary @click.stop="handleDelete()">
22 + <n-button :loading="loadingCaseDelete" :size type="error" secondary @click.stop="handleDelete()">
23 <template #icon>
24 <Icon :name="DeleteIcon" />
25 </template>
frontend/src/components/soc/SocCases/SocCaseNote.vue
+3 -3
@@ -13,7 +13,7 @@
13 </div>
14 </template>
15 <div class="flex flex-col px-1 py-2">
16 - <SocCaseNoteTimeline :note="note" />
16 + <SocCaseNoteTimeline :note />
17 </div>
18 </n-popover>
19 </template>
@@ -59,7 +59,7 @@
59 <n-tabs type="line" animated :tabs-padding="24">
60 <n-tab-pane name="Info" tab="Info" display-directive="show">
61 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 p-7 pt-4">
62 - <CardKV v-for="(value, key) of properties" :key="key">
62 + <CardKV v-for="(value, key) of properties" :key>
63 <template #key>
64 {{ key }}
65 </template>
@@ -86,7 +86,7 @@
86 </n-tab-pane>
87 <n-tab-pane name="History" tab="History" display-directive="show:lazy">
88 <div class="p-7 pt-4">
89 - <SocCaseNoteTimeline :note="note" />
89 + <SocCaseNoteTimeline :note />
90 </div>
91 </n-tab-pane>
92 </n-tabs>
frontend/src/components/soc/SocCases/SocCaseNotesList.vue
+1 -1
@@ -5,7 +5,7 @@
5 </div>
6 <n-spin :show="loadingNotes" class="min-h-28">
7 <div v-if="notesList?.length" class="flex flex-col gap-2 p-7 pt-3" style="container-type: inline-size">
8 - <SocCaseNote v-for="note of notesList" :key="note.note_id" :note="note" />
8 + <SocCaseNote v-for="note of notesList" :key="note.note_id" :note />
9 </div>
10 <template v-else>
11 <n-empty v-if="!loadingNotes" description="No items found" class="h-48 justify-center" />
frontend/src/components/soc/SocCases/SocCasesList.vue
+5 -5
@@ -37,9 +37,9 @@
37 <n-pagination
38 v-model:page="currentPage"
39 v-model:page-size="pageSize"
40 - :page-slot="pageSlot"
41 - :show-size-picker="showSizePicker"
42 - :page-sizes="pageSizes"
40 + :page-slot
41 + :show-size-picker
42 + :page-sizes
43 :item-count="total"
44 :simple="simpleMode"
45 />
@@ -89,7 +89,7 @@
89 <SocCaseItem
90 v-for="caseData of itemsPaginated"
91 :key="caseData.case_id"
92 - :case-data="caseData"
92 + :case-data
93 class="item-appear item-appear-bottom item-appear-005"
94 @deleted="getData()"
95 />
@@ -103,7 +103,7 @@
103 <n-pagination
104 v-if="itemsPaginated.length > 3"
105 v-model:page="currentPage"
106 - :page-size="pageSize"
106 + :page-size
107 :item-count="total"
108 :page-slot="6"
109 />
frontend/src/components/stackProvisioning/StackProvisioningButton.vue
+16 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button :size="size" :type="type" @click="showForm = true">
2 + <n-button :size :type :secondary @click="openModal">
3 <template #icon>
4 <Icon :name="PackIcon" />
5 </template>
@@ -26,11 +26,25 @@ import { ref } from "vue"
26 import Icon from "@/components/common/Icon.vue"
27 import StackProvisioningList from "./StackProvisioningList.vue"
28
29 -const { type, size } = defineProps<{
29 +defineProps<{
30 size?: Size
31 type?: Type
32 + secondary?: boolean
33 }>()
34
35 const PackIcon = "mdi:package-variant"
36 const showForm = ref(false)
37 +
38 +function openModal() {
39 + showForm.value = true
40 +}
41 +
42 +function closeModal() {
43 + showForm.value = false
44 +}
45 +
46 +defineExpose({
47 + openModal,
48 + closeModal
49 +})
50 </script>
frontend/src/components/threatIntel/AIAnalystButton.vue
+1 -1
@@ -75,7 +75,7 @@
75 display-directive="show"
76 >
77 <div class="p-7 pt-4">
78 - <CodeSource :code="analysisResponse.base64_decoded" :decode="true" />
78 + <CodeSource :code="analysisResponse.base64_decoded" decode />
79 </div>
80 </n-tab-pane>
81 <n-tab-pane
frontend/src/components/threatIntel/AIWazuhExclusionRuleButton.vue
+3 -2
@@ -46,7 +46,7 @@
46 class="flex flex-col gap-7 p-7"
47 >
48 <div v-if="analysisResponse?.wazuh_exclusion_rule">
49 - <CodeSource :code="analysisResponse.wazuh_exclusion_rule" :decode="true" />
49 + <CodeSource :code="analysisResponse.wazuh_exclusion_rule" decode />
50 </div>
51 <div v-if="analysisResponse?.wazuh_exclusion_rule_justification">
52 <Markdown :source="analysisResponse.wazuh_exclusion_rule_justification" />
@@ -93,6 +93,7 @@ const licenseChecking = ref(false)
93 const licenseChecked = ref(forceLicenseResponse !== undefined)
94 const licenseResponse = ref(forceLicenseResponse ?? false)
95 const disabledLicenseCheck = ref(forceLicenseResponse !== undefined)
96 +const WAZUH_RULE_BACKSLASH_REGEX = /\\\\/g
97
98 function openResponse() {
99 showModal.value = true
@@ -109,7 +110,7 @@ function analysis() {
110
111 if (res.data.wazuh_exclusion_rule) {
112 analysisResponse.value.wazuh_exclusion_rule = res.data.wazuh_exclusion_rule.replace(
112 - /\\\\/g,
113 + WAZUH_RULE_BACKSLASH_REGEX,
114 "\\\\\\\\"
115 )
116 }
frontend/src/components/threatIntel/ThreatIntelButton.vue
+15 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button :size="size" :type="type" @click="showThreatIntelDrawer = true">
2 + <n-button :size :type :secondary @click="openDrawer">
3 <template #icon>
4 <Icon :name="ThreatIcon" />
5 </template>
@@ -38,9 +38,10 @@ import Icon from "@/components/common/Icon.vue"
38 import ThreatIntelForm from "./ThreatIntelForm.vue"
39 import VirusTotalForm from "./VirusTotalForm.vue"
40
41 -const { type, size } = defineProps<{
41 +defineProps<{
42 size?: Size
43 type?: Type
44 + secondary?: boolean
45 }>()
46
47 const ThreatIcon = "mynaui:info-waves"
@@ -48,8 +49,20 @@ const showThreatIntelDrawer = ref(false)
49 const threatIntelCTX = ref<{ restore: () => void } | null>(null)
50 const virusTotalCTX = ref<{ restore: () => void } | null>(null)
51
52 +function openDrawer() {
53 + showThreatIntelDrawer.value = true
54 +}
55 +function closeDrawer() {
56 + showThreatIntelDrawer.value = false
57 +}
58 +
59 watch(showThreatIntelDrawer, () => {
60 threatIntelCTX.value?.restore()
61 virusTotalCTX.value?.restore()
62 })
63 +
64 +defineExpose({
65 + openDrawer,
66 + closeDrawer
67 +})
68 </script>
frontend/src/components/threatIntel/VirusTotalEnrichmentButton.vue
+2 -2
@@ -113,7 +113,7 @@
113 </n-tab-pane>
114 <n-tab-pane name="Details" tab="Details" display-directive="show">
115 <div class="p-7 pt-4">
116 - <CodeSource :code="properties" :decode="true" />
116 + <CodeSource :code="properties" decode />
117 </div>
118 </n-tab-pane>
119 <n-tab-pane
@@ -433,7 +433,7 @@
433 <template #value>
434 <CodeSource
435 :code="virusTotalDataResponse.attributes.last_https_certificate.public_key"
436 - :decode="true"
436 + decode
437 />
438 </template>
439 </CardKV>
frontend/src/components/threatIntel/VirusTotalForm.vue
+3 -3
@@ -126,7 +126,7 @@
126 <div class="divide-border flex flex-col gap-2 divide-y">
127 <div
128 v-for="(val, key) of analysisResponse.attributes.stats"
129 - :key="key"
129 + :key
130 class="flex items-end justify-between gap-4"
131 >
132 <div>{{ key }}</div>
@@ -168,7 +168,7 @@
168 <div class="divide-border bg-default flex flex-col gap-1 divide-y">
169 <div
170 v-for="(val, key) of resultVal"
171 - :key="key"
171 + :key
172 class="flex items-end justify-between gap-4 px-2 py-0.5"
173 >
174 <div>{{ key }}</div>
@@ -183,7 +183,7 @@
183 <div class="divide-border flex flex-col gap-2 divide-y">
184 <div
185 v-for="(val, key) of analysisResponse.links"
186 - :key="key"
186 + :key
187 class="flex items-end justify-between"
188 >
189 <a
frontend/src/components/users/AssignCustomer.vue
+2 -2
@@ -45,14 +45,14 @@
45
46 <div class="flex justify-end gap-3">
47 <n-button @click="showModal = false">Cancel</n-button>
48 - <n-button type="primary" :loading="loading" @click="handleAssignCustomers">Assign Customers</n-button>
48 + <n-button type="primary" :loading @click="handleAssignCustomers">Assign Customers</n-button>
49 </div>
50 </div>
51 </n-modal>
52 </template>
53
54 <script setup lang="ts">
55 -// TODO: refactor
55 +// TODO-FE: refactor
56 import type { Customer } from "@/types/customers.d"
57 import type { User } from "@/types/user.d"
58 import { NButton, NForm, NFormItem, NModal, NSelect, NTag, useMessage } from "naive-ui"
frontend/src/components/users/AssignRole.vue
+3 -3
@@ -22,20 +22,20 @@
22 {{ user?.username }}
23 </div>
24
25 - <n-form ref="formRef" :model="formModel" :rules="rules">
25 + <n-form ref="formRef" :model="formModel" :rules>
26 <n-form-item path="role" label="Select Role">
27 <n-select
28 v-model:value="formModel.role"
29 :options="roleOptions"
30 placeholder="Choose a role"
31 - :loading="loading"
31 + :loading
32 />
33 </n-form-item>
34 </n-form>
35
36 <div class="flex justify-end gap-3">
37 <n-button @click="showModal = false">Cancel</n-button>
38 - <n-button type="primary" :loading="loading" :disabled="!formModel.role" @click="handleAssignRole">
38 + <n-button type="primary" :loading :disabled="!formModel.role" @click="handleAssignRole">
39 Assign Role
40 </n-button>
41 </div>
frontend/src/components/users/AssignTags.vue
+5 -5
@@ -41,11 +41,11 @@
41 </template>
42
43 <script setup lang="ts">
44 -// TODO: refactor
44 +// TODO-FE: refactor
45 import type { AlertTag } from "@/types/tags"
46 import type { User } from "@/types/user.d"
47 import { NButton, NSelect, NSpin, useMessage } from "naive-ui"
48 -import { computed, onMounted, ref, watch } from "vue"
48 +import { computed, onBeforeMount, ref, watch } from "vue"
49 import Api from "@/api"
50 import Icon from "@/components/common/Icon.vue"
51
@@ -78,8 +78,8 @@ const tagOptions = computed(() =>
78
79 const hasChanges = computed(() => {
80 if (selectedTagIds.value.length !== originalTagIds.value.length) return true
81 - const sorted1 = [...selectedTagIds.value].sort()
82 - const sorted2 = [...originalTagIds.value].sort()
81 + const sorted1 = selectedTagIds.value.toSorted()
82 + const sorted2 = originalTagIds.value.toSorted()
83 return sorted1.some((val, idx) => val !== sorted2[idx])
84 })
85
@@ -178,7 +178,7 @@ watch(
178 }
179 )
180
181 -onMounted(async () => {
181 +onBeforeMount(async () => {
182 await loadSettings()
183 if (tagRbacEnabled.value) {
184 await loadAvailableTags()
frontend/src/components/users/ChangePassword.vue
+1 -1
@@ -15,7 +15,7 @@
15 >
16 <n-drawer-content title="Change Password" closable :native-scrollbar="false">
17 <n-spin :show="loading">
18 - <n-form ref="formRef" :model="model" :rules="rules">
18 + <n-form ref="formRef" :model :rules>
19 <div class="flex flex-col gap-3">
20 <n-form-item label="Username" required>
21 <n-input :value="username" disabled readonly />
frontend/src/components/users/TagRbacSettings.vue
+3 -3
@@ -121,7 +121,7 @@
121 </template>
122
123 <script setup lang="ts">
124 -// TODO: refactor
124 +// TODO-FE: refactor
125 import type { AlertTag } from "@/types/tags"
126 import {
127 NAlert,
@@ -136,7 +136,7 @@ import {
136 NSwitch,
137 useMessage
138 } from "naive-ui"
139 -import { computed, onMounted, reactive, ref, watch } from "vue"
139 +import { computed, onBeforeMount, reactive, ref, watch } from "vue"
140 import Api from "@/api"
141
142 interface TagAccessSettings {
@@ -261,7 +261,7 @@ watch(
261 }
262 )
263
264 -onMounted(async () => {
264 +onBeforeMount(async () => {
265 await loadSettings()
266 // Pre-load tags if default_tag is already selected
267 if (settings.untagged_alert_behavior === "default_tag") {
frontend/src/components/users/UsersList.vue
+1 -1
@@ -106,7 +106,7 @@
106 </template>
107
108 <script setup lang="ts">
109 -// TODO: refactor
109 +// TODO-FE: refactor
110 import type { User } from "@/types/user.d"
111 import { NButton, NDropdown, NModal, NScrollbar, NSpin, NTable, NTag, useMessage } from "naive-ui"
112 import { computed, defineAsyncComponent, h, onBeforeMount, ref } from "vue"
frontend/src/components/vulnerabilities/GenerateReportForm.vue
+3 -3
@@ -1,5 +1,5 @@
1 <template>
2 - <n-form ref="formRef" :model="formData" :rules="rules" label-placement="top">
2 + <n-form ref="formRef" :model="formData" :rules label-placement="top">
3 <n-form-item label="Report Name" path="report_name">
4 <n-input v-model:value="formData.report_name" placeholder="Leave empty for auto-generated name" clearable />
5 </n-form-item>
@@ -45,13 +45,13 @@
45
46 <div class="mt-6 flex justify-end gap-3">
47 <n-button @click="$emit('cancel')">Cancel</n-button>
48 - <n-button type="primary" :loading="loading" @click="handleSubmit">Generate Report</n-button>
48 + <n-button type="primary" :loading @click="handleSubmit">Generate Report</n-button>
49 </div>
50 </n-form>
51 </template>
52
53 <script setup lang="ts">
54 -// TODO: refactor
54 +// TODO-FE: refactor
55 import type { FormInst, FormRules } from "naive-ui"
56 import type { VulnerabilityReportGenerateRequest } from "@/types/vulnerabilities.d"
57 import { NButton, NDivider, NForm, NFormItem, NInput, NSelect, NSwitch } from "naive-ui"
frontend/src/components/vulnerabilities/VulnerabilityCard.vue
+1 -1
@@ -93,7 +93,7 @@
93 </template>
94
95 <script setup lang="ts">
96 -// TODO: review VulnerabilityCard (this component)
96 +// TODO-FE: review VulnerabilityCard (this component)
97 import type { VulnerabilitySearchItem } from "@/types/vulnerabilities.d"
98 import { NModal } from "naive-ui"
99 import { ref } from "vue"
frontend/src/components/vulnerabilities/VulnerabilityCardContent.vue
+5 -3
@@ -106,7 +106,7 @@
106 </template>
107
108 <script setup lang="ts">
109 -// TODO: review VulnerabilityCardContent (this component)
109 +// TODO-FE: review VulnerabilityCardContent (this component)
110 import type { VulnerabilitySearchItem } from "@/types/vulnerabilities.d"
111 import { NCard, NEmpty, NStatistic, NTabPane, NTabs, NTimeline, NTimelineItem } from "naive-ui"
112 import CardKV from "@/components/common/cards/CardKV.vue"
@@ -123,6 +123,8 @@ const dFormats = useSettingsStore().dateFormat
123 const { routeCustomer } = useNavigation()
124
125 const LinkIcon = "carbon:launch"
126 +const REFS_SPLIT_REGEX = /[,;\n|]/
127 +const SPACE_SPLIT_REGEX = /\s+/
128
129 function parseReferences(references: string): string[] {
130 if (!references || references.trim() === "") return []
@@ -140,13 +142,13 @@ function parseReferences(references: string): string[] {
142
143 // 2. Comma, semicolon, or newline separated
144 let refs = references
143 - .split(/[,;\n|]/)
145 + .split(REFS_SPLIT_REGEX)
146 .map(ref => ref.trim())
147 .filter(ref => ref.length > 0)
148
149 // 3. Space separated URLs (if they start with http)
150 if (refs.length === 1 && refs?.[0]?.includes("http")) {
149 - const spaceRefs = refs[0].split(/\s+/).filter(ref => ref.startsWith("http"))
151 + const spaceRefs = refs[0].split(SPACE_SPLIT_REGEX).filter(ref => ref.startsWith("http"))
152 if (spaceRefs.length > 1) {
153 refs = spaceRefs
154 }
frontend/src/components/vulnerabilities/VulnerabilityReports.vue
+11 -11
@@ -25,7 +25,7 @@
25 class="flex-1"
26 @update:value="loadReports"
27 />
28 - <n-button :loading="loading" @click="loadReports">
28 + <n-button :loading @click="loadReports">
29 <template #icon>
30 <Icon :name="RefreshIcon" />
31 </template>
@@ -37,10 +37,10 @@
37 <!-- Reports Table -->
38 <n-card>
39 <n-data-table
40 - :columns="columns"
40 + :columns
41 :data="reports"
42 - :loading="loading"
43 - :pagination="pagination"
42 + :loading
43 + :pagination
44 :row-key="(row: VulnerabilityReport) => row.id"
45 />
46 </n-card>
@@ -51,10 +51,10 @@
51 preset="card"
52 title="Generate Vulnerability Report"
53 style="width: 600px; max-width: 90vw"
54 - :closable="true"
54 + closable
55 >
56 <GenerateReportForm
57 - :customers="customers"
57 + :customers
58 :loading="generating"
59 @generate="handleGenerateReport"
60 @cancel="showGenerateModal = false"
@@ -78,11 +78,11 @@
78 </template>
79
80 <script setup lang="ts">
81 -// TODO: review VulnerabilityReports (this component)
81 +// TODO-FE: review VulnerabilityReports (this component)
82 import type { DataTableColumns } from "naive-ui"
83 import type { VulnerabilityReport, VulnerabilityReportGenerateRequest } from "@/types/vulnerabilities.d"
84 import { NButton, NCard, NDataTable, NModal, NSelect, NSpace, NTag, NTooltip, useMessage } from "naive-ui"
85 -import { computed, h, onMounted, ref } from "vue"
85 +import { computed, h, onBeforeMount, ref } from "vue"
86 import Api from "@/api"
87 import Icon from "@/components/common/Icon.vue"
88 import { useSettingsStore } from "@/stores/settings"
@@ -250,7 +250,7 @@ async function loadCustomers() {
250 let customerData = response.data
251
252 // If response.data is wrapped in a data property
253 - // TODO: review this (customerData.data) - it's not in the type definition
253 + // TODO-FE: review this (customerData.data) - it's not in the type definition
254 // @ts-expect-error - customerData.data is not in the type definition
255 if (customerData.data) {
256 // @ts-expect-error - customerData.data is not in the type definition
@@ -325,7 +325,7 @@ function startStatusPolling(reportId: number) {
325 }, 3000) // Poll every 3 seconds
326
327 // Stop polling after 5 minutes
328 - setTimeout(() => clearInterval(pollInterval), 300000)
328 + setTimeout(clearInterval, 300000, pollInterval)
329 }
330
331 async function handleDownload(report: VulnerabilityReport) {
@@ -373,7 +373,7 @@ async function confirmDelete() {
373 }
374 }
375
376 -onMounted(() => {
376 +onBeforeMount(() => {
377 loadReports()
378 loadCustomers()
379 })
frontend/src/components/vulnerabilities/VulnerabilityStats.vue
+4 -5
@@ -303,11 +303,10 @@ const topEpssPackages = computed(() => {
303 })
304
305 // Convert to array and sort by max EPSS score
306 - return Array.from(packageMap.values())
307 - .map(pkg => ({
308 - ...pkg,
309 - affectedAgents: pkg.affectedAgents.size
310 - }))
306 + return Array.from(packageMap.values(), pkg => ({
307 + ...pkg,
308 + affectedAgents: pkg.affectedAgents.size
309 + }))
310 .sort((a, b) => b.maxEpssScore - a.maxEpssScore)
311 .slice(0, 5)
312 })
frontend/src/components/webVulnerabilityAssessment/ReportsItem.vue
+1 -1
@@ -112,7 +112,7 @@ function checkEvent(event: PointerEvent) {
112
113 function pageBack() {
114 reportNavigation.value.pop()
115 - currentReportPage.value = reportNavigation.value[reportNavigation.value.length - 1] || "index.md"
115 + currentReportPage.value = reportNavigation.value.at(-1) || "index.md"
116 }
117
118 function navigationReset() {
frontend/src/components/webVulnerabilityAssessment/ScanHostForm.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <n-spin :show="loading" class="creation-report-form">
3 - <n-form ref="formRef" :label-width="80" :model="form" :rules="rules">
3 + <n-form ref="formRef" :label-width="80" :model="form" :rules>
4 <div class="flex flex-col gap-2">
5 <div class="flex items-start gap-4">
6 <n-form-item label="Host" path="host" class="grow">
frontend/src/components/webVulnerabilityAssessment/WebVulnerabilityAssessmentButton.vue
+3 -2
@@ -1,5 +1,5 @@
1 <template>
2 - <n-button :size="size" :type="type" @click="gotoPage()">
2 + <n-button :size :type :secondary @click="gotoPage()">
3 <template #icon>
4 <Icon :name="VulnerabilityIcon" :size="16" />
5 </template>
@@ -13,9 +13,10 @@ import { NButton } from "naive-ui"
13 import { useRouter } from "vue-router"
14 import Icon from "@/components/common/Icon.vue"
15
16 -const { type, size } = defineProps<{
16 +defineProps<{
17 size?: Size
18 type?: Type
19 + secondary?: boolean
20 }>()
21
22 const VulnerabilityIcon = "bi:radioactive"
frontend/src/composables/useBreadcrumb.ts
+1 -1
@@ -36,7 +36,7 @@ export function useBreadcrumb() {
36 */
37 function updateLastCrumbName(name: string) {
38 if (breadcrumbItems.value.length > 0) {
39 - const lastItem = breadcrumbItems.value[breadcrumbItems.value.length - 1]
39 + const lastItem = breadcrumbItems.value.at(-1)
40 if (!lastItem) return
41
42 breadcrumbItems.value[breadcrumbItems.value.length - 1] = {
frontend/src/main.ts
+2 -2
@@ -21,5 +21,5 @@ app.use(router)
21
22 app.mount("#app")
23
24 -// TODO: Rename all files to file-case. For example: activeResponse.ts -> active-response.ts
25 -// TODO: use .ts files instead of .d.ts files
24 +// TODO-FE: Rename all files to file-case. For example: activeResponse.ts -> active-response.ts
25 +// TODO-FE: use .ts files instead of .d.ts files
frontend/src/router/index.ts
+9 -1
@@ -6,7 +6,7 @@ import { authCheck } from "@/utils/auth"
6 import AuthPage from "@/views/Auth.vue"
7 import OverviewPage from "@/views/Overview.vue"
8
9 -// TODO: refactor
9 +// TODO-FE: refactor
10 const router = createRouter({
11 history: createWebHistory(import.meta.env.BASE_URL),
12 routes: [
@@ -91,6 +91,14 @@ const router = createRouter({
91 component: () => import("@/views/agents/CopilotActions.vue"),
92 meta: { title: "CoPilot Actions" }
93 },
94 + {
95 + path: "/copilot-searches",
96 + name: "CopilotSearches",
97 + component: () => import("@/views/agents/CopilotSearches.vue"),
98 + meta: {
99 + title: "CoPilot Searches"
100 + }
101 + },
102 {
103 path: "vulnerability-overview",
104 name: "VulnerabilityOverview",
frontend/src/stores/auth.ts
+1 -1
@@ -19,7 +19,7 @@ export const useAuthStore = defineStore("auth", {
19 email: "",
20 role: AuthUserRole.Unknown
21 } as AuthUser,
22 - tokenDebounceTime: _toNumber(import.meta.env.VITE_TOKEN_DEBOUNCE_TIME) as number // seconds
22 + tokenDebounceTime: _toNumber(import.meta.env.VITE_TOKEN_DEBOUNCE_TIME) // seconds
23 }),
24 actions: {
25 setLogged(token: string) {
frontend/src/stores/caseReportTemplate.ts
+3 -3
@@ -17,15 +17,15 @@ export const useCaseReportTemplateStore = defineStore("caseReportTemplate", {
17 },
18 setTemplatesList(templatesList: string[]) {
19 this.templatesList = templatesList
20 - this.checkedAt = new Date().getTime()
20 + this.checkedAt = Date.now()
21 },
22 addTemplate(templateName: string) {
23 this.templatesList.push(templateName)
24 - this.checkedAt = new Date().getTime()
24 + this.checkedAt = Date.now()
25 },
26 removeTemplate(templateName: string) {
27 this.templatesList = this.templatesList.filter(o => o !== templateName)
28 - this.checkedAt = new Date().getTime()
28 + this.checkedAt = Date.now()
29 },
30 setLoading(value: boolean) {
31 this.loading = value
frontend/src/stores/healthcheck.ts
+2 -4
@@ -8,10 +8,8 @@ import { useAuthStore } from "./auth"
8
9 export const useHealthcheckStore = defineStore("healthcheck", {
10 state: () => ({
11 - uncommittedJournalEntriesThreshold: _toNumber(
12 - import.meta.env.VITE_UNCOMMITTED_JOURNAL_ENTRIES_THRESHOLD
13 - ) as number,
14 - healthchecksInterval: (_toNumber(import.meta.env.VITE_HEALTHCHECKS_INTERVAL) * 1000) as number,
11 + uncommittedJournalEntriesThreshold: _toNumber(import.meta.env.VITE_UNCOMMITTED_JOURNAL_ENTRIES_THRESHOLD),
12 + healthchecksInterval: _toNumber(import.meta.env.VITE_HEALTHCHECKS_INTERVAL) * 1000,
13 getDataTimer: null as NodeJS.Timeout | null,
14 uncommittedJournalEntries: 0 as number | null,
15 clusterName: "" as string | null,
frontend/src/stores/main.ts
+2 -2
@@ -3,12 +3,12 @@ import { acceptHMRUpdate, defineStore } from "pinia"
3
4 export const useMainStore = defineStore("main", {
5 state: () => ({
6 - forceRefresh: new Date().getTime(),
6 + forceRefresh: Date.now(),
7 loadingBar: null as LoadingBarInst | null
8 }),
9 actions: {
10 softReload() {
11 - this.forceRefresh = new Date().getTime()
11 + this.forceRefresh = Date.now()
12 this.loadingBar?.start()
13 setTimeout(() => {
14 this.loadingBar?.finish()
frontend/src/types/agents.d.ts
+1 -1
@@ -1,4 +1,4 @@
1 -// TODO: refactor
1 +// TODO-FE: refactor
2 export interface Agent {
3 id?: number
4 agent_id: string
frontend/src/types/artifacts.d.ts
+1 -1
@@ -1,4 +1,4 @@
1 -// TODO: refactor
1 +// TODO-FE: refactor
2 export interface Artifact {
3 description: string
4 name: string
frontend/src/types/common.d.ts
+1 -1
@@ -3,7 +3,7 @@ import type { FlaskBaseResponse } from "./flask"
3
4 export type OsTypesFull = "Unknown" | "Windows" | "MacOS" | "UNIX" | "Linux"
5 export type OsTypesLower = "linux" | "windows" | "macos"
6 -export type SafeAny = string | number | boolean | object
6 +export type SafeAny = string | number | boolean | object | Date | null
7 export type ApiError = AxiosError<FlaskBaseResponse>
8 export type ApiCommonResponse<T = unknown> = AxiosResponse<FlaskBaseResponse & T>
9 export type DeepNullable<T> = {
frontend/src/types/copilotSearches.d.ts new
+211
@@ -0,0 +1,211 @@
1 +export type PlatformFilter = "all" | "linux" | "windows"
2 +export type RuleStatus = "production" | "experimental" | "deprecated"
3 +export type RuleSeverity = "low" | "medium" | "high" | "critical"
4 +
5 +export interface ParameterSchema {
6 + name: string
7 + description: string
8 + type: string
9 + required: boolean
10 + default?: string | number | boolean | null
11 + example?: string | number | boolean | null
12 +}
13 +
14 +export interface GraylogQuery {
15 + query: string
16 +}
17 +
18 +export interface RuleSummary {
19 + id: string
20 + name: string
21 + version: number
22 + status: string
23 + type: string
24 + description: string
25 + author: string
26 + date: string
27 + severity: string
28 + risk_score: number
29 + platform: string
30 + mitre_attack_id: string[]
31 + analytic_story: string[]
32 + cve: string[]
33 + file_path: string
34 + has_graylog_query: boolean
35 +}
36 +
37 +export interface RuleDetail {
38 + id: string
39 + name: string
40 + version: number
41 + schema_version: string
42 + status: string
43 + type: string
44 + description: string
45 + author: string
46 + date: string
47 + data_source: string[]
48 + search: Record<string, unknown>
49 + parameters: ParameterSchema[]
50 + how_to_implement: string
51 + known_false_positives: string
52 + references: string[]
53 + response: RuleResponse
54 + tags: RuleTags
55 + file_path: string
56 + raw_yaml: string
57 + graylog: GraylogQuery | null
58 +}
59 +
60 +export interface RuleResponse {
61 + message: string
62 + risk_score: number
63 + severity: string
64 + risk_objects: RiskObject[]
65 + threat_objects: ThreatObject[]
66 +}
67 +
68 +export interface RiskObject {
69 + field: string
70 + type: string
71 + score: number
72 +}
73 +
74 +export interface ThreatObject {
75 + field: string
76 + type: string
77 +}
78 +
79 +export interface RuleTags {
80 + analytic_story: string[]
81 + asset_type: string
82 + mitre_attack_id: string[]
83 + product: string[]
84 + security_domain: string
85 + cve?: string[]
86 +}
87 +
88 +export interface RuleListResponse {
89 + success: boolean
90 + message: string
91 + total: number
92 + filtered: number
93 + platform: string
94 + rules: RuleSummary[]
95 +}
96 +
97 +export interface RuleDetailResponse {
98 + success: boolean
99 + message: string
100 + rule: RuleDetail
101 +}
102 +
103 +export interface RuleStatsResponse {
104 + success: boolean
105 + message: string
106 + total_rules: number
107 + by_platform: Record<string, number>
108 + by_status: Record<string, number>
109 + by_severity: Record<string, number>
110 + by_mitre_tactic: Record<string, number>
111 + rules_with_graylog: number
112 + last_refreshed: string | null
113 + cache_ttl_minutes: number
114 +}
115 +
116 +export interface RefreshResponse {
117 + success: boolean
118 + message: string
119 + rules_loaded: number
120 + timestamp: string
121 +}
122 +
123 +// Search Execution Types
124 +
125 +export interface ExecuteSearchRequest {
126 + rule_id: string
127 + index_pattern: string
128 + parameters: Record<string, string | number | boolean>
129 + size?: number
130 +}
131 +
132 +export interface SearchHit {
133 + index: string
134 + id: string
135 + score: number | null
136 + source: Record<string, unknown>
137 +}
138 +
139 +export interface ExecuteSearchResponse {
140 + success: boolean
141 + message: string
142 + rule_id: string
143 + rule_name: string
144 + total_hits: number
145 + returned_hits: number
146 + took_ms: number
147 + hits: SearchHit[]
148 + query_executed: Record<string, unknown>
149 +}
150 +
151 +export interface SearchValidationError {
152 + parameter: string
153 + message: string
154 +}
155 +
156 +export interface ExecuteSearchErrorResponse {
157 + success: boolean
158 + message: string
159 + rule_id?: string
160 + validation_errors: SearchValidationError[]
161 +}
162 +
163 +// Graylog Query Types
164 +
165 +export interface ExecuteGraylogQueryRequest {
166 + rule_id: string
167 + parameters?: Record<string, string | number | boolean>
168 +}
169 +
170 +export interface GraylogQueryResponse {
171 + success: boolean
172 + message: string
173 + rule_id: string
174 + rule_name: string
175 + graylog_query: string
176 + original_query: string
177 +}
178 +
179 +// Graylog Alert Provisioning Types
180 +
181 +export interface ProvisionGraylogAlertRequest {
182 + rule_id: string
183 + search_within_seconds?: number
184 + execute_every_seconds?: number
185 + streams?: string[]
186 + custom_title?: string
187 + priority?: 1 | 2 | 3
188 + event_limit?: number
189 +}
190 +
191 +export interface ProvisionGraylogAlertResponse {
192 + success: boolean
193 + message: string
194 + rule_id: string
195 + rule_name: string
196 + alert_title: string
197 + graylog_query: string
198 +}
199 +
200 +// Query Parameters
201 +
202 +export interface RuleListQuery {
203 + platform?: PlatformFilter
204 + status?: RuleStatus
205 + severity?: RuleSeverity
206 + mitre_id?: string
207 + search?: string
208 + has_graylog?: boolean
209 + skip?: number
210 + limit?: number
211 +}
frontend/src/types/customers.d.ts
+1 -1
@@ -13,7 +13,7 @@ export interface Customer {
13 postal_code: string
14 country: string
15 customer_type: string
16 - // TODO: consider removing it (logo_file)
16 + // TODO-FE: consider removing it (logo_file)
17 logo_file: string
18 is_provisioned?: boolean
19 }
frontend/src/types/incidentManagement/cases.d.ts
+10
@@ -25,6 +25,16 @@ export type CaseStatus = AlertStatus
25
26 export type CasePayload = Omit<Case, "id" | "alerts">
27
28 +export interface CasesListResponse {
29 + cases: Case[]
30 + total: number | null
31 + open: number | null
32 + in_progress: number | null
33 + closed: number | null
34 + success: boolean
35 + message: string
36 +}
37 +
38 export interface CaseDataStore {
39 id: number
40 case_id: number
frontend/src/types/patchTuesday.d.ts
+1 -1
@@ -1,4 +1,4 @@
1 -// TODO: refactor
1 +// TODO-FE: refactor
2 export interface CVSSInfo {
3 base: number | null
4 vector: string | null
frontend/src/types/sca.d.ts
+1 -1
@@ -1,4 +1,4 @@
1 -// TODO: refactor
1 +// TODO-FE: refactor
2 export interface AgentScaOverviewItem {
3 agent_id: string
4 agent_name: string
frontend/src/types/vulnerabilities.d.ts
+1 -1
@@ -1,4 +1,4 @@
1 -// TODO: refactor
1 +// TODO-FE: refactor
2 export interface VulnerabilitySearchItem {
3 cve_id: string
4 severity: VulnerabilitySeverity
frontend/src/utils/highlighter.ts
+5 -2
@@ -50,6 +50,9 @@ export const codeThemes = {
50 dark: THEME_DARK
51 }
52
53 +const LEADING_WHITESPACE_REGEX = /^\s+/
54 +const TAB_CHAR_REGEX = /\t/g
55 +
56 export function resetIndent(el: HTMLElement) {
57 if (el) {
58 let lines: string[] = el.innerHTML?.split("\n")
@@ -59,12 +62,12 @@ export function resetIndent(el: HTMLElement) {
62 lines.shift()
63 }
64
62 - const matches = /^\s+/.exec(lines[0] ?? "") ?? null
65 + const matches = LEADING_WHITESPACE_REGEX.exec(lines[0] ?? "") ?? null
66 const indentation = matches !== null ? (matches[0] ?? "") : ""
67 if (indentation) {
68 lines = lines.map(line => {
69 line = line.replace(indentation, "")
67 - return line.replace(/\t/g, " ")
70 + return line.replace(TAB_CHAR_REGEX, " ")
71 })
72
73 el.innerHTML = lines.join("\n").trim()
frontend/src/utils/index.ts
+42 -8
@@ -1,11 +1,13 @@
1 import type { Component } from "vue"
2 -import type { OsTypesFull } from "@/types/common.d"
2 +import type { OsTypesFull, SafeAny } from "@/types/common.d"
3 import process from "node:process"
4 import { isMobile as detectMobile } from "detect-touch-device"
5 import { md5 } from "js-md5"
6 +import isDateObject from "lodash/isDate"
7 import _trim from "lodash/trim"
8 import { h } from "vue"
9 import Icon from "@/components/common/Icon.vue"
10 +import dayjs from "./dayjs"
11
12 // Transform File Instance in base64 string
13 export function file2Base64(blob: Blob): Promise<string> {
@@ -31,9 +33,10 @@ export function isMobile() {
33 return detectMobile
34 }
35
36 +const URL_PATTERN = /^https?:\/\//i
37 +
38 export function isUrlLike(text: string) {
35 - const urlPattern = /^https?:\/\//i
36 - return urlPattern.test(text)
39 + return URL_PATTERN.test(text)
40 }
41
42 export function renderIcon(icon: Component | string) {
@@ -45,13 +48,13 @@ export function renderIcon(icon: Component | string) {
48 }
49
50 export function iconFromOs(os: string): string {
48 - switch (getOS(os)) {
49 - case "Windows":
51 + switch (getOS(os).toLowerCase()) {
52 + case "windows":
53 return "mdi:microsoft"
51 - case "MacOS":
54 + case "macos":
55 return "mdi:apple"
53 - case "Linux":
54 - case "UNIX":
56 + case "linux":
57 + case "unix":
58 return "mdi:linux"
59 default:
60 return "mdi:help-box"
@@ -139,3 +142,34 @@ export function getAvatar(params: { seed: string; text?: string; size?: number;
142
143 return `https://avatar.vercel.sh/${params.seed}.${format}?text=${params.text || ""}&size=${params.size || 32}`
144 }
145 +
146 +const NUMERIC_TIMESTAMP_REGEX = /^\d{10,}$/
147 +
148 +export function isDate(val?: SafeAny): boolean {
149 + if (val === undefined || val === null || val === "") return false
150 +
151 + if (isDateObject(val)) return true
152 +
153 + const strVal = String(val)
154 +
155 + // Check for numeric timestamps (seconds, ms or µs)
156 + // We enforce a minimum length of 10 digits to avoid false positives like "188" or "2"
157 + if (NUMERIC_TIMESTAMP_REGEX.test(strVal)) {
158 + const num = Number.parseInt(strVal)
159 + // Handle ms (13 digits) or µs (16+ digits) by normalizing to ms
160 + const date = strVal.length >= 13 ? dayjs(num / 10 ** (strVal.length - 13)) : dayjs(num * 1000)
161 + return date.isValid()
162 + }
163 +
164 + // For ISO strings and other complex formats, we use a hybrid approach
165 + // 1. Check for ISO-like strings (containing T and possibly Z or +/- offset)
166 + if (strVal.includes("T")) {
167 + // dayjs() parser is quite robust for ISO 8601 even without explicit format
168 + return dayjs(strVal).isValid()
169 + }
170 +
171 + // 2. Strict parsing for regional formats
172 + const regionalFormats = ["DD/MM/YYYY", "MM/DD/YYYY", "DD-MM-YYYY", "MM-DD-YYYY", "YYYY-MM-DD", "YYYY-DD-MM"]
173 +
174 + return dayjs(strVal, regionalFormats, true).isValid()
175 +}
frontend/src/utils/theme.ts
+3 -1
@@ -92,8 +92,10 @@ export function getThemeColors(colors: Record<ColorType, string>) {
92 * @returns An array of expanded strings, e.g.
93 * ["brand-seablue-10", "brand-seablue-20", ..., "brand-green-50"]
94 */
95 +const PARENTHESIS_GROUP_REGEX = /\(([^)]+)\)/
96 +
97 export function expandPattern(input: string): string[] {
96 - const match = input.match(/\(([^)]+)\)/)
98 + const match = input.match(PARENTHESIS_GROUP_REGEX)
99 if (!match) {
100 // If there are no more parentheses, returns the input as an array
101 return [input]
frontend/src/views/Auth.vue
+1 -1
@@ -3,7 +3,7 @@
3 <div v-if="!isLogged" class="wrapper flex justify-center">
4 <div v-if="align === 'right'" class="image-box basis-2/3" />
5 <div class="form-box flex basis-1/3 items-center justify-center" :class="{ centered: align === 'center' }">
6 - <AuthForm :type="type" />
6 + <AuthForm :type />
7 </div>
8 <div v-if="align === 'left'" class="image-box basis-2/3">
9 <video playsinline autoplay muted loop poster="/images/login/cover.webp">
frontend/src/views/Customers.vue
+1 -1
@@ -1,7 +1,7 @@
1 <template>
2 <div class="page">
3 <CustomersList
4 - :highlight="highlight"
4 + :highlight
5 :reload
6 @loaded="
7 (() => {
frontend/src/views/Indices.vue
+5 -5
@@ -1,11 +1,11 @@
1 <template>
2 <div class="page">
3 <div class="section">
4 - <IndicesMarquee :indices="indices" @click="setIndex" />
4 + <IndicesMarquee :indices @click="setIndex" />
5 </div>
6
7 <div class="section">
8 - <Details v-model="currentIndex" :indices="indices" />
8 + <Details v-model="currentIndex" :indices />
9 </div>
10
11 <div class="section">
@@ -14,7 +14,7 @@
14 <ClusterHealth class="stretchy" />
15 </div>
16 <div class="col basis-1/2">
17 - <UnhealthyIndices :indices="indices" class="stretchy" @click="setIndex" />
17 + <UnhealthyIndices :indices class="stretchy" @click="setIndex" />
18 </div>
19 </div>
20 </div>
@@ -29,7 +29,7 @@
29 <NodeAllocation class="stretchy" style="border-radius: 0" :bordered="false" />
30 </div>
31 <div class="col basis-3/5 overflow-hidden">
32 - <TopIndices :indices="indices" style="border-radius: 0" :bordered="false" />
32 + <TopIndices :indices style="border-radius: 0" :bordered="false" />
33 </div>
34 </div>
35 </n-card>
@@ -37,7 +37,7 @@
37 </template>
38
39 <script lang="ts" setup>
40 -// TODO: refactor
40 +// TODO-FE: refactor
41 import type { IndexStats } from "@/types/indices.d"
42 import { NCard, useMessage } from "naive-ui"
43 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
frontend/src/views/Logs.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <div class="page">
3 - <LogsList :user-id="userId" />
3 + <LogsList :user-id />
4 </div>
5 </template>
6
frontend/src/views/Overview.vue
+12 -12
@@ -1,23 +1,23 @@
1 <template>
2 - <div ref="page" class="page">
2 + <div ref="page" class="page @container">
3 <!-- Add version banner at the top -->
4 <div class="section">
5 <VersionUpdateBanner />
6 </div>
7
8 - <div class="section flex justify-end gap-3 min-[70rem]:justify-between">
9 - <div class="left-box hidden gap-3 min-[70rem]:flex">
10 - <StackProvisioningButton size="small" type="primary" />
11 - <CloudSecurityAssessmentButton size="small" type="primary" />
12 - <WebVulnerabilityAssessmentButton size="small" type="primary" />
13 - <GitHubAuditButton size="small" type="primary" />
8 + <div class="section flex justify-end gap-3 @[70rem]:justify-between">
9 + <div class="left-box hidden gap-3 @[70rem]:flex">
10 + <StackProvisioningButton size="small" type="primary" secondary />
11 + <CloudSecurityAssessmentButton size="small" type="primary" secondary />
12 + <WebVulnerabilityAssessmentButton size="small" type="primary" secondary />
13 + <GitHubAuditButton size="small" type="primary" secondary />
14 </div>
15 - <div class="right-box hidden gap-3 min-[70rem]:flex">
16 - <ActiveResponseWizardButton size="small" type="primary" />
17 - <ThreatIntelButton size="small" type="primary" />
15 + <div class="right-box hidden gap-3 @[70rem]:flex">
16 + <ActiveResponseWizardButton size="small" type="primary" secondary />
17 + <ThreatIntelButton size="small" type="primary" secondary />
18 </div>
19 - <div class="mobile-box block min-[70rem]:hidden">
20 - <n-button size="small" type="primary" @click="showQuickActions = true">
19 + <div class="mobile-box block @[70rem]:hidden">
20 + <n-button size="small" type="primary" secondary @click="showQuickActions = true">
21 <template #icon>
22 <Icon :name="QuickActionsIcon" />
23 </template>
frontend/src/views/ReportCreation.vue
+1 -8
@@ -10,14 +10,7 @@
10 @panels="panels = $event"
11 />
12
13 - <ReportPanels
14 - v-if="licenseResponse"
15 - :timerange="timerange"
16 - :org="org"
17 - :dashboard="dashboard"
18 - :panels="panels"
19 - class="animate-fade"
20 - />
13 + <ReportPanels v-if="licenseResponse" :timerange :org :dashboard :panels class="animate-fade" />
14
15 <div v-if="licenseResponse" class="over-layer mobile-layer">
16 <n-alert>
frontend/src/views/Snapshots.vue
+1 -1
@@ -19,7 +19,7 @@
19 </template>
20
21 <script setup lang="ts">
22 -// TODO: refactor
22 +// TODO-FE: refactor
23 import { NTabPane, NTabs } from "naive-ui"
24 import { ref } from "vue"
25 import SnapshotList from "@/components/snapshots/SnapshotList.vue"
frontend/src/views/Users.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <div class="page">
3 - <UsersList :highlight="highlight" />
3 + <UsersList :highlight />
4 </div>
5 </template>
6
frontend/src/views/agents/Agents.vue
+6 -11
@@ -7,9 +7,9 @@
7 :syncing="loadingSync"
8 :agents-length="agents.length"
9 :agents-filtered-length="agentsFiltered.length"
10 - :agents-critical="agentsCritical"
11 - :agents-online="agentsOnline"
12 - :selection-mode="selectionMode"
10 + :agents-critical
11 + :agents-online
12 + :selection-mode
13 :selected-count="selectedAgents.length"
14 @run="runCommand($event)"
15 @click="routeAgent($event.agent_id).navigate()"
@@ -50,12 +50,7 @@
50 </n-spin>
51
52 <div class="pagination-wrapper">
53 - <n-pagination
54 - v-model:page="page"
55 - :page-size="pageSize"
56 - :page-slot="5"
57 - :item-count="agentsFiltered.length"
58 - />
53 + <n-pagination v-model:page="page" :page-size :page-slot="5" :item-count="agentsFiltered.length" />
54 </div>
55 </div>
56 </div>
@@ -63,7 +58,7 @@
58 <!-- Bulk Delete Modal -->
59 <BulkDeleteModal
60 v-model:show="showBulkDeleteModal"
66 - :selected-agents="selectedAgents"
61 + :selected-agents
62 :customers="uniqueCustomers"
63 @remove-selection="removeFromSelection"
64 @deleted="onBulkDeleteComplete"
@@ -137,7 +132,7 @@ const agentsOnline = computed(() => {
132 // Get unique customer codes for filter dropdown
133 const uniqueCustomers = computed(() => {
134 const codes = new Set(agents.value.map(a => a.customer_code).filter(Boolean))
140 - return Array.from(codes) as string[]
135 + return [...codes] as string[]
136 })
137
138 // Selection helpers
frontend/src/views/agents/CopilotSearches.vue new
+9
@@ -0,0 +1,9 @@
1 +<template>
2 + <div class="page">
3 + <List />
4 + </div>
5 +</template>
6 +
7 +<script setup lang="ts">
8 +import List from "@/components/copilotSearches/List.vue"
9 +</script>
frontend/src/views/agents/Overview.vue
+2 -2
@@ -132,7 +132,7 @@
132 />
133 </n-tab-pane>
134 <n-tab-pane name="active-response" tab="Active Response" display-directive="show:lazy">
135 - <ActiveResponseAgent v-if="agent" :agent="agent" embedded />
135 + <ActiveResponseAgent v-if="agent" :agent embedded />
136 </n-tab-pane>
137 <n-tab-pane name="file-collection" tab="File Collection" display-directive="show:lazy">
138 <div class="section">
@@ -141,7 +141,7 @@
141 </n-tab-pane>
142 <n-tab-pane name="data-store" tab="Data Store" display-directive="show:lazy">
143 <div class="section">
144 - <AgentDataStoreTab v-if="agent" :agent="agent" />
144 + <AgentDataStoreTab v-if="agent" :agent />
145 </div>
146 </n-tab-pane>
147 </n-tabs>
frontend/src/views/graylog/Metrics.vue
+2 -2
@@ -2,7 +2,7 @@
2 <div class="page">
3 <div class="header flex flex-wrap items-center justify-between gap-4">
4 <div class="info flex items-center gap-3">
5 - <n-button size="small" type="primary" secondary :loading="loading" @click="getData()">
5 + <n-button size="small" type="primary" secondary :loading @click="getData()">
6 <template #icon>
7 <Icon :name="UpdatedIcon" :size="15" />
8 </template>
@@ -33,7 +33,7 @@
33 </div>
34
35 <div>
36 - <MetricsList :throughput-metrics="throughputMetrics" />
36 + <MetricsList :throughput-metrics />
37 </div>
38 </div>
39 </template>
frontend/src/views/soc/Alerts.vue
+1 -1
@@ -1,6 +1,6 @@
1 <template>
2 <div class="page">
3 - <SocAlertsFullList :highlight="highlight" />
3 + <SocAlertsFullList :highlight />
4 </div>
5 </template>
6
frontend/vite.config.ts
+3 -1
@@ -29,7 +29,9 @@ export default defineConfig(({ mode }) => {
29 }
30 }),
31 vueJsx(),
32 - VueDevTools(),
32 + VueDevTools({
33 + launchEditor: "cursor"
34 + }),
35 svgLoader()
36 // uncomment to enable analyzer after build
37 // analyzer()
mkdocs.yml
+1
@@ -123,6 +123,7 @@ nav:
123 - Management: user/ui/graylog-management.md
124 - Metrics: user/ui/graylog-metrics.md
125 - Pipelines: user/ui/graylog-pipelines.md
126 + - Threshold Alerts: user/ui/alerts-graylog-threshold.md
127 - Connectors: user/ui/connectors.md
128 - External Services:
129 - External Services: user/ui/external-services.md