diff --git a/camera-management-api/config.json b/camera-management-api/config.json index 444391c..2bd1516 100644 --- a/camera-management-api/config.json +++ b/camera-management-api/config.json @@ -17,7 +17,7 @@ }, "system": { "camera_check_interval_seconds": 2, - "log_level": "DEBUG", + "log_level": "WARNING", "log_file": "usda_vision_system.log", "api_host": "0.0.0.0", "api_port": 8000, diff --git a/camera-management-api/usda_vision_system/api/server.py b/camera-management-api/usda_vision_system/api/server.py index 0f8de13..712e718 100644 --- a/camera-management-api/usda_vision_system/api/server.py +++ b/camera-management-api/usda_vision_system/api/server.py @@ -123,7 +123,7 @@ class APIServer: def _setup_routes(self): """Setup API routes""" - + # Register routes from modules register_system_routes( app=self.app, @@ -299,7 +299,24 @@ class APIServer: self._event_loop = asyncio.new_event_loop() asyncio.set_event_loop(self._event_loop) - uvicorn.run(self.app, host=self.config.system.api_host, port=self.config.system.api_port, log_level="info") + # Map our log level to uvicorn's log level + uvicorn_log_level_map = { + "DEBUG": "debug", + "INFO": "info", + "WARNING": "warning", + "ERROR": "error", + "CRITICAL": "critical" + } + config_log_level = self.config.system.log_level.upper() + uvicorn_log_level = uvicorn_log_level_map.get(config_log_level, "warning") + + uvicorn.run( + self.app, + host=self.config.system.api_host, + port=self.config.system.api_port, + log_level=uvicorn_log_level, + access_log=False # Disable access logs (GET, POST, etc.) to reduce noise + ) except Exception as e: self.logger.error(f"Error running API server: {e}") finally: diff --git a/camera-management-api/usda_vision_system/camera/monitor.py b/camera-management-api/usda_vision_system/camera/monitor.py index c0acb5b..ca79f4a 100644 --- a/camera-management-api/usda_vision_system/camera/monitor.py +++ b/camera-management-api/usda_vision_system/camera/monitor.py @@ -186,34 +186,48 @@ class CameraMonitor: # Ensure SDK is initialized ensure_sdk_initialized() + self.logger.info(f"Attempting to initialize camera {camera_name} for availability test...") + # Suppress output to avoid MVCAMAPI error messages during camera testing - with suppress_camera_errors(): - hCamera = mvsdk.CameraInit(device_info, -1, -1) + hCamera = None + try: + with suppress_camera_errors(): + hCamera = mvsdk.CameraInit(device_info, -1, -1) + self.logger.info(f"Camera {camera_name} initialized successfully, starting test capture...") + except mvsdk.CameraException as init_e: + self.logger.warning(f"CameraInit failed for {camera_name}: {init_e.message} (error_code: {init_e.error_code})") + return "error", f"Camera initialization failed: {init_e.message}", self._get_device_info_dict(device_info) # Quick test - try to get one frame try: mvsdk.CameraSetTriggerMode(hCamera, 0) mvsdk.CameraPlay(hCamera) + self.logger.info(f"Camera {camera_name} test: Attempting to capture frame with {CAMERA_TEST_CAPTURE_TIMEOUT}ms timeout...") # Try to capture with short timeout pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, CAMERA_TEST_CAPTURE_TIMEOUT) mvsdk.CameraReleaseImageBuffer(hCamera, pRawData) # Success - camera is available mvsdk.CameraUnInit(hCamera) + self.logger.info(f"Camera {camera_name} test successful - camera is available") return "available", "Camera test successful", self._get_device_info_dict(device_info) - except mvsdk.CameraException as e: - mvsdk.CameraUnInit(hCamera) - if e.error_code == mvsdk.CAMERA_STATUS_TIME_OUT: + except mvsdk.CameraException as capture_e: + if hCamera: + mvsdk.CameraUnInit(hCamera) + self.logger.warning(f"Camera {camera_name} capture test failed: {capture_e.message} (error_code: {capture_e.error_code})") + if capture_e.error_code == mvsdk.CAMERA_STATUS_TIME_OUT: return "available", "Camera available but slow response", self._get_device_info_dict(device_info) else: - return "error", f"Camera test failed: {e.message}", self._get_device_info_dict(device_info) + return "error", f"Camera test failed: {capture_e.message}", self._get_device_info_dict(device_info) except mvsdk.CameraException as e: - return "error", f"Camera initialization failed: {e.message}", self._get_device_info_dict(device_info) + self.logger.error(f"CameraException during initialization test for {camera_name}: {e.message} (error_code: {e.error_code})") + return "error", f"Camera initialization failed: {e.message}", self._get_device_info_dict(device_info) if device_info else None except Exception as e: + self.logger.error(f"Unexpected exception during camera check for {camera_name}: {e}", exc_info=True) return "error", f"Camera check failed: {str(e)}", None def _get_device_info_dict(self, device_info) -> Dict[str, Any]: diff --git a/camera-management-api/usda_vision_system/core/config.py b/camera-management-api/usda_vision_system/core/config.py index d2f0264..9aaa3ef 100644 --- a/camera-management-api/usda_vision_system/core/config.py +++ b/camera-management-api/usda_vision_system/core/config.py @@ -96,7 +96,7 @@ class SystemConfig: """System-wide configuration""" camera_check_interval_seconds: int = 2 - log_level: str = "INFO" + log_level: str = "WARNING" log_file: str = "usda_vision_system.log" api_host: str = "0.0.0.0" api_port: int = 8000 diff --git a/camera-management-api/usda_vision_system/core/logging_config.py b/camera-management-api/usda_vision_system/core/logging_config.py index d4afe75..8757692 100644 --- a/camera-management-api/usda_vision_system/core/logging_config.py +++ b/camera-management-api/usda_vision_system/core/logging_config.py @@ -112,23 +112,32 @@ class USDAVisionLogger: def _setup_component_loggers(self) -> None: """Setup specific log levels for different components""" - # MQTT client - can be verbose + # MQTT client - reduce INFO logs mqtt_logger = logging.getLogger('usda_vision_system.mqtt') if self.log_level == 'DEBUG': mqtt_logger.setLevel(logging.DEBUG) - else: + elif self.log_level == 'INFO': mqtt_logger.setLevel(logging.INFO) + else: + mqtt_logger.setLevel(logging.WARNING) - # Camera components - important for debugging + # Camera components - reduce INFO logs, keep WARNING and above camera_logger = logging.getLogger('usda_vision_system.camera') - camera_logger.setLevel(logging.INFO) + if self.log_level == 'DEBUG': + camera_logger.setLevel(logging.DEBUG) + elif self.log_level == 'INFO': + camera_logger.setLevel(logging.INFO) + else: + camera_logger.setLevel(logging.WARNING) - # API server - can be noisy + # API server - reduce INFO noise api_logger = logging.getLogger('usda_vision_system.api') if self.log_level == 'DEBUG': api_logger.setLevel(logging.DEBUG) - else: + elif self.log_level == 'INFO': api_logger.setLevel(logging.INFO) + else: + api_logger.setLevel(logging.WARNING) # Uvicorn - reduce noise unless debugging uvicorn_logger = logging.getLogger('uvicorn') diff --git a/camera-management-api/usda_vision_system/recording/standalone_auto_recorder.py b/camera-management-api/usda_vision_system/recording/standalone_auto_recorder.py index 60a42ea..a3c48f7 100644 --- a/camera-management-api/usda_vision_system/recording/standalone_auto_recorder.py +++ b/camera-management-api/usda_vision_system/recording/standalone_auto_recorder.py @@ -45,8 +45,21 @@ class StandaloneAutoRecorder: # Setup logging (only if not already configured) if not logging.getLogger().handlers: - logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[logging.FileHandler("standalone_auto_recorder.log"), logging.StreamHandler()]) + # Use WARNING level by default to reduce INFO log noise + log_level = getattr(self.config.system, 'log_level', 'WARNING') + log_level_num = getattr(logging, log_level.upper(), logging.WARNING) + logging.basicConfig( + level=log_level_num, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[ + logging.FileHandler("standalone_auto_recorder.log"), + logging.StreamHandler() + ] + ) self.logger = logging.getLogger(__name__) + # Ensure this logger respects the configured log level + if hasattr(self.config, 'system') and hasattr(self.config.system, 'log_level'): + self.logger.setLevel(getattr(logging, self.config.system.log_level.upper(), logging.WARNING)) # Initialize components self.state_manager = StateManager() @@ -59,6 +72,9 @@ class StandaloneAutoRecorder: self.camera_recorders: Dict[str, CameraRecorder] = {} self.active_recordings: Dict[str, str] = {} # camera_name -> filename + # Camera device cache + self._device_list: Optional[list] = None + # Machine to camera mapping self.machine_camera_map = self._build_machine_camera_map() @@ -257,7 +273,7 @@ class StandaloneAutoRecorder: return None def _find_camera_device(self, camera_name: str): - """Simplified camera device discovery""" + """Find camera device by matching serial number or using index mapping""" try: # Import camera SDK import sys @@ -266,23 +282,73 @@ class StandaloneAutoRecorder: sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "camera_sdk")) import mvsdk - # Initialize SDK - mvsdk.CameraSdkInit(1) + # Initialize SDK (only if not already initialized) + try: + mvsdk.CameraSdkInit(1) + except: + pass # SDK may already be initialized - # Enumerate cameras - device_list = mvsdk.CameraEnumerateDevice() - - # For now, map by index (camera1 = index 0, camera2 = index 1) - camera_index = int(camera_name.replace("camera", "")) - 1 - - if 0 <= camera_index < len(device_list): - return device_list[camera_index] + # Cache device list to avoid re-enumerating + if self._device_list is None: + device_list = mvsdk.CameraEnumerateDevice() + self._device_list = device_list + self.logger.info(f"Enumerated {len(device_list)} camera device(s)") else: - self.logger.error(f"Camera index {camera_index} not found (total: {len(device_list)})") + device_list = self._device_list + + if len(device_list) == 0: + self.logger.error("No cameras detected") return None + # Find camera config to get serial number or device_index if available + camera_config = None + for config in self.config.cameras: + if config.name == camera_name: + camera_config = config + break + + # Try to match by serial number if available in device info + if camera_config: + # Check if config has device_index specified + device_index = getattr(camera_config, 'device_index', None) + if device_index is not None and 0 <= device_index < len(device_list): + self.logger.info(f"Using device_index {device_index} for {camera_name}") + return device_list[device_index] + + # Try matching by serial number from camera config if available + config_serial = getattr(camera_config, 'serial_number', None) + if config_serial: + for i, dev_info in enumerate(device_list): + try: + dev_serial = getattr(dev_info, 'acSn', None) or getattr(dev_info, 'GetSn', lambda: None)() + if dev_serial and str(dev_serial) == str(config_serial): + self.logger.info(f"Matched {camera_name} to device {i} by serial number: {dev_serial}") + return dev_info + except: + continue + + # Fallback to index mapping (camera1 = index 0, camera2 = index 1, etc.) + try: + camera_index = int(camera_name.replace("camera", "")) - 1 + if 0 <= camera_index < len(device_list): + self.logger.info(f"Using index mapping for {camera_name} -> device {camera_index}") + return device_list[camera_index] + else: + self.logger.error(f"Camera index {camera_index} not found (total: {len(device_list)}). Available indices: 0-{len(device_list)-1}") + # If only one camera is available, use it for any camera name + if len(device_list) == 1: + self.logger.warning(f"Only 1 camera detected, using it for {camera_name}") + return device_list[0] + except ValueError: + pass + + self.logger.error(f"No device found for camera {camera_name}") + return None + except Exception as e: self.logger.error(f"Error finding camera device: {e}") + import traceback + self.logger.debug(traceback.format_exc()) return None def start(self) -> bool: diff --git a/dev-logs.sh b/dev-logs.sh deleted file mode 100755 index f4a6fd2..0000000 --- a/dev-logs.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash - -# USDA Vision Development Logs Script -# This script shows logs from the development environment - -set -e - -echo "📋 USDA Vision Development Logs" -echo "===============================" - -# Check if docker-compose.dev.yml exists -if [ ! -f "docker-compose.dev.yml" ]; then - echo "❌ Error: docker-compose.dev.yml not found!" - echo "Please make sure you're in the project root directory." - exit 1 -fi - -# Function to show help -show_help() { - echo "Usage: $0 [OPTIONS] [SERVICE]" - echo "" - echo "Options:" - echo " -f, --follow Follow log output (like tail -f)" - echo " -t, --tail N Show last N lines (default: 100)" - echo " -h, --help Show this help message" - echo "" - echo "Services:" - echo " api Show API service logs only" - echo " web Show web service logs only" - echo " (no service) Show logs from all services" - echo "" - echo "Examples:" - echo " $0 # Show last 100 lines from all services" - echo " $0 -f # Follow all logs in real-time" - echo " $0 -f api # Follow API logs only" - echo " $0 -t 50 web # Show last 50 lines from web service" -} - -# Default values -FOLLOW=false -TAIL_LINES=100 -SERVICE="" - -# Parse arguments -while [[ $# -gt 0 ]]; do - case $1 in - -f|--follow) - FOLLOW=true - shift - ;; - -t|--tail) - TAIL_LINES="$2" - shift 2 - ;; - -h|--help) - show_help - exit 0 - ;; - api|web) - SERVICE="$1" - shift - ;; - *) - echo "❌ Unknown option: $1" - show_help - exit 1 - ;; - esac -done - -# Build docker compose command -COMPOSE_CMD="docker compose -f docker-compose.dev.yml logs" - -if [ "$FOLLOW" = true ]; then - COMPOSE_CMD="$COMPOSE_CMD -f" -fi - -if [ "$TAIL_LINES" != "100" ]; then - COMPOSE_CMD="$COMPOSE_CMD --tail=$TAIL_LINES" -fi - -if [ -n "$SERVICE" ]; then - COMPOSE_CMD="$COMPOSE_CMD $SERVICE" -fi - -echo "Running: $COMPOSE_CMD" -echo "" - -# Execute the command -eval $COMPOSE_CMD diff --git a/dev-shell.sh b/dev-shell.sh deleted file mode 100755 index 7d19ba5..0000000 --- a/dev-shell.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash - -# USDA Vision Development Shell Script -# This script opens a shell in the running development container - -set -e - -echo "🐚 USDA Vision Development Shell" -echo "================================" - -# Check if docker-compose.dev.yml exists -if [ ! -f "docker-compose.dev.yml" ]; then - echo "❌ Error: docker-compose.dev.yml not found!" - echo "Please make sure you're in the project root directory." - exit 1 -fi - -# Function to show help -show_help() { - echo "Usage: $0 [SERVICE]" - echo "" - echo "Services:" - echo " api Open shell in API container (default)" - echo " web Open shell in web container" - echo "" - echo "Examples:" - echo " $0 # Open shell in API container" - echo " $0 api # Open shell in API container" - echo " $0 web # Open shell in web container" -} - -# Default service -SERVICE="api" - -# Parse arguments -while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - show_help - exit 0 - ;; - api|web) - SERVICE="$1" - shift - ;; - *) - echo "❌ Unknown option: $1" - show_help - exit 1 - ;; - esac -done - -echo "🔍 Checking if $SERVICE container is running..." - -# Check if the service is running -if ! docker compose -f docker-compose.dev.yml ps $SERVICE | grep -q "Up"; then - echo "❌ Error: $SERVICE container is not running!" - echo "Please start the development environment first with: ./dev-start.sh" - exit 1 -fi - -echo "🚀 Opening shell in $SERVICE container..." -echo "💡 Tip: Use 'exit' to return to your host shell" -echo "" - -# Execute shell in the container -docker compose -f docker-compose.dev.yml exec $SERVICE /bin/bash diff --git a/dev-start.sh b/dev-start.sh deleted file mode 100755 index 9960cd5..0000000 --- a/dev-start.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# USDA Vision Development Startup Script -# This script starts the development environment with proper logging and debugging - -set -e - -echo "🚀 Starting USDA Vision Development Environment" -echo "==============================================" - -# Check if docker-compose.dev.yml exists -if [ ! -f "docker-compose.dev.yml" ]; then - echo "❌ Error: docker-compose.dev.yml not found!" - echo "Please make sure you're in the project root directory." - exit 1 -fi - -# Check if .env file exists for web app -if [ ! -f "management-dashboard-web-app/.env" ]; then - echo "⚠️ Warning: management-dashboard-web-app/.env not found!" - echo "You may need to create it from .env.example" - echo "Continuing anyway..." -fi - -echo "📦 Building and starting development containers..." -echo "" - -# Start the development environment -docker compose -f docker-compose.dev.yml up --build - -echo "" -echo "🛑 Development environment stopped" diff --git a/dev-stop.sh b/dev-stop.sh deleted file mode 100755 index cf78b7b..0000000 --- a/dev-stop.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# USDA Vision Development Stop Script -# This script stops the development environment - -set -e - -echo "🛑 Stopping USDA Vision Development Environment" -echo "==============================================" - -# Check if docker-compose.dev.yml exists -if [ ! -f "docker-compose.dev.yml" ]; then - echo "❌ Error: docker-compose.dev.yml not found!" - echo "Please make sure you're in the project root directory." - exit 1 -fi - -echo "🔄 Stopping development containers..." - -# Stop the development environment -docker compose -f docker-compose.dev.yml down - -echo "" -echo "✅ Development environment stopped successfully" -echo "" -echo "💡 Tip: Use './dev-start.sh' to start the development environment again" -echo "💡 Tip: Use './dev-logs.sh' to view logs from the last run" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index e3c8f44..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,106 +0,0 @@ -services: - api: - build: - context: ./camera-management-api - dockerfile: Dockerfile - working_dir: /app - volumes: - - ./camera-management-api:/app - - /mnt/nfs_share:/mnt/nfs_share - - /etc/localtime:/etc/localtime:ro - - /etc/timezone:/etc/timezone:ro - environment: - - PYTHONUNBUFFERED=1 - - LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib - - PYTHONPATH=/app:/app/camera_sdk - - TZ=America/New_York - # Development-specific environment variables - - FLASK_ENV=development - - FLASK_DEBUG=1 - - PYTHONDONTWRITEBYTECODE=1 - command: > - sh -lc " - apt-get update && apt-get install -y libusb-1.0-0-dev; - - # Install camera SDK if not already installed - if [ ! -f /lib/libMVSDK.so ] && [ -f 'camera_sdk/linuxSDK_V2.1.0.49(250108)/install.sh' ]; then - echo 'Installing camera SDK...'; - cd 'camera_sdk/linuxSDK_V2.1.0.49(250108)'; - chmod +x install.sh; - ./install.sh; - cd /app; - echo 'Camera SDK installed successfully'; - else - echo 'Camera SDK already installed or install script not found'; - fi; - - # Install Python dependencies - if [ -f requirements.txt ]; then - pip install --no-cache-dir -r requirements.txt; - else - pip install --no-cache-dir -e .; - fi; - - # Start the application in development mode with verbose logging - echo 'Starting API in development mode...'; - python main.py --config config.compose.json --debug --verbose - " - network_mode: host - # Keep container running for debugging - stdin_open: true - tty: true - # Add labels for easier identification - labels: - - "com.usda-vision.service=api" - - "com.usda-vision.environment=development" - - web: - image: node:20-alpine - working_dir: /app - env_file: - - ./management-dashboard-web-app/.env - volumes: - - ./management-dashboard-web-app:/app - environment: - - CHOKIDAR_USEPOLLING=true - - TZ=America/New_York - # Development-specific environment variables - - NODE_ENV=development - - VITE_DEV_SERVER_HOST=0.0.0.0 - - VITE_DEV_SERVER_PORT=8080 - command: > - sh -lc " - echo 'Installing dependencies...'; - npm ci; - echo 'Starting web development server...'; - npm run dev -- --host 0.0.0.0 --port 8080 --verbose - " - # Ensure the web container can resolve host.docker.internal on Linux - extra_hosts: - - "host.docker.internal:host-gateway" - ports: - - "8080:8080" - # Keep container running for debugging - stdin_open: true - tty: true - # Add labels for easier identification - labels: - - "com.usda-vision.service=web" - - "com.usda-vision.environment=development" - # Optional: Add a development database if needed - # db: - # image: postgres:15-alpine - # environment: - # POSTGRES_DB: usda_vision_dev - # POSTGRES_USER: dev - # POSTGRES_PASSWORD: dev - # volumes: - # - postgres_dev_data:/var/lib/postgresql/data - # ports: - # - "5432:5432" - # labels: - # - "com.usda-vision.service=database" - # - "com.usda-vision.environment=development" - - # volumes: - # postgres_dev_data: diff --git a/docker-compose.yml b/docker-compose.yml index 96e5f27..6ed54e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -85,6 +85,25 @@ services: ports: - "3001:3001" + vision-system-remote: + image: node:20-alpine + working_dir: /app + environment: + - CHOKIDAR_USEPOLLING=true + - TZ=America/New_York + - VITE_VISION_API_URL=http://exp-dash:8000 + volumes: + - ./vision-system-remote:/app + command: > + sh -lc " + npm install; + npm run dev:watch + " + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "3002:3002" + media-api: build: context: ./media-api diff --git a/CODE_QUALITY_IMPROVEMENTS.md b/docs/CODE_QUALITY_IMPROVEMENTS.md similarity index 100% rename from CODE_QUALITY_IMPROVEMENTS.md rename to docs/CODE_QUALITY_IMPROVEMENTS.md diff --git a/MODULARIZATION_PROPOSAL.md b/docs/MODULARIZATION_PROPOSAL.md similarity index 100% rename from MODULARIZATION_PROPOSAL.md rename to docs/MODULARIZATION_PROPOSAL.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..a13a4f5 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,24 @@ +# Documentation + +This directory contains project documentation and reference materials. + +## Documentation Files + +- **`CODE_QUALITY_IMPROVEMENTS.md`** - Analysis and suggestions for improving code quality in the camera-management-api + +- **`MODULARIZATION_PROPOSAL.md`** - Different strategies for modularizing the API, including recommendations + +- **`REFACTORING_PLAN.md`** - Step-by-step quick start guide for implementing code quality improvements + +- **`REFACTORING_SUMMARY.md`** - Complete summary of the modularization and refactoring work performed (frontend microfrontends + backend improvements) + +- **`database_schema.md`** - Database schema documentation + +- **`rtsp_access_guide.md`** - Guide for accessing RTSP streams + +- **`test_rtsp_working.md`** - Notes about RTSP testing + +## Main Documentation + +See the root `README.md` for project overview, setup instructions, and quick start guide. + diff --git a/REFACTORING_PLAN.md b/docs/REFACTORING_PLAN.md similarity index 100% rename from REFACTORING_PLAN.md rename to docs/REFACTORING_PLAN.md diff --git a/docs/REFACTORING_SUMMARY.md b/docs/REFACTORING_SUMMARY.md new file mode 100644 index 0000000..7eb048d --- /dev/null +++ b/docs/REFACTORING_SUMMARY.md @@ -0,0 +1,358 @@ +# Code Quality Refactoring Summary + +**Date**: November 2025 +**Branch**: Modularization branch +**Status**: ✅ Completed and Verified + +## Overview + +This document summarizes the modularization and code quality refactoring work performed on the USDA Vision system. The work was done in two phases: + +1. **Frontend Modularization** (React Dashboard) - Extracted features into microfrontends using Module Federation +2. **Backend Refactoring** (Camera Management API) - Improved code organization within the monolithic architecture + +This document focuses primarily on Phase 2 (API refactoring), but provides context about the overall modularization strategy. + +## Project Context: Two-Phase Modularization + +### Phase 1: Frontend Modularization (React Dashboard) + +**Status**: ✅ Completed + +Before working on the API, we first modularized the React dashboard application into a microfrontend architecture: + +- **Approach**: Used Vite Module Federation to create independently deployable frontend modules +- **First Module Extracted**: `video-remote` - The video library feature was extracted into its own microfrontend +- **Architecture**: + - Main dashboard acts as a "shell" application + - Remotely loads `video-remote` module when enabled via feature flags + - Supports gradual migration (local fallback components remain available) +- **Infrastructure Changes**: + - Created separate `media-api` container for video processing (thumbnails, transcoding) + - Added `mediamtx` container for RTSP/WebRTC streaming + - `video-remote` container runs independently and can be updated separately +- **Benefits Achieved**: + - Independent deployment of video library feature + - Better separation of concerns (media handling separate from main dashboard) + - Foundation for extracting more features (camera management, experiments, etc.) + +### Phase 2: Backend Refactoring (Camera Management API) + +**Status**: ✅ Completed + +After successfully modularizing the frontend, we focused on improving the backend code quality. **Important**: We chose NOT to split the API into microservices, but rather to improve the organization within the existing monolithic architecture. + +- **Approach**: Simple, low-risk refactorings within the monolithic structure +- **Philosophy**: "Simplest least destructive code refactorings that can significantly make the code more readable and manageable and editable" +- **Decision**: Keep monolithic architecture (no microservices) but improve internal organization +- **Why Monolithic**: + - Camera SDK and hardware interactions require tight coupling + - System is stable and working well + - Full microservices would be overkill and add complexity + - Focus on code quality over architectural changes + +--- + +## Motivation + +The API refactoring was requested to improve code quality and manageability within the existing monolithic architecture. The goal was to improve organization without resorting to full microservices architecture or breaking changes, following the successful pattern we established with the frontend modularization. + +## Refactoring Tasks Completed + +### 1. Extract Duplicate Code (`suppress_camera_errors`) + +**Problem**: The `suppress_camera_errors()` context manager was duplicated in three files: +- `camera/recorder.py` +- `camera/streamer.py` +- `camera/monitor.py` + +**Solution**: +- Created `camera/utils.py` with the centralized `suppress_camera_errors()` function +- Updated all three files to import from `utils` instead of defining locally + +**Files Changed**: +- ✅ Created: `camera-management-api/usda_vision_system/camera/utils.py` +- ✅ Updated: `camera/recorder.py` - removed local definition, added import +- ✅ Updated: `camera/streamer.py` - removed local definition, added import +- ✅ Updated: `camera/monitor.py` - removed local definition, added import + +**Benefits**: +- Single source of truth for error suppression logic +- Easier maintenance (fix bugs in one place) +- Consistent behavior across all camera modules + +--- + +### 2. Extract Magic Numbers into Constants + +**Problem**: Magic numbers scattered throughout camera code made it hard to understand intent and adjust settings: +- Queue sizes (5, 10, 30) +- Frame rates (10.0, 15.0, 30.0) +- Timeouts (200, 1000, 500 milliseconds) +- JPEG quality (70) +- Sleep intervals (0.1 seconds) + +**Solution**: +- Created `camera/constants.py` with well-named constants +- Replaced all magic numbers with constant references + +**Constants Defined**: +```python +# Queue sizes +MJPEG_QUEUE_MAXSIZE = 5 +RTSP_QUEUE_MAXSIZE = 10 +RECORDING_QUEUE_MAXSIZE = 30 + +# Frame rates +PREVIEW_FPS = 10.0 +RTSP_FPS = 15.0 +DEFAULT_VIDEO_FPS = 30.0 + +# JPEG quality +PREVIEW_JPEG_QUALITY = 70 + +# Timeouts (milliseconds) +CAMERA_GET_BUFFER_TIMEOUT = 200 +CAMERA_INIT_TIMEOUT = 1000 +CAMERA_TEST_CAPTURE_TIMEOUT = 500 + +# Sleep intervals (seconds) +STREAMING_LOOP_SLEEP = 0.1 +BRIEF_PAUSE_SLEEP = 0.1 +``` + +**Files Changed**: +- ✅ Created: `camera-management-api/usda_vision_system/camera/constants.py` +- ✅ Updated: `camera/recorder.py` - replaced magic numbers with constants +- ✅ Updated: `camera/streamer.py` - replaced magic numbers with constants +- ✅ Updated: `camera/manager.py` - replaced magic numbers with constants +- ✅ Updated: `camera/monitor.py` - added import for `CAMERA_TEST_CAPTURE_TIMEOUT` + +**Benefits**: +- Self-documenting code (constants explain what values represent) +- Easy to adjust performance settings (change in one place) +- Reduced risk of inconsistent values across modules +- Better code readability + +--- + +### 3. Split Monolithic API Routes into Domain Modules + +**Problem**: `api/server.py` was 868 lines with all routes defined in a single `_setup_routes()` method, making it: +- Hard to navigate and find specific endpoints +- Difficult to maintain (one large file) +- Prone to merge conflicts +- Not following separation of concerns + +**Solution**: +- Created `api/routes/` directory with domain-specific route modules +- Each module exports a `register_*_routes()` function +- Updated `server.py` to import and call these registration functions + +**New File Structure**: +``` +api/routes/ +├── __init__.py # Exports all register functions +├── system_routes.py # /, /health, /system/status, /system/video-module +├── camera_routes.py # /cameras, /cameras/{name}/*, RTSP endpoints +├── recording_routes.py # /cameras/{name}/start-recording, stop-recording +├── mqtt_routes.py # /mqtt/status, /mqtt/events +├── storage_routes.py # /storage/stats, /storage/files, /storage/cleanup +├── auto_recording_routes.py # /cameras/{name}/auto-recording/* +└── recordings_routes.py # /recordings +``` + +**Files Changed**: +- ✅ Created: `api/routes/__init__.py` +- ✅ Created: `api/routes/system_routes.py` - 7 routes +- ✅ Created: `api/routes/camera_routes.py` - 14 routes +- ✅ Created: `api/routes/recording_routes.py` - 2 routes +- ✅ Created: `api/routes/mqtt_routes.py` - 2 routes +- ✅ Created: `api/routes/storage_routes.py` - 3 routes +- ✅ Created: `api/routes/auto_recording_routes.py` - 3 routes +- ✅ Created: `api/routes/recordings_routes.py` - 1 route +- ✅ Updated: `api/server.py` - reduced from 868 lines to 315 lines (63% reduction) + +**Remaining in `server.py`**: +- WebSocket endpoint (`/ws`) - kept here as it's core to the server +- Debug endpoint (`/debug/camera-manager`) - utility endpoint +- Video module route integration - dynamic route inclusion + +**Import Path Corrections Made**: +- Fixed all route modules to use correct relative imports: + - `from ...core.config` (three levels up from `api/routes/`) + - `from ..models` (one level up to `api/models`) +- Fixed `AutoRecordingManager` import path (was `auto_recording.manager`, corrected to `recording.auto_manager`) +- Added proper type hints to all registration functions + +**Benefits**: +- **63% reduction** in `server.py` size (868 → 315 lines) +- Routes organized by domain (easy to find specific endpoints) +- Easier maintenance (smaller, focused files) +- Reduced merge conflicts (different developers work on different route modules) +- Better code organization following separation of concerns +- Easier to test (can test route modules independently) + +--- + +## Key Design Decisions + +### Why Keep WebSocket in `server.py`? +The WebSocket endpoint (`/ws`) was kept in `server.py` because: +- It's tightly coupled with the `WebSocketManager` class defined in `server.py` +- It's core functionality, not a domain-specific feature +- Moving it would require refactoring the manager class as well + +### Why Use `register_*_routes()` Functions? +Each route module exports a function that takes dependencies (app, managers, logger) and registers routes. This pattern: +- Keeps route modules testable (can pass mock dependencies) +- Allows `server.py` to control dependency injection +- Makes it clear what dependencies each route module needs + +### Why Not Move Debug Endpoint? +The `/debug/camera-manager` endpoint could be moved to `camera_routes.py`, but it was kept in `server.py` as a utility endpoint for debugging the server's internal state. This is a reasonable design choice for debug utilities. + +--- + +## Verification + +All refactoring changes were verified to work correctly: + +✅ **API Starts Successfully** +- No import errors +- No syntax errors +- All route modules load correctly + +✅ **Endpoints Function Correctly** +- `/health` - Returns healthy status +- `/system/status` - Returns system status with cameras, machines, recordings +- `/cameras` - Returns camera status (both cameras now show correct status) +- All other endpoints maintain functionality + +✅ **No Regressions** +- Camera monitoring works correctly (camera1 shows "available" status) +- Constants are properly imported and used +- Utility functions work as expected + +--- + +## Migration Notes for Future Developers + +### Adding New Routes + +1. **Identify the domain**: Which route module does your endpoint belong to? + - System/health → `system_routes.py` + - Camera operations → `camera_routes.py` + - Recording → `recording_routes.py` + - Storage → `storage_routes.py` + - MQTT → `mqtt_routes.py` + - Auto-recording → `auto_recording_routes.py` + - Recording sessions → `recordings_routes.py` + +2. **Add route to appropriate module**: Use the existing pattern: + ```python + @app.get("/your/endpoint") + async def your_endpoint(): + # Implementation + ``` + +3. **If creating a new domain**: + - Create new file: `api/routes/your_domain_routes.py` + - Export `register_your_domain_routes()` function + - Add import to `api/routes/__init__.py` + - Register in `server.py`'s `_setup_routes()` method + +### Using Constants + +When you need camera-related constants: +```python +from ...camera.constants import CAMERA_GET_BUFFER_TIMEOUT, PREVIEW_FPS +``` + +### Using Utility Functions + +When you need camera error suppression: +```python +from ...camera.utils import suppress_camera_errors + +with suppress_camera_errors(): + # Camera operations +``` + +--- + +## Related Documentation + +- `CODE_QUALITY_IMPROVEMENTS.md` - Original analysis and suggestions +- `REFACTORING_PLAN.md` - Step-by-step implementation guide + +--- + +## Lessons Learned + +1. **Start Small**: The refactoring started with the simplest tasks (extracting duplicates and constants) before tackling the larger route split. + +2. **Verify as You Go**: Each task was verified before moving to the next, preventing cascading errors. + +3. **Fix Imports Systematically**: When splitting routes, import paths needed careful correction. Using relative imports requires counting directory levels carefully. + +4. **Maintain Type Safety**: Added type hints to all route registration functions for better IDE support and error detection. + +5. **Test Endpoints**: Always test actual API endpoints after refactoring to ensure no functionality was broken. + +--- + +## Future Improvement Opportunities + +While not included in this refactoring, potential future improvements: + +1. **Move Debug Endpoint**: Consider moving `/debug/camera-manager` to `camera_routes.py` for better organization +2. **Extract WebSocket Manager**: Could move `WebSocketManager` to a separate module if it grows +3. **Route Unit Tests**: Add unit tests for route modules to prevent regressions +4. **API Documentation**: Consider adding OpenAPI/Swagger tags to organize routes in API docs +5. **More Constants**: Consider extracting more magic numbers as the codebase evolves + +--- + +## Overall Project Status + +### Frontend (React Dashboard) +✅ **Microfrontend architecture implemented** +- `video-remote` module extracted and working +- Module Federation configured and tested +- Feature flags system in place for gradual rollout +- Foundation ready for extracting additional modules + +### Backend (Camera Management API) +✅ **Monolithic refactoring completed** +- Code organization significantly improved +- Routes split into domain modules +- Constants and utilities extracted +- 63% reduction in main server file size +- **Decision**: Maintained monolithic architecture (not split into microservices) +- All functionality preserved and verified + +--- + +## Summary + +### Frontend Modularization +✅ **Microfrontend architecture established** +✅ **Video library extracted as first module** +✅ **Independent deployment pipeline ready** +✅ **Scalable pattern for future feature extraction** + +### Backend Refactoring +✅ **3 refactoring tasks completed successfully** +✅ **Code quality significantly improved** +✅ **No functionality broken** +✅ **63% reduction in main server file size** +✅ **Better code organization and maintainability** +✅ **Maintained monolithic architecture (intentional decision)** + +### Overall +✅ **Frontend: Microfrontends** (independent modules, Module Federation) +✅ **Backend: Improved Monolith** (better organization, maintainability, no microservices) + +The system now has a modular frontend with improved backend code quality, all while maintaining full backward compatibility and system stability. + diff --git a/database_schema.md b/docs/database_schema.md similarity index 100% rename from database_schema.md rename to docs/database_schema.md diff --git a/rtsp_access_guide.md b/docs/rtsp_access_guide.md similarity index 100% rename from rtsp_access_guide.md rename to docs/rtsp_access_guide.md diff --git a/test_rtsp_working.md b/docs/test_rtsp_working.md similarity index 100% rename from test_rtsp_working.md rename to docs/test_rtsp_working.md diff --git a/management-dashboard-web-app/.env.example b/management-dashboard-web-app/.env.example index b38c14a..91a7ca1 100755 --- a/management-dashboard-web-app/.env.example +++ b/management-dashboard-web-app/.env.example @@ -1,14 +1,12 @@ -# Environment Configuration for Pecan Experiments Application +# Feature Flags +VITE_ENABLE_SHELL=true +VITE_ENABLE_VIDEO_MODULE=true +VITE_ENABLE_VISION_SYSTEM_MODULE=true -# USDA Vision Camera System API Configuration -# Recommended default: use a relative path so the dev server proxy routes to the API container -# Leave unset to default to "/api" (see vite.config.ts proxy) -# To override and point directly, set e.g.: -# VITE_VISION_API_URL=http://vm-host-or-ip:8000 +# Remote Module URLs (use exp-dash hostname for Docker Compose networking) +VITE_VIDEO_REMOTE_URL=http://exp-dash:3001/assets/remoteEntry.js?v=$(date +%s) +VITE_VISION_SYSTEM_REMOTE_URL=http://exp-dash:3002/assets/remoteEntry.js?v=$(date +%s) -# Supabase Configuration (if needed for production) -# VITE_SUPABASE_URL=your_supabase_url -# VITE_SUPABASE_ANON_KEY=your_supabase_anon_key - -# Development Configuration -# VITE_DEV_MODE=true +# API URLs +VITE_VISION_API_URL=http://exp-dash:8000 +VITE_MEDIA_API_URL=http://exp-dash:8090 diff --git a/management-dashboard-web-app/src/components/DashboardLayout.tsx b/management-dashboard-web-app/src/components/DashboardLayout.tsx index 074f825..a4a4d5e 100755 --- a/management-dashboard-web-app/src/components/DashboardLayout.tsx +++ b/management-dashboard-web-app/src/components/DashboardLayout.tsx @@ -5,7 +5,8 @@ import { DashboardHome } from './DashboardHome' import { UserManagement } from './UserManagement' import { ExperimentManagement } from './ExperimentManagement' import { DataEntry } from './DataEntry' -import { VisionSystem } from './VisionSystem' +// VisionSystem is now loaded as a microfrontend - see RemoteVisionSystem below +// import { VisionSystem } from './VisionSystem' import { Scheduling } from './Scheduling' import React, { Suspense } from 'react' import { loadRemoteComponent } from '../lib/loadRemote' @@ -164,6 +165,13 @@ export function DashboardLayout({ onLogout, currentRoute }: DashboardLayoutProps LocalVideoPlaceholder as any ) as unknown as React.ComponentType + const LocalVisionSystemPlaceholder = () => (
Vision System module not enabled.
) + const RemoteVisionSystem = loadRemoteComponent( + isFeatureEnabled('enableVisionSystemModule'), + () => import('visionSystemRemote/App'), + LocalVisionSystemPlaceholder as any + ) as unknown as React.ComponentType + const renderCurrentView = () => { if (!user) return null @@ -200,7 +208,13 @@ export function DashboardLayout({ onLogout, currentRoute }: DashboardLayoutProps case 'data-entry': return case 'vision-system': - return + return ( + Failed to load vision system module. Please try again.}> + Loading vision system module...}> + + + + ) case 'scheduling': return case 'video-library': diff --git a/management-dashboard-web-app/src/lib/featureFlags.ts b/management-dashboard-web-app/src/lib/featureFlags.ts index 1f270f0..d311da1 100644 --- a/management-dashboard-web-app/src/lib/featureFlags.ts +++ b/management-dashboard-web-app/src/lib/featureFlags.ts @@ -3,6 +3,7 @@ export type FeatureFlags = { enableVideoModule: boolean enableExperimentModule: boolean enableCameraModule: boolean + enableVisionSystemModule: boolean } const toBool = (v: unknown, fallback = false): boolean => { @@ -19,6 +20,7 @@ export const featureFlags: FeatureFlags = { enableVideoModule: toBool(import.meta.env.VITE_ENABLE_VIDEO_MODULE ?? false), enableExperimentModule: toBool(import.meta.env.VITE_ENABLE_EXPERIMENT_MODULE ?? false), enableCameraModule: toBool(import.meta.env.VITE_ENABLE_CAMERA_MODULE ?? false), + enableVisionSystemModule: toBool(import.meta.env.VITE_ENABLE_VISION_SYSTEM_MODULE ?? false), } export const isFeatureEnabled = (flag: keyof FeatureFlags): boolean => featureFlags[flag] diff --git a/management-dashboard-web-app/vite.config.ts b/management-dashboard-web-app/vite.config.ts index edbd905..f72eee6 100755 --- a/management-dashboard-web-app/vite.config.ts +++ b/management-dashboard-web-app/vite.config.ts @@ -19,7 +19,8 @@ export default defineConfig({ }, remotes: { // Allow overriding by env; default to localhost for dev - videoRemote: process.env.VITE_VIDEO_REMOTE_URL || 'http://localhost:3001/assets/remoteEntry.js' + videoRemote: process.env.VITE_VIDEO_REMOTE_URL || 'http://localhost:3001/assets/remoteEntry.js', + visionSystemRemote: process.env.VITE_VISION_SYSTEM_REMOTE_URL || 'http://localhost:3002/assets/remoteEntry.js' }, shared: { react: { singleton: true, eager: true }, diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..c94f5c7 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,38 @@ +# Scripts Directory + +This directory contains utility scripts, test files, and diagnostic tools for the USDA Vision system. + +## Shell Scripts (.sh) + +- **`check_rtsp_status.sh`** - Quick RTSP streaming status check + - Starts RTSP stream for camera1 + - Checks MediaMTX for stream availability + - Displays access URLs (WebRTC, RTSP) + +- **`diagnose_rtsp.sh`** - Comprehensive RTSP diagnostic tool + - Performs full RTSP streaming health check + - Tests API health, camera status, FFmpeg process + - Verifies MediaMTX stream availability + - Provides detailed diagnostic output + +## Test Files + +- **`api-tests.http`** - REST Client test file for API endpoints + - Camera Management API tests + - Media API tests + - MediaMTX endpoint tests + - Can be used with REST Client extensions in VS Code/Cursor + +- **`test_rtsp.py`** - Python script for testing RTSP streaming functionality + +## Usage + +All scripts assume you're running from the project root directory and that Docker Compose services are running. + +### Example: +```bash +# From project root +./scripts/check_rtsp_status.sh +./scripts/diagnose_rtsp.sh +``` + diff --git a/api-tests.http b/scripts/api-tests.http similarity index 100% rename from api-tests.http rename to scripts/api-tests.http diff --git a/check_rtsp_status.sh b/scripts/check_rtsp_status.sh similarity index 100% rename from check_rtsp_status.sh rename to scripts/check_rtsp_status.sh diff --git a/diagnose_rtsp.sh b/scripts/diagnose_rtsp.sh similarity index 100% rename from diagnose_rtsp.sh rename to scripts/diagnose_rtsp.sh diff --git a/scripts/docker-compose-reset.sh b/scripts/docker-compose-reset.sh new file mode 100644 index 0000000..e892b07 --- /dev/null +++ b/scripts/docker-compose-reset.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Docker Compose Reset Script +# This script performs a complete reset of the Docker Compose environment: +# - Stops and removes containers, networks, and volumes +# - Prunes unused Docker resources (containers, images, networks, volumes) +# - Rebuilds and starts all services in detached mode + +set -e # Exit on error + +echo "=== Docker Compose Reset ===" +echo "" + +# Get the project root directory (parent of scripts directory) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Change to project root directory +cd "$PROJECT_ROOT" + +echo "Working directory: $PROJECT_ROOT" +echo "" + +echo "1. Stopping and removing containers, networks, and volumes..." +docker compose down -v +echo "" + +echo "2. Pruning unused Docker resources..." +echo " - Pruning unused containers..." +docker container prune -f + +echo " - Pruning unused images..." +docker image prune -af + +echo " - Pruning unused networks..." +docker network prune -f + +echo " - Pruning unused volumes..." +docker volume prune -f +echo "" + +echo "3. Rebuilding and starting all services in detached mode..." +docker compose up --build -d +echo "" + +echo "4. Checking service status..." +docker compose ps +echo "" + +echo "=== Docker Compose Reset Complete ===" +echo "" +echo "All services have been reset and are running in detached mode." +echo "Use 'docker compose logs -f' to view logs or 'docker compose ps' to check status." + diff --git a/test_rtsp.py b/scripts/test_rtsp.py similarity index 100% rename from test_rtsp.py rename to scripts/test_rtsp.py diff --git a/vision-system-remote/index.html b/vision-system-remote/index.html new file mode 100644 index 0000000..f1f9db2 --- /dev/null +++ b/vision-system-remote/index.html @@ -0,0 +1,13 @@ + + + + + + Vision System Remote + + +
+ + + + diff --git a/vision-system-remote/package-lock.json b/vision-system-remote/package-lock.json new file mode 100644 index 0000000..0202df0 --- /dev/null +++ b/vision-system-remote/package-lock.json @@ -0,0 +1,3994 @@ +{ + "name": "vision-system-remote", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vision-system-remote", + "version": "0.0.1", + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@originjs/vite-plugin-federation": "^1.3.3", + "@tailwindcss/vite": "^4.1.11", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^4.6.0", + "http-server": "^14.1.1", + "serve": "^14.2.3", + "tailwindcss": "^4.1.11", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@originjs/vite-plugin-federation": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@originjs/vite-plugin-federation/-/vite-plugin-federation-1.4.1.tgz", + "integrity": "sha512-Uo08jW5pj1t58OUKuZNkmzcfTN2pqeVuAWCCiKf/75/oll4Efq4cHOqSE1FXMlvwZNGDziNdDyBbQ5IANem3CQ==", + "dev": true, + "license": "MulanPSL-2.0", + "dependencies": { + "estree-walker": "^3.0.2", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0", + "pnpm": ">=7.0.1" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz", + "integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.19", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.16" + } + }, + "node_modules/@tailwindcss/node/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz", + "integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-x64": "4.1.16", + "@tailwindcss/oxide-freebsd-x64": "4.1.16", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-x64-musl": "4.1.16", + "@tailwindcss/oxide-wasm32-wasi": "4.1.16", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz", + "integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz", + "integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz", + "integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz", + "integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz", + "integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz", + "integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz", + "integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz", + "integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz", + "integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz", + "integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz", + "integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz", + "integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.16.tgz", + "integrity": "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.16", + "@tailwindcss/oxide": "4.1.16", + "tailwindcss": "4.1.16" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", + "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", + "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001753", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", + "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.244", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", + "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serve": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz", + "integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.8.1", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.6", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", + "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/vision-system-remote/package.json b/vision-system-remote/package.json new file mode 100644 index 0000000..b6f1470 --- /dev/null +++ b/vision-system-remote/package.json @@ -0,0 +1,31 @@ +{ + "name": "vision-system-remote", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "build:watch": "vite build --watch", + "serve:dist": "serve -s dist -l 3002", + "preview": "vite preview --port 3002", + "dev:watch": "npm run build && (npm run build:watch &) && sleep 1 && npx http-server dist -p 3002 --cors -c-1" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@originjs/vite-plugin-federation": "^1.3.3", + "@tailwindcss/vite": "^4.1.11", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^4.6.0", + "http-server": "^14.1.1", + "serve": "^14.2.3", + "tailwindcss": "^4.1.11", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } +} + diff --git a/vision-system-remote/src/App.tsx b/vision-system-remote/src/App.tsx new file mode 100644 index 0000000..01077fb --- /dev/null +++ b/vision-system-remote/src/App.tsx @@ -0,0 +1,466 @@ +import React, { useState, useEffect, useCallback } from 'react' +import { useWebSocket } from './hooks/useWebSocket' +import { visionApi, type SystemStatus, type CameraStatus, type RecordingInfo } from './services/api' +import { SystemHealthWidget } from './widgets/SystemHealthWidget' +import { MqttStatusWidget } from './widgets/MqttStatusWidget' +import { RecordingsCountWidget } from './widgets/RecordingsCountWidget' +import { CameraCountWidget } from './widgets/CameraCountWidget' +import { CameraCard } from './components/CameraCard' +import { CameraPreviewModal } from './components/CameraPreviewModal' +import { CameraConfigModal } from './components/CameraConfigModal' + +// Get WebSocket URL from environment or construct it +const getWebSocketUrl = () => { + const apiUrl = import.meta.env.VITE_VISION_API_URL || '/api' + + // If it's a relative path, use relative WebSocket URL + if (apiUrl.startsWith('/')) { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + return `${protocol}//${window.location.host}${apiUrl.replace(/\/api$/, '')}/ws` + } + + // Convert http(s):// to ws(s):// + const wsUrl = apiUrl.replace(/^http/, 'ws') + return `${wsUrl.replace(/\/api$/, '')}/ws` +} + +export default function App() { + const [systemStatus, setSystemStatus] = useState(null) + const [cameras, setCameras] = useState>({}) + const [recordings, setRecordings] = useState>({}) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [lastUpdate, setLastUpdate] = useState(null) + const [notification, setNotification] = useState<{ type: 'success' | 'error', message: string } | null>(null) + + // Modal states + const [previewModalOpen, setPreviewModalOpen] = useState(false) + const [previewCamera, setPreviewCamera] = useState(null) + const [configModalOpen, setConfigModalOpen] = useState(false) + const [selectedCamera, setSelectedCamera] = useState(null) + + // WebSocket connection + const { isConnected, subscribe } = useWebSocket(getWebSocketUrl()) + + // Fetch initial data + const fetchInitialData = useCallback(async () => { + try { + setError(null) + const [status, camerasData, recordingsData] = await Promise.all([ + visionApi.getSystemStatus(), + visionApi.getCameras(), + visionApi.getRecordings(), + ]) + + setSystemStatus(status) + setCameras(camerasData) + setRecordings(recordingsData) + setLastUpdate(new Date()) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch data') + console.error('Failed to fetch initial data:', err) + } finally { + setLoading(false) + } + }, []) + + // Set up WebSocket subscriptions for real-time updates + useEffect(() => { + const unsubscribeFunctions: Array<() => void> = [] + + // Subscribe to camera status changes + unsubscribeFunctions.push( + subscribe('camera_status_changed', (event) => { + const { camera_name, status, is_recording } = event.data + setCameras((prev) => ({ + ...prev, + [camera_name]: { + ...prev[camera_name], + status, + is_recording, + last_checked: new Date().toISOString(), + }, + })) + setLastUpdate(new Date()) + }) + ) + + // Subscribe to recording started events + unsubscribeFunctions.push( + subscribe('recording_started', (event) => { + const { camera_name } = event.data + setCameras((prev) => ({ + ...prev, + [camera_name]: { + ...prev[camera_name], + is_recording: true, + }, + })) + + // Refresh recordings to get accurate count + visionApi.getRecordings().then(setRecordings).catch(console.error) + + // Refresh system status to update counts + visionApi.getSystemStatus().then(setSystemStatus).catch(console.error) + + setLastUpdate(new Date()) + }) + ) + + // Subscribe to recording stopped events + unsubscribeFunctions.push( + subscribe('recording_stopped', (event) => { + const { camera_name } = event.data + setCameras((prev) => ({ + ...prev, + [camera_name]: { + ...prev[camera_name], + is_recording: false, + }, + })) + + // Refresh recordings and system status + Promise.all([ + visionApi.getRecordings(), + visionApi.getSystemStatus(), + ]).then(([recordingsData, statusData]) => { + setRecordings(recordingsData) + setSystemStatus(statusData) + }).catch(console.error) + + setLastUpdate(new Date()) + }) + ) + + // Subscribe to system status changes + unsubscribeFunctions.push( + subscribe('system_status_changed', () => { + visionApi.getSystemStatus().then(setSystemStatus).catch(console.error) + setLastUpdate(new Date()) + }) + ) + + // Subscribe to MQTT status changes + unsubscribeFunctions.push( + subscribe('mqtt_status_changed', () => { + visionApi.getSystemStatus().then(setSystemStatus).catch(console.error) + setLastUpdate(new Date()) + }) + ) + + return () => { + unsubscribeFunctions.forEach((unsub) => unsub()) + } + }, [subscribe]) + + // Fetch initial data on mount + useEffect(() => { + fetchInitialData() + }, [fetchInitialData]) + + // Camera action handlers + const handleStartRecording = useCallback(async (cameraName: string) => { + try { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-') + const filename = `manual_${cameraName}_${timestamp}.mp4` + const result = await visionApi.startRecording(cameraName, filename) + + if (result.success) { + setNotification({ type: 'success', message: `Recording started: ${result.filename}` }) + + // Immediately update state optimistically (UI updates instantly) + setCameras((prev) => ({ + ...prev, + [cameraName]: { + ...prev[cameraName], + is_recording: true, + current_recording_file: result.filename, + }, + })) + + // Refresh camera status from API as backup (in case WebSocket is delayed) + setTimeout(() => { + visionApi.getCameras().then(setCameras).catch(console.error) + }, 500) + } else { + setNotification({ type: 'error', message: `Failed: ${result.message}` }) + } + } catch (err) { + setNotification({ type: 'error', message: err instanceof Error ? err.message : 'Unknown error' }) + } + }, []) + + const handleStopRecording = useCallback(async (cameraName: string) => { + try { + const result = await visionApi.stopRecording(cameraName) + if (result.success) { + setNotification({ type: 'success', message: 'Recording stopped' }) + + // Immediately update state optimistically (UI updates instantly) + setCameras((prev) => ({ + ...prev, + [cameraName]: { + ...prev[cameraName], + is_recording: false, + current_recording_file: null, + }, + })) + + // Refresh camera status from API as backup (in case WebSocket is delayed) + setTimeout(() => { + visionApi.getCameras().then(setCameras).catch(console.error) + }, 500) + } else { + setNotification({ type: 'error', message: `Failed: ${result.message}` }) + } + } catch (err) { + setNotification({ type: 'error', message: err instanceof Error ? err.message : 'Unknown error' }) + } + }, []) + + const handlePreviewModal = useCallback((cameraName: string) => { + setPreviewCamera(cameraName) + setPreviewModalOpen(true) + }, []) + + const handlePreviewNewWindow = useCallback((cameraName: string) => { + // Open camera stream in new window/tab + const streamUrl = visionApi.getStreamUrl(cameraName) + window.open(streamUrl, '_blank') + }, []) + + const handleConfigure = useCallback((cameraName: string) => { + setSelectedCamera(cameraName) + setConfigModalOpen(true) + }, []) + + const handleRestart = useCallback(async (cameraName: string) => { + try { + setNotification({ type: 'success', message: `Restarting camera ${cameraName}...` }) + const result = await visionApi.reinitializeCamera(cameraName) + + if (result.success) { + setNotification({ type: 'success', message: `Camera ${cameraName} restarted successfully` }) + // Refresh camera status + setTimeout(() => { + visionApi.getCameras().then(setCameras).catch(console.error) + visionApi.getSystemStatus().then(setSystemStatus).catch(console.error) + }, 2000) // Wait 2 seconds for camera to reinitialize + } else { + setNotification({ type: 'error', message: `Failed: ${result.message}` }) + } + } catch (err) { + setNotification({ type: 'error', message: err instanceof Error ? err.message : 'Unknown error' }) + } + }, []) + + const handleStopStreaming = useCallback(async (cameraName: string) => { + try { + const result = await visionApi.stopStream(cameraName) + if (result.success) { + setNotification({ type: 'success', message: 'Streaming stopped' }) + // Refresh camera status + visionApi.getCameras().then(setCameras).catch(console.error) + } else { + setNotification({ type: 'error', message: `Failed: ${result.message}` }) + } + } catch (err) { + setNotification({ type: 'error', message: err instanceof Error ? err.message : 'Unknown error' }) + } + }, []) + + // Auto-hide notifications + useEffect(() => { + if (notification) { + const timer = setTimeout(() => setNotification(null), 5000) + return () => clearTimeout(timer) + } + }, [notification]) + + if (loading) { + return ( +
+
+
+
+

Loading vision system...

+
+
+
+ ) + } + + if (error) { + return ( +
+
+
+
+ + + +
+
+

Error loading vision system

+
+

{error}

+
+
+ +
+
+
+
+
+ ) + } + + const cameraCount = Object.keys(cameras).length + const machineCount = systemStatus ? Object.keys(systemStatus.machines).length : 0 + const activeRecordings = systemStatus?.active_recordings ?? 0 + // Fix: Use recordings object length instead of total_recordings (which may be incorrect) + const totalRecordings = Object.keys(recordings).length + + return ( +
+ {/* Header */} +
+
+

Vision System

+

Monitor cameras, machines, and recording status

+ {lastUpdate && ( +

+ Last updated: {lastUpdate.toLocaleTimeString()} + {isConnected ? ( + + + Live Updates + + ) : ( + + Polling Mode + + )} +

+ )} +
+
+ + {/* Status Widgets */} +
+ + + + +
+ + {/* Cameras Grid */} +
+
+

Cameras

+

+ Current status of all cameras in the system +

+
+
+
+ {Object.entries(cameras).map(([cameraName, camera]) => ( + + ))} +
+
+
+ + {/* Notification */} + {notification && ( +
+
+
+ {notification.type === 'success' ? ( + + + + ) : ( + + + + )} +
+
+

{notification.message}

+
+
+ +
+
+
+ )} + + {/* Camera Preview Modal */} + {previewCamera && ( + { + setPreviewModalOpen(false) + setPreviewCamera(null) + }} + /> + )} + + {/* Camera Configuration Modal */} + {selectedCamera && ( + { + setConfigModalOpen(false) + setSelectedCamera(null) + }} + onSuccess={(message) => { + setNotification({ type: 'success', message }) + // Refresh camera status + visionApi.getCameras().then(setCameras).catch(console.error) + }} + onError={(error) => { + setNotification({ type: 'error', message: error }) + }} + /> + )} +
+ ) +} + diff --git a/vision-system-remote/src/components/CameraCard.tsx b/vision-system-remote/src/components/CameraCard.tsx new file mode 100644 index 0000000..7adf922 --- /dev/null +++ b/vision-system-remote/src/components/CameraCard.tsx @@ -0,0 +1,245 @@ +import React from 'react' +import type { CameraStatus } from '../services/api' + +interface CameraCardProps { + cameraName: string + camera: CameraStatus + onStartRecording: (cameraName: string) => void + onStopRecording: (cameraName: string) => void + onPreviewModal: (cameraName: string) => void + onPreviewNewWindow: (cameraName: string) => void + onStopStreaming: (cameraName: string) => void + onConfigure: (cameraName: string) => void + onRestart: (cameraName: string) => void +} + +export const CameraCard: React.FC = ({ + cameraName, + camera, + onStartRecording, + onStopRecording, + onPreviewModal, + onPreviewNewWindow, + onStopStreaming, + onConfigure, + onRestart, +}) => { + const friendlyName = camera.device_info?.friendly_name || cameraName + const isConnected = camera.status === 'available' || camera.status === 'connected' || camera.status === 'streaming' + const hasError = camera.status === 'error' + const isStreaming = camera.status === 'streaming' + const isRecording = camera.is_recording + const needsRestart = hasError || camera.status === 'crashed' || camera.status === 'failed' + + const getStatusColor = () => { + if (isRecording) return 'bg-red-500' + if (isStreaming) return 'bg-blue-500' + if (isConnected) return 'bg-green-500' + if (hasError) return 'bg-yellow-500' + return 'bg-gray-400' + } + + const getStatusText = () => { + if (isRecording) return 'Recording' + if (isStreaming) return 'Streaming' + if (isConnected) return 'Connected' + if (hasError) return 'Error' + return 'Offline' + } + + return ( +
+ {/* Header with Status Indicator */} +
+
+
+
+
+

{friendlyName}

+ {friendlyName !== cameraName && ( +

{cameraName}

+ )} +
+
+ + {getStatusText()} + +
+
+ + {/* Camera Details */} +
+ {/* Status Info */} +
+ {camera.device_info?.serial_number && ( +
+ Serial: + + {camera.device_info.serial_number} + +
+ )} + {camera.frame_rate && ( +
+ FPS: + {camera.frame_rate.toFixed(1)} +
+ )} +
+ + {/* Recording Indicator */} + {isRecording && ( +
+
+ Recording Active + {camera.current_recording_file && ( + + {camera.current_recording_file.split('/').pop()} + + )} +
+ )} + + {/* Error Display */} + {camera.last_error && ( +
+

+ Error: {camera.last_error} +

+
+ )} + + {/* Action Buttons */} +
+ {/* Recording Controls */} +
+ {!isRecording ? ( + + ) : ( + + )} +
+ + {/* Preview and Stream Controls */} +
+ + + + + {isStreaming && ( + + )} +
+ + {/* Error Recovery - Restart Button */} + {needsRestart && ( +
+ +
+ )} + + {/* Configuration Button */} +
+ +
+
+
+
+ ) +} + diff --git a/vision-system-remote/src/components/CameraConfigModal.tsx b/vision-system-remote/src/components/CameraConfigModal.tsx new file mode 100755 index 0000000..4cf7051 --- /dev/null +++ b/vision-system-remote/src/components/CameraConfigModal.tsx @@ -0,0 +1,768 @@ +import React, { useState, useEffect } from 'react' +import { visionApi, type CameraConfig, type CameraConfigUpdate } from '../services/api' + + +interface CameraConfigModalProps { + cameraName: string + isOpen: boolean + onClose: () => void + onSuccess?: (message: string) => void + onError?: (error: string) => void +} + +export const CameraConfigModal: React.FC = ({ cameraName, isOpen, onClose, onSuccess, onError }) => { + const [config, setConfig] = useState(null) + const [loading, setLoading] = useState(false) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + const [hasChanges, setHasChanges] = useState(false) + const [originalConfig, setOriginalConfig] = useState(null) + + useEffect(() => { + if (isOpen && cameraName) { + loadConfig() + } + }, [isOpen, cameraName]) + + const loadConfig = async () => { + try { + setLoading(true) + setError(null) + const configData = await visionApi.getCameraConfig(cameraName) + + // Map API field names to UI expected field names + const configWithDefaults = { + ...configData, + // Map auto_start_recording_enabled from API to auto_record_on_machine_start for UI + auto_record_on_machine_start: configData.auto_start_recording_enabled ?? false, + } + + setConfig(configWithDefaults as CameraConfig) + setOriginalConfig(configWithDefaults as CameraConfig) + setHasChanges(false) + } catch (err) { + let errorMessage = 'Failed to load camera configuration' + + if (err instanceof Error) { + errorMessage = err.message + + // Handle specific API validation errors for missing video format fields + if (err.message.includes('video_format') || err.message.includes('video_codec') || err.message.includes('video_quality')) { + errorMessage = 'Camera configuration is missing video format settings. This may indicate the backend needs to be updated to support MP4 format. Using default values.' + + // Create a default configuration for display + const defaultConfig = { + name: cameraName, + machine_topic: '', + storage_path: '', + enabled: true, + auto_record_on_machine_start: false, + auto_start_recording_enabled: false, + auto_recording_max_retries: 3, + auto_recording_retry_delay_seconds: 2, + exposure_ms: 1.0, + gain: 3.5, + target_fps: 0, + video_format: 'mp4', + video_codec: 'mp4v', + video_quality: 95, + sharpness: 120, + contrast: 110, + saturation: 100, + gamma: 100, + noise_filter_enabled: true, + denoise_3d_enabled: false, + auto_white_balance: true, + color_temperature_preset: 0, + anti_flicker_enabled: true, + light_frequency: 1, + bit_depth: 8, + hdr_enabled: false, + hdr_gain_mode: 0, + } + + setConfig(defaultConfig) + setOriginalConfig(defaultConfig) + setHasChanges(false) + setError(errorMessage) + return + } + } + + setError(errorMessage) + onError?.(errorMessage) + } finally { + setLoading(false) + } + } + + const updateSetting = (key: keyof CameraConfigUpdate, value: number | boolean | string) => { + if (!config) return + + const newConfig = { ...config, [key]: value } + setConfig(newConfig) + + // Check if there are changes from original + const hasChanges = originalConfig && Object.keys(newConfig).some(k => { + const configKey = k as keyof CameraConfig + return newConfig[configKey] !== originalConfig[configKey] + }) + setHasChanges(!!hasChanges) + + // Video format settings are read-only, no validation needed + } + + const saveConfig = async () => { + if (!config || !originalConfig) return + + try { + setSaving(true) + setError(null) + + // Build update object with only changed values + const updates: CameraConfigUpdate = {} + const configKeys: (keyof CameraConfigUpdate)[] = [ + 'exposure_ms', 'gain', 'target_fps', 'sharpness', 'contrast', 'saturation', + 'gamma', 'noise_filter_enabled', 'denoise_3d_enabled', 'auto_white_balance', + 'color_temperature_preset', 'anti_flicker_enabled', 'light_frequency', + 'hdr_enabled', 'hdr_gain_mode', 'auto_record_on_machine_start', + 'auto_start_recording_enabled', 'auto_recording_max_retries', 'auto_recording_retry_delay_seconds' + ] + + configKeys.forEach(key => { + if (config[key] !== originalConfig[key]) { + // Map auto_record_on_machine_start back to auto_start_recording_enabled for API + if (key === 'auto_record_on_machine_start') { + updates.auto_start_recording_enabled = config[key] as boolean + } else { + updates[key] = config[key] as any + } + } + }) + + // Remove auto_record_on_machine_start if it was added, as it's not an API field + if ('auto_record_on_machine_start' in updates) { + delete updates.auto_record_on_machine_start + } + + if (Object.keys(updates).length === 0) { + onSuccess?.('No changes to save') + return + } + + const result = await visionApi.updateCameraConfig(cameraName, updates) + + if (result.success) { + setOriginalConfig(config) + setHasChanges(false) + onSuccess?.(`Configuration updated: ${result.updated_settings.join(', ')}`) + } else { + throw new Error(result.message) + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to save configuration' + setError(errorMessage) + onError?.(errorMessage) + } finally { + setSaving(false) + } + } + + + + const resetChanges = () => { + if (originalConfig) { + setConfig(originalConfig) + setHasChanges(false) + } + } + + if (!isOpen) return null + + return ( +
+
+
e.stopPropagation()}> + {/* Close Button */} + + + {/* Header */} +
+
+

+ Camera Configuration - {cameraName} +

+
+
+ + {/* Content */} +
+ {loading && ( +
+
+ Loading configuration... +
+ )} + + {error && ( +
+
+
+ + + +
+
+

Configuration Error

+

{error}

+ {error.includes('video_format') && ( +

+ Note: The video format settings are displayed with default values. + You can still modify and save the configuration, but the backend may need to be updated + to fully support MP4 format settings. +

+ )} +
+
+
+ )} + + {config && !loading && ( +
+ {/* System Information (Read-Only) */} +
+

System Information

+
+
+
+ +
{config.name}
+
+
+ +
{config.machine_topic}
+
+
+ +
{config.storage_path}
+
+
+ +
+ + {config.enabled ? 'Enabled' : 'Disabled'} + +
+
+
+
+
+ + {/* Auto-Recording Settings (Read-Only) */} +
+

Auto-Recording Settings

+
+
+
+ +
+ + {config.auto_start_recording_enabled ? 'Enabled' : 'Disabled'} + +
+
+
+ +
{config.auto_recording_max_retries}
+
+
+ +
{config.auto_recording_retry_delay_seconds}s
+
+
+

Auto-recording settings are configured in the system configuration file

+
+
+ + {/* Basic Settings */} +
+

Basic Settings

+
+
+ + updateSetting('exposure_ms', parseFloat(e.target.value))} + className="w-full" + /> +
+ 0.1ms + 10ms +
+
+ +
+ + updateSetting('gain', parseFloat(e.target.value))} + className="w-full" + /> +
+ 0 + 10 +
+
+ +
+ + updateSetting('target_fps', parseInt(e.target.value))} + className="w-full" + /> +
+ 0 (Max) + 30 +
+
+
+
+ + {/* Image Quality Settings */} +
+

Image Quality

+
+
+ + updateSetting('sharpness', parseInt(e.target.value))} + className="w-full" + /> +
+ 0 + 200 +
+
+ +
+ + updateSetting('contrast', parseInt(e.target.value))} + className="w-full" + /> +
+ 0 + 200 +
+
+ +
+ + updateSetting('saturation', parseInt(e.target.value))} + className="w-full" + /> +
+ 0 + 200 +
+
+ +
+ + updateSetting('gamma', parseInt(e.target.value))} + className="w-full" + /> +
+ 0 + 300 +
+
+
+
+ + {/* Color Settings */} +
+

Color Settings

+
+
+ +
+ +
+ + updateSetting('color_temperature_preset', parseInt(e.target.value))} + className="w-full" + /> +
+ 0 (Auto) + 10 +
+
+
+
+ + {/* White Balance RGB Gains */} +
+

White Balance RGB Gains

+
+
+ + updateSetting('wb_red_gain', parseFloat(e.target.value))} + className="w-full" + /> +
+ 0.00 + 3.99 +
+
+ +
+ + updateSetting('wb_green_gain', parseFloat(e.target.value))} + className="w-full" + /> +
+ 0.00 + 3.99 +
+
+ +
+ + updateSetting('wb_blue_gain', parseFloat(e.target.value))} + className="w-full" + /> +
+ 0.00 + 3.99 +
+
+
+

Manual white balance gains (only effective when Auto White Balance is disabled)

+
+ + {/* Advanced Settings */} +
+

Advanced Settings

+
+
+ +
+ +
+ + +
+ +
+ +

Requires restart to apply

+
+ +
+ +

Requires restart to apply

+
+
+
+ + {/* HDR Settings */} +
+

HDR Settings

+
+
+ +
+ +
+ + updateSetting('hdr_gain_mode', parseInt(e.target.value))} + className="w-full" + disabled={!config.hdr_enabled} + /> +
+ 0 + 3 +
+
+
+
+ + {/* Video Recording Settings (Read-Only) */} +
+

Video Recording Settings

+
+
+
+ +
+ {config.video_format?.toUpperCase() || 'MP4'} +
+

Current recording format

+
+ +
+ +
+ {config.video_codec?.toUpperCase() || 'MP4V'} +
+

Compression codec

+
+ +
+ +
+ {config.video_quality || 95}% +
+

Recording quality

+
+
+ +
+
+
+ + + +
+
+

Video Format Information

+
+

Video recording settings are configured in the system configuration file and require a service restart to modify.

+

Current benefits: MP4 format provides ~40% smaller file sizes and better web compatibility than AVI.

+
+
+
+
+
+
+ + + + {/* Information */} +
+
+
+ + + +
+
+

Configuration Notes

+
+
    +
  • Real-time settings: Exposure, gain, image quality, white balance - apply immediately
  • +
  • System settings: Video format, noise reduction, auto-recording - configured in system files
  • +
  • Performance: HDR mode may impact frame rate when enabled
  • +
  • White balance: RGB gains only effective when auto white balance is disabled
  • +
+
+
+
+
+
+ )} +
+ + {/* Footer */} + {config && !loading && ( +
+
+
+ {hasChanges && ( + + You have unsaved changes + + )} +
+
+ {hasChanges && ( + + )} + + +
+
+
+ )} +
+
+ ) +} diff --git a/vision-system-remote/src/components/CameraPreviewModal.tsx b/vision-system-remote/src/components/CameraPreviewModal.tsx new file mode 100644 index 0000000..f655606 --- /dev/null +++ b/vision-system-remote/src/components/CameraPreviewModal.tsx @@ -0,0 +1,180 @@ +import React, { useState, useEffect, useRef } from 'react' +import { visionApi } from '../services/api' + +interface CameraPreviewModalProps { + cameraName: string + isOpen: boolean + onClose: () => void + onError?: (error: string) => void +} + +export const CameraPreviewModal: React.FC = ({ + cameraName, + isOpen, + onClose, + onError, +}) => { + const [loading, setLoading] = useState(false) + const [streaming, setStreaming] = useState(false) + const [error, setError] = useState(null) + const imgRef = useRef(null) + const streamUrlRef = useRef(null) + + useEffect(() => { + if (isOpen && cameraName) { + startStreaming() + } + return () => { + if (streaming) { + stopStreaming() + } + } + }, [isOpen, cameraName]) + + const startStreaming = async () => { + try { + setLoading(true) + setError(null) + + const result = await visionApi.startStream(cameraName) + + if (result.success) { + setStreaming(true) + const streamUrl = visionApi.getStreamUrl(cameraName) + streamUrlRef.current = streamUrl + + if (imgRef.current) { + imgRef.current.src = `${streamUrl}?t=${Date.now()}` + } + } else { + throw new Error(result.message) + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to start stream' + setError(errorMessage) + onError?.(errorMessage) + } finally { + setLoading(false) + } + } + + const stopStreaming = async () => { + try { + if (streaming) { + await visionApi.stopStream(cameraName) + setStreaming(false) + streamUrlRef.current = null + + if (imgRef.current) { + imgRef.current.src = '' + } + } + } catch (err) { + console.error('Error stopping stream:', err) + } + } + + const handleClose = () => { + stopStreaming() + onClose() + } + + if (!isOpen) return null + + return ( +
+
+
e.stopPropagation()}> + {/* Close Button */} + + +
+ {/* Header */} +
+

+ Camera Preview: {cameraName} +

+
+ + {/* Content */} +
+ {loading && ( +
+
+
+

Starting camera stream...

+
+
+ )} + + {error && ( +
+
+
+ + + +
+
+

Stream Error

+
+

{error}

+
+
+ +
+
+
+
+ )} + + {streaming && !loading && !error && ( +
+ {`Live setError('Failed to load camera stream')} + /> +
+ )} +
+ + {/* Footer */} +
+
+ {streaming && ( +
+
+ Live Stream Active +
+ )} +
+ +
+
+
+
+ ) +} + diff --git a/vision-system-remote/src/hooks/useWebSocket.ts b/vision-system-remote/src/hooks/useWebSocket.ts new file mode 100644 index 0000000..62fc228 --- /dev/null +++ b/vision-system-remote/src/hooks/useWebSocket.ts @@ -0,0 +1,124 @@ +import { useEffect, useRef, useState, useCallback } from 'react' + +// WebSocket message types from the API +export interface WebSocketEvent { + type: 'event' + event_type: string + source: string + data: any + timestamp: string +} + +type EventHandler = (event: WebSocketEvent) => void + +export function useWebSocket(url: string, options?: { enabled?: boolean }) { + const [isConnected, setIsConnected] = useState(false) + const [reconnectAttempts, setReconnectAttempts] = useState(0) + const wsRef = useRef(null) + const reconnectTimeoutRef = useRef(null) + const handlersRef = useRef>>(new Map()) + const enabled = options?.enabled !== false + + const connect = useCallback(() => { + if (!enabled || wsRef.current?.readyState === WebSocket.OPEN) { + return + } + + try { + const ws = new WebSocket(url) + wsRef.current = ws + + ws.onopen = () => { + setIsConnected(true) + setReconnectAttempts(0) + } + + ws.onmessage = (event) => { + try { + const message: WebSocketEvent = JSON.parse(event.data) + + // Call all handlers for this event type + const handlers = handlersRef.current.get(message.event_type) + if (handlers) { + handlers.forEach(handler => handler(message)) + } + + // Also call handlers for 'all' type + const allHandlers = handlersRef.current.get('all') + if (allHandlers) { + allHandlers.forEach(handler => handler(message)) + } + } catch (err) { + console.error('Failed to parse WebSocket message:', err) + } + } + + ws.onerror = (error) => { + console.error('WebSocket error:', error) + } + + ws.onclose = () => { + setIsConnected(false) + + // Reconnect with exponential backoff (max 10 attempts) + if (enabled && reconnectAttempts < 10) { + const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000) + reconnectTimeoutRef.current = setTimeout(() => { + setReconnectAttempts(prev => prev + 1) + connect() + }, delay) + } + } + } catch (err) { + console.error('Failed to create WebSocket connection:', err) + setIsConnected(false) + } + }, [url, enabled, reconnectAttempts]) + + useEffect(() => { + if (enabled) { + connect() + } + + return () => { + if (reconnectTimeoutRef.current) { + clearTimeout(reconnectTimeoutRef.current) + } + if (wsRef.current) { + wsRef.current.close() + wsRef.current = null + } + setIsConnected(false) + } + }, [connect, enabled]) + + const subscribe = useCallback((eventType: string | 'all', handler: EventHandler) => { + if (!handlersRef.current.has(eventType)) { + handlersRef.current.set(eventType, new Set()) + } + handlersRef.current.get(eventType)!.add(handler) + + return () => { + const handlers = handlersRef.current.get(eventType) + if (handlers) { + handlers.delete(handler) + } + } + }, []) + + const send = useCallback((message: any) => { + if (wsRef.current?.readyState === WebSocket.OPEN) { + wsRef.current.send(JSON.stringify(message)) + } else { + console.warn('WebSocket is not connected') + } + }, []) + + return { + isConnected, + subscribe, + send, + reconnect: connect, + } +} + diff --git a/vision-system-remote/src/index.css b/vision-system-remote/src/index.css new file mode 100644 index 0000000..f8078f6 --- /dev/null +++ b/vision-system-remote/src/index.css @@ -0,0 +1,11 @@ +@import "tailwindcss"; + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + diff --git a/vision-system-remote/src/main.tsx b/vision-system-remote/src/main.tsx new file mode 100644 index 0000000..ccd3cda --- /dev/null +++ b/vision-system-remote/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +) + diff --git a/vision-system-remote/src/services/api.ts b/vision-system-remote/src/services/api.ts new file mode 100644 index 0000000..b0e5365 --- /dev/null +++ b/vision-system-remote/src/services/api.ts @@ -0,0 +1,320 @@ +// Vision System API Client for vision-system-remote +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore - Vite provides import.meta.env +const VISION_API_BASE_URL = (import.meta.env?.VITE_VISION_API_URL as string | undefined) || 'http://exp-dash:8000' + +// Types (simplified - we'll use the same types from the original) +export interface SystemStatus { + system_started: boolean + mqtt_connected: boolean + last_mqtt_message: string + machines: Record + cameras: Record + active_recordings: number + total_recordings: number + uptime_seconds: number +} + +export interface MachineStatus { + name: string + state: string + last_updated: string + last_message?: string + mqtt_topic?: string +} + +export interface CameraStatus { + name?: string + status: string + is_recording: boolean + last_checked: string + last_error?: string | null + device_info?: { + friendly_name?: string + serial_number?: string + port_type?: string + model?: string + firmware_version?: string + } + current_recording_file?: string | null + recording_start_time?: string | null + last_frame_time?: string + frame_rate?: number + auto_recording_enabled: boolean + auto_recording_active: boolean + auto_recording_failure_count: number + auto_recording_last_attempt?: string + auto_recording_last_error?: string +} + +export interface RecordingInfo { + camera_name: string + filename: string + start_time: string + state: string + end_time?: string + file_size_bytes?: number + frame_count?: number + duration_seconds?: number + error_message?: string | null +} + +export interface StorageStats { + base_path: string + total_files: number + total_size_bytes: number + cameras: Record + disk_usage: { + total: number + used: number + free: number + } +} + +export interface MqttStatus { + connected: boolean + broker_host: string + broker_port: number + subscribed_topics: string[] + last_message_time: string + message_count: number + error_count: number + uptime_seconds: number +} + +export interface StartRecordingResponse { + success: boolean + message: string + filename: string +} + +export interface StopRecordingResponse { + success: boolean + message: string +} + +export interface StreamStopResponse { + success: boolean + message: string +} + +export interface CameraConfig { + name: string + machine_topic: string + storage_path: string + enabled: boolean + auto_start_recording_enabled: boolean + auto_recording_max_retries: number + auto_recording_retry_delay_seconds: number + exposure_ms: number + gain: number + target_fps: number + video_format: string + video_codec: string + video_quality: number + sharpness: number + contrast: number + saturation: number + gamma: number + noise_filter_enabled: boolean + denoise_3d_enabled: boolean + auto_white_balance: boolean + color_temperature_preset: number + wb_red_gain: number + wb_green_gain: number + wb_blue_gain: number + anti_flicker_enabled: boolean + light_frequency: number + bit_depth: number + hdr_enabled: boolean + hdr_gain_mode: number +} + +export interface CameraConfigUpdate { + exposure_ms?: number + gain?: number + target_fps?: number + sharpness?: number + contrast?: number + saturation?: number + gamma?: number + noise_filter_enabled?: boolean + denoise_3d_enabled?: boolean + auto_white_balance?: boolean + color_temperature_preset?: number + wb_red_gain?: number + wb_green_gain?: number + wb_blue_gain?: number + anti_flicker_enabled?: boolean + light_frequency?: number + hdr_enabled?: boolean + hdr_gain_mode?: number + auto_start_recording_enabled?: boolean + auto_recording_max_retries?: number + auto_recording_retry_delay_seconds?: number +} + +export interface CameraConfigUpdateResponse { + success: boolean + message: string + updated_settings: string[] +} + +export interface CameraRecoveryResponse { + success: boolean + message: string + camera_name: string + operation: string +} + +export interface StreamStartResponse { + success: boolean + message: string +} + +class VisionApiClient { + private baseUrl: string + + constructor(baseUrl: string = VISION_API_BASE_URL) { + this.baseUrl = baseUrl + } + + private async request(endpoint: string, options: RequestInit = {}): Promise { + const url = `${this.baseUrl}${endpoint}` + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + ...options, + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`) + } + + return response.json() + } + + async getSystemStatus(): Promise { + return this.request('/system/status') + } + + async getCameras(): Promise> { + return this.request('/cameras') + } + + async getRecordings(): Promise> { + return this.request('/recordings') + } + + async getStorageStats(): Promise { + return this.request('/storage/stats') + } + + async getMqttStatus(): Promise { + return this.request('/mqtt/status') + } + + async startRecording(cameraName: string, filename?: string): Promise { + return this.request(`/cameras/${cameraName}/start-recording`, { + method: 'POST', + body: JSON.stringify({ filename }), + }) + } + + async stopRecording(cameraName: string): Promise { + return this.request(`/cameras/${cameraName}/stop-recording`, { + method: 'POST', + }) + } + + async stopStream(cameraName: string): Promise { + return this.request(`/cameras/${cameraName}/stop-stream`, { + method: 'POST', + }) + } + + getStreamUrl(cameraName: string): string { + return `${this.baseUrl}/cameras/${cameraName}/stream` + } + + async startStream(cameraName: string): Promise { + return this.request(`/cameras/${cameraName}/start-stream`, { + method: 'POST', + }) + } + + async getCameraConfig(cameraName: string): Promise { + return this.request(`/cameras/${cameraName}/config`) + } + + async updateCameraConfig(cameraName: string, config: CameraConfigUpdate): Promise { + return this.request(`/cameras/${cameraName}/config`, { + method: 'PUT', + body: JSON.stringify(config), + }) + } + + async applyCameraConfig(cameraName: string): Promise<{ success: boolean; message: string }> { + return this.request(`/cameras/${cameraName}/apply-config`, { + method: 'POST', + }) + } + + async reinitializeCamera(cameraName: string): Promise { + return this.request(`/cameras/${cameraName}/reinitialize`, { + method: 'POST', + }) + } + + async fullResetCamera(cameraName: string): Promise { + return this.request(`/cameras/${cameraName}/full-reset`, { + method: 'POST', + }) + } +} + +export const visionApi = new VisionApiClient() + +// Utility functions +export const formatBytes = (bytes: number): string => { + if (bytes === 0) return '0 Bytes' + const k = 1024 + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] +} + +export const formatDuration = (seconds: number): string => { + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = Math.floor(seconds % 60) + + if (hours > 0) { + return `${hours}h ${minutes}m ${secs}s` + } else if (minutes > 0) { + return `${minutes}m ${secs}s` + } else { + return `${secs}s` + } +} + +export const formatUptime = (seconds: number): string => { + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + + if (days > 0) { + return `${days}d ${hours}h ${minutes}m` + } else if (hours > 0) { + return `${hours}h ${minutes}m` + } else { + return `${minutes}m` + } +} + diff --git a/vision-system-remote/src/vite-env.d.ts b/vision-system-remote/src/vite-env.d.ts new file mode 100644 index 0000000..1163228 --- /dev/null +++ b/vision-system-remote/src/vite-env.d.ts @@ -0,0 +1,12 @@ +/// + +interface ImportMetaEnv { + readonly VITE_VISION_API_URL?: string + readonly VITE_WS_URL?: string + readonly VITE_MEDIA_API_URL?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + diff --git a/vision-system-remote/src/widgets/CameraCountWidget.tsx b/vision-system-remote/src/widgets/CameraCountWidget.tsx new file mode 100644 index 0000000..77d00bb --- /dev/null +++ b/vision-system-remote/src/widgets/CameraCountWidget.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +interface CameraCountWidgetProps { + cameraCount: number + machineCount: number +} + +export const CameraCountWidget: React.FC = ({ + cameraCount, + machineCount, +}) => { + return ( +
+
+
+
+
+ {cameraCount} Cameras +
+
+
+
+
Devices
+
{machineCount} Machines
+
+
+
+ ) +} + diff --git a/vision-system-remote/src/widgets/MqttStatusWidget.tsx b/vision-system-remote/src/widgets/MqttStatusWidget.tsx new file mode 100644 index 0000000..489ed4a --- /dev/null +++ b/vision-system-remote/src/widgets/MqttStatusWidget.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { StatusWidget } from './StatusWidget' +import type { SystemStatus } from '../services/api' + +interface MqttStatusWidgetProps { + systemStatus: SystemStatus | null +} + +export const MqttStatusWidget: React.FC = ({ systemStatus }) => { + const isConnected = systemStatus?.mqtt_connected ?? false + const lastMessage = systemStatus?.last_mqtt_message + + return ( + + } + /> + ) +} + diff --git a/vision-system-remote/src/widgets/RecordingsCountWidget.tsx b/vision-system-remote/src/widgets/RecordingsCountWidget.tsx new file mode 100644 index 0000000..d53a58a --- /dev/null +++ b/vision-system-remote/src/widgets/RecordingsCountWidget.tsx @@ -0,0 +1,27 @@ +import React from 'react' + +interface RecordingsCountWidgetProps { + active: number + total: number +} + +export const RecordingsCountWidget: React.FC = ({ active, total }) => { + return ( +
+
+
+
+
+ {active} Active +
+
+
+
+
Recordings
+
Total: {total}
+
+
+
+ ) +} + diff --git a/vision-system-remote/src/widgets/StatusWidget.tsx b/vision-system-remote/src/widgets/StatusWidget.tsx new file mode 100644 index 0000000..11ab377 --- /dev/null +++ b/vision-system-remote/src/widgets/StatusWidget.tsx @@ -0,0 +1,47 @@ +import React from 'react' + +interface StatusWidgetProps { + title: string + status: boolean + statusText?: string + subtitle?: string + icon?: React.ReactNode + className?: string +} + +export const StatusWidget: React.FC = ({ + title, + status, + statusText, + subtitle, + icon, + className = '', +}) => { + return ( +
+
+
+
+ {icon &&
{icon}
} +
+
+ {statusText || (status ? 'Online' : 'Offline')} +
+
+
+
+
+
{title}
+ {subtitle && ( +
{subtitle}
+ )} +
+
+
+ ) +} + diff --git a/vision-system-remote/src/widgets/SystemHealthWidget.tsx b/vision-system-remote/src/widgets/SystemHealthWidget.tsx new file mode 100644 index 0000000..ffe7315 --- /dev/null +++ b/vision-system-remote/src/widgets/SystemHealthWidget.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { StatusWidget } from './StatusWidget' +import { formatUptime } from '../services/api' +import type { SystemStatus } from '../services/api' + +interface SystemHealthWidgetProps { + systemStatus: SystemStatus | null +} + +export const SystemHealthWidget: React.FC = ({ systemStatus }) => { + const isOnline = systemStatus?.system_started ?? false + const uptime = systemStatus?.uptime_seconds ?? 0 + + return ( + 0 ? `Uptime: ${formatUptime(uptime)}` : undefined} + icon={ +
+ } + /> + ) +} + diff --git a/vision-system-remote/tsconfig.json b/vision-system-remote/tsconfig.json new file mode 100644 index 0000000..14a70f4 --- /dev/null +++ b/vision-system-remote/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "src/vite-env.d.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/vision-system-remote/tsconfig.node.json b/vision-system-remote/tsconfig.node.json new file mode 100644 index 0000000..e428d50 --- /dev/null +++ b/vision-system-remote/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} + diff --git a/vision-system-remote/vite.config.ts b/vision-system-remote/vite.config.ts new file mode 100644 index 0000000..9e0a09a --- /dev/null +++ b/vision-system-remote/vite.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import federation from '@originjs/vite-plugin-federation' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [ + react(), + tailwindcss(), + federation({ + name: 'visionSystemRemote', + filename: 'remoteEntry.js', + exposes: { + './App': './src/App.tsx', + }, + shared: { + react: { singleton: true, eager: true }, + 'react-dom': { singleton: true, eager: true }, + }, + }), + ], + server: { + port: 3002, + host: '0.0.0.0', + allowedHosts: ['exp-dash', 'localhost'], + cors: true + }, + build: { + target: 'esnext', + }, +}) +