diff --git a/README.md b/README.md index 3ea3f44..aec74bc 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,25 @@ The repeater supports two radio backends: - **SX1262 (SPI)** — Direct connection to LoRa modules (HATs, etc.) as listed below. - **KISS modem** — Serial TNC using the KISS protocol. Set `radio_type: kiss` in config and configure `kiss.port` and `kiss.baud_rate`. -The following SX1262 hardware is currently supported out-of-the-box: +> [!CAUTION] +> ## Compatibility +> +> ### Supported Radio Interfaces +> +> | Interface | Supported | +> |------------|------------| +> | Native SPI radio SX1262 | ✅ Yes | +> | USB–SPI bridge (CH341F) | ✅ Yes | +> | UART-based HATs | ❌ No | +> | SX1302 concentrator boards | ❌ No | +> | SX1303 concentrator boards | ❌ No | +> +> This project supports **single-radio SPI transceivers only**, either: +> - Connected directly via SPI +> - Connected via a CH341F USB–SPI adapter +> - Connected using hardware that supports Meshcore Kiss Modem firmware + +The following hardware is currently supported out-of-the-box: Waveshare LoRaWAN/GNSS HAT (SPI Version Only) @@ -199,6 +217,91 @@ The upgrade script will: - Restart the service automatically - Preserve your existing configuration +--- + +## Installing on Proxmox (LXC Container) + +pyMC Repeater can run inside a Proxmox LXC container using a **CH341 USB-to-SPI adapter** for radio communication. This is ideal for headless, always-on deployments without dedicating a full Raspberry Pi. + +### Requirements + +- **Proxmox VE 7.x or 8.x** host +- **CH341 USB-to-SPI adapter** (VID `1a86`, PID `5512`) connected to the Proxmox host +- **SX1262-based LoRa module** (e.g. Ebyte E22-900M30S) wired to the CH341 adapter +- Internet connectivity for the container + +### One-Line Install + +Run this on the **Proxmox host** (not inside a container): + +```bash +bash -c "$(curl -fsSL https://raw.githubusercontent.com/rightup/pyMC_Repeater/feat/newRadios/scripts/proxmox-install.sh)" +``` + +> **Tip:** Replace `feat/newRadios` in the URL with whichever branch you want to install. + +The installer will interactively prompt you for container settings (hostname, RAM, disk, bridge, etc.) and then: + +1. Download a Debian 12 LXC template +2. Create a **privileged** container with USB passthrough +3. Install a host-side udev rule for the CH341 device +4. Clone the repository and pre-seed the config with CH341 GPIO pin mappings +5. Run `manage.sh install` inside the container +6. Display the dashboard URL when finished + +### Default Container Settings + +| Setting | Default | +|-----------|-----------------| +| Hostname | `pymc-repeater` | +| RAM | 1024 MB | +| Disk | 4 GB | +| CPU cores | 2 | +| Bridge | `vmbr0` | +| Storage | `local-lvm` | +| Password | `pymc` | + +### After Installation + +```bash +# Enter the container +pct enter + +# View service logs +journalctl -u pymc-repeater -f + +# Access web dashboard +http://:8000 + +# Manage the repeater +cd /opt/pymc_repeater && bash manage.sh +``` + +### CH341 GPIO Pin Mapping + +The installer pre-configures the CH341 GPIO pins for an E22 module. These differ from the Raspberry Pi BCM pin numbers: + +| Function | CH341 GPIO | Pi BCM (default) | +|----------|-----------|-------------------| +| CS | 0 | 21 | +| RXEN | 1 | -1 | +| Reset | 2 | 18 | +| Busy | 4 | 20 | +| IRQ | 6 | 16 | + +The installer also enables `use_dio3_tcxo` and `use_dio2_rf` for E22 modules. + +### Troubleshooting (Proxmox) + +- **USB device not found**: Make sure the CH341 is plugged into the Proxmox host and shows up with `lsusb -d 1a86:5512` +- **Permission denied on USB**: The installer creates a host udev rule (`/etc/udev/rules.d/99-ch341.rules`). Run `udevadm trigger` on the host if needed +- **Container can't see USB**: Verify USB passthrough lines exist in `/etc/pve/lxc/.conf`: + ``` + lxc.cgroup2.devices.allow: c 189:* rwm + lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir 0 0 + ``` +- **NoBackendError (libusb)**: The installer installs `libusb-1.0-0` automatically. If you see this error, run `apt-get install libusb-1.0-0` inside the container + @@ -217,6 +320,17 @@ This script will: The script will prompt you for each optional removal step. +## Docker + +You can now run PyMC Repeater from within a [Docker Container](https://www.docker.com/). Checkout the example [Docker Compose](./docker-compose.yml) file before you get started. + +```bash +docker compose up -d --force-recreate --build +``` + +Just note that you will have to pass in a `config.yaml` into the container. You can create a new config by following the instructions in the [Configuration section](#configuration). + + ## Roadmap / Planned Features - [ ] **Public Map Integration** - Submit repeater location and details to public map for discovery @@ -264,8 +378,6 @@ Pre-commit hooks will automatically: - Lint with flake8 - Fix trailing whitespace and other file issues - - ## Support - [Core Lib Documentation](https://rightup.github.io/pyMC_core/) diff --git a/config.yaml.example b/config.yaml.example index cd5d626..b6ea9c4 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -41,6 +41,53 @@ repeater: # with its node information (node type 2 - repeater) allow_discovery: true + # Incoming advert rate limiter (per advert public key) + # Uses a token bucket to smooth bursts. + advert_rate_limit: + # Master switch for token bucket limiting + enabled: false + # Max burst size allowed immediately per pubkey + # Keep this small for long advert intervals. + bucket_capacity: 2 + # Number of tokens added each refill interval + refill_tokens: 1 + # Refill interval in seconds (10 hours) + refill_interval_seconds: 36000 + # Optional hard minimum spacing between adverts from same pubkey + # Set 0 to disable (recommended - mesh retransmissions are normal in active networks) + min_interval_seconds: 0 + + # Penalty box for repeat advert limit violations (per pubkey) + advert_penalty_box: + # Master switch for escalating cooldowns + enabled: false + # Number of violations within decay window before cooldown starts + violation_threshold: 2 + # Reset violation count if pubkey stays quiet for this long + violation_decay_seconds: 43200 + # First penalty duration in seconds + base_penalty_seconds: 21600 + # Exponential growth factor for repeated violations + penalty_multiplier: 2.0 + # Maximum penalty duration cap + max_penalty_seconds: 86400 + + # Adaptive rate limiting based on mesh activity + # Rate limits scale with mesh busyness: quiet mesh = lenient, busy mesh = strict + advert_adaptive: + # Master switch for adaptive scaling + enabled: false + # EWMA smoothing factor (0.0-1.0, higher = faster response) + ewma_alpha: 0.1 + # Seconds without metrics change before tier change takes effect (hysteresis) + hysteresis_seconds: 300 + # Tier thresholds based on adverts per minute EWMA + thresholds: + quiet_max: 0.05 # Below this = QUIET tier (no limiting) + normal_max: 0.20 # Below this = NORMAL tier (1x limits) + busy_max: 0.50 # Below this = BUSY tier (0.5x capacity) + # Above busy_max = CONGESTED tier (0.25x capacity) + # Security settings for login/authentication (shared across all identities) security: # Maximum number of authenticated clients (across all identities) @@ -70,6 +117,10 @@ mesh: # Individual transport keys can override this setting global_flood_allow: true + # Flood loop detection mode + # off = disabled, minimal = allow up to 3 self-hashes, moderate = allow up to 1, strict = allow 0 + loop_detect: minimal + # Multiple Identity Configuration (Optional) # Define additional identities for the repeater to manage # Each identity operates independently with its own key pair and configuration @@ -150,9 +201,6 @@ radio: # Sync word (LoRa network ID) sync_word: 13380 - # Enable CRC checking - crc_enabled: true - # Use implicit header mode implicit_header: false diff --git a/convert_firmware_key.sh b/convert_firmware_key.sh index b5660d0..1153f87 100755 --- a/convert_firmware_key.sh +++ b/convert_firmware_key.sh @@ -1,7 +1,7 @@ #!/bin/bash # Convert MeshCore firmware 64-byte private key to pyMC_Repeater format # -# Usage: sudo ./convert_firmware_key.sh <64-byte-hex-key> [config-path] +# Usage: sudo ./convert_firmware_key.sh <64-byte-hex-key> [--output-format=] [config-path] # Example: sudo ./convert_firmware_key.sh 987BDA619630197351F2B3040FD19B2EE0DEE357DD69BBEEE295786FA78A4D5F298B0BF1B7DE73CBC23257CDB2C562F5033DF58C232916432948B0F6BA4448F2 set -e @@ -9,10 +9,10 @@ set -e if [ $# -eq 0 ]; then echo "Error: No key provided" echo "" - echo "Usage: sudo $0 <64-byte-hex-key> [config-path]" + echo "Usage: sudo $0 <64-byte-hex-key> [--output-format=] [config-path]" echo "" echo "This script imports a 64-byte MeshCore firmware private key into" - echo "pyMC_Repeater config.yaml for full identity compatibility." + echo "pyMC_Repeater for full identity compatibility." echo "" echo "The 64-byte key format: [32-byte scalar][32-byte nonce]" echo " - Enables same node address as firmware device" @@ -20,10 +20,17 @@ if [ $# -eq 0 ]; then echo " - Fully compatible with pyMC_core LocalIdentity" echo "" echo "Arguments:" + echo " --output-format: Optional output format (yaml|identity, default: yaml)" + echo " yaml - Store in config.yaml (embedded binary)" + echo " identity - Save to identity.key file (base64 encoded)" echo " config-path: Optional path to config.yaml (default: /etc/pymc_repeater/config.yaml)" echo "" - echo "Example:" + echo "Examples:" + echo " # Save to config.yaml (default)" echo " sudo $0 987BDA619630197351F2B3040FD19B2EE0DEE357DD69BBEEE295786FA78A4D5F298B0BF1B7DE73CBC23257CDB2C562F5033DF58C232916432948B0F6BA4448F2" + echo "" + echo " # Save to identity.key file" + echo " sudo $0 987BDA619630197351F2B3040FD19B2EE0DEE357DD69BBEEE295786FA78A4D5F298B0BF1B7DE73CBC23257CDB2C562F5033DF58C232916432948B0F6BA4448F2 --output-format=identity" exit 1 fi @@ -35,6 +42,33 @@ if [ "$EUID" -ne 0 ]; then fi FULL_KEY="$1" +OUTPUT_FORMAT="yaml" # Default format +CONFIG_PATH="" + +# Parse arguments +shift # Remove the key argument +while [ $# -gt 0 ]; do + case "$1" in + --output-format=*) + OUTPUT_FORMAT="${1#*=}" + ;; + *) + CONFIG_PATH="$1" + ;; + esac + shift +done + +# Validate output format +if [ "$OUTPUT_FORMAT" != "yaml" ] && [ "$OUTPUT_FORMAT" != "identity" ]; then + echo "Error: Invalid output format '$OUTPUT_FORMAT'. Must be 'yaml' or 'identity'" + exit 1 +fi + +# Set default config path if not provided +if [ -z "$CONFIG_PATH" ]; then + CONFIG_PATH="/etc/pymc_repeater/config.yaml" +fi # Validate hex string if ! [[ "$FULL_KEY" =~ ^[0-9a-fA-F]+$ ]]; then @@ -49,17 +83,28 @@ if [ "$KEY_LEN" -ne 128 ]; then exit 1 fi -# Get config path -CONFIG_PATH="${2:-/etc/pymc_repeater/config.yaml}" - -# Check if config exists -if [ ! -f "$CONFIG_PATH" ]; then - echo "Error: Config file not found: $CONFIG_PATH" - exit 1 +# Check if config/identity file location exists (only for yaml format or if saving identity.key) +if [ "$OUTPUT_FORMAT" = "yaml" ]; then + # Check if config exists + if [ ! -f "$CONFIG_PATH" ]; then + echo "Error: Config file not found: $CONFIG_PATH" + exit 1 + fi +else + # For identity format, use system-wide location matching config.yaml + IDENTITY_DIR="/etc/pymc_repeater" + IDENTITY_PATH="$IDENTITY_DIR/identity.key" fi echo "=== MeshCore Firmware Key Import ===" echo "" +echo "Output format: $OUTPUT_FORMAT" +if [ "$OUTPUT_FORMAT" = "yaml" ]; then + echo "Target file: $CONFIG_PATH" +else + echo "Target file: $IDENTITY_PATH" +fi +echo "" echo "Input (64-byte firmware key):" echo " $FULL_KEY" echo "" @@ -70,11 +115,13 @@ import sys import yaml import base64 import hashlib +import os from pathlib import Path # Import the key key_hex = "$FULL_KEY" key_bytes = bytes.fromhex(key_hex) +output_format = "$OUTPUT_FORMAT" # Verify with pyMC if available try: @@ -94,47 +141,116 @@ except ImportError: print("Warning: PyNaCl not available, skipping verification") print() -# Load config -config_path = Path("$CONFIG_PATH") -try: - with open(config_path, 'r') as f: - config = yaml.safe_load(f) or {} -except Exception as e: - print(f"Error loading config: {e}") - sys.exit(1) +if output_format == "yaml": + # Save to config.yaml + config_path = Path("$CONFIG_PATH") + try: + with open(config_path, 'r') as f: + config = yaml.safe_load(f) or {} + except Exception as e: + print(f"Error loading config: {e}") + sys.exit(1) -# Check for existing key -if 'mesh' in config and 'identity_key' in config['mesh']: - existing = config['mesh']['identity_key'] - if isinstance(existing, bytes): - print(f"WARNING: Existing identity_key found ({len(existing)} bytes)") + # Check for existing key + if 'mesh' in config and 'identity_key' in config['mesh']: + existing = config['mesh']['identity_key'] + if isinstance(existing, bytes): + print(f"WARNING: Existing identity_key found ({len(existing)} bytes)") + else: + print(f"WARNING: Existing identity_key found") + print() + + # Ensure mesh section exists + if 'mesh' not in config: + config['mesh'] = {} + + # Store the full 64-byte key + config['mesh']['identity_key'] = key_bytes + + # Save config atomically + backup_path = f"{config_path}.backup.{Path(config_path).stat().st_mtime_ns}" + import shutil + shutil.copy2(config_path, backup_path) + print(f"Created backup: {backup_path}") + + try: + with open(config_path, 'w') as f: + yaml.safe_dump(config, f, default_flow_style=False, allow_unicode=True) + print(f"✓ Successfully updated {config_path}") + print() + except Exception as e: + print(f"Error writing config: {e}") + shutil.copy2(backup_path, config_path) + print(f"Restored from backup") + sys.exit(1) + +else: + # Save to identity.key file + identity_path = Path("$IDENTITY_PATH") + + # Create directory if it doesn't exist + identity_path.parent.mkdir(parents=True, exist_ok=True) + + # Check for existing identity.key + if identity_path.exists(): + print(f"WARNING: Existing identity.key found at {identity_path}") + backup_path = identity_path.with_suffix('.key.backup') + import shutil + shutil.copy2(identity_path, backup_path) + print(f"Created backup: {backup_path}") + print() + + # Save as base64-encoded + try: + with open(identity_path, 'wb') as f: + f.write(base64.b64encode(key_bytes)) + os.chmod(identity_path, 0o600) # Restrict permissions + print(f"✓ Successfully saved to {identity_path}") + print(f"✓ File permissions set to 0600 (owner read/write only)") + print() + except Exception as e: + print(f"Error writing identity.key: {e}") + sys.exit(1) + + # Update config.yaml to remove embedded identity_key so it uses the file + config_path = Path("$CONFIG_PATH") + if config_path.exists(): + try: + with open(config_path, 'r') as f: + config = yaml.safe_load(f) or {} + + # Check if identity_key exists in config + if 'mesh' in config and 'identity_key' in config['mesh']: + print(f"Updating {config_path} to use identity.key file...") + + # Create backup + backup_path = f"{config_path}.backup.{Path(config_path).stat().st_mtime_ns}" + import shutil + shutil.copy2(config_path, backup_path) + print(f"Created backup: {backup_path}") + + # Remove identity_key from config + del config['mesh']['identity_key'] + + # Save updated config + with open(config_path, 'w') as f: + yaml.safe_dump(config, f, default_flow_style=False, allow_unicode=True) + + print(f"✓ Removed embedded identity_key from {config_path}") + print(f"✓ Config will now use {identity_path}") + print() + else: + print(f"✓ Config file already configured to use identity.key file") + print() + + except Exception as e: + print(f"Warning: Could not update config.yaml: {e}") + print(f"You may need to manually remove 'identity_key' from {config_path}") + print() else: - print(f"WARNING: Existing identity_key found") - print() - -# Ensure mesh section exists -if 'mesh' not in config: - config['mesh'] = {} - -# Store the full 64-byte key -config['mesh']['identity_key'] = key_bytes - -# Save config atomically -backup_path = f"{config_path}.backup.{Path(config_path).stat().st_mtime_ns}" -import shutil -shutil.copy2(config_path, backup_path) -print(f"Created backup: {backup_path}") - -try: - with open(config_path, 'w') as f: - yaml.safe_dump(config, f, default_flow_style=False, allow_unicode=True) - print(f"✓ Successfully updated {config_path}") - print() -except Exception as e: - print(f"Error writing config: {e}") - shutil.copy2(backup_path, config_path) - print(f"Restored from backup") - sys.exit(1) + print(f"Note: Config file not found at {config_path}") + print(f" Identity will be loaded from {identity_path}") + print() EOF @@ -143,20 +259,41 @@ if [ $? -ne 0 ]; then exit 1 fi -# Offer to restart service -if systemctl is-active --quiet pymc-repeater 2>/dev/null; then - read -p "Restart pymc-repeater service now? (yes/no): " RESTART - if [ "$RESTART" = "yes" ]; then - systemctl restart pymc-repeater - echo "✓ Service restarted" - echo "" - echo "Check logs for new identity:" - echo " sudo journalctl -u pymc-repeater -f | grep -i 'identity\|hash'" +# Offer to restart service (only relevant for yaml format) +if [ "$OUTPUT_FORMAT" = "yaml" ]; then + if systemctl is-active --quiet pymc-repeater 2>/dev/null; then + read -p "Restart pymc-repeater service now? (yes/no): " RESTART + if [ "$RESTART" = "yes" ]; then + systemctl restart pymc-repeater + echo "✓ Service restarted" + echo "" + echo "Check logs for new identity:" + echo " sudo journalctl -u pymc-repeater -f | grep -i 'identity\|hash'" + else + echo "Remember to restart the service:" + echo " sudo systemctl restart pymc-repeater" + fi else - echo "Remember to restart the service:" - echo " sudo systemctl restart pymc-repeater" + echo "Note: pymc-repeater service is not running" + echo "Start it with: sudo systemctl start pymc-repeater" fi else - echo "Note: pymc-repeater service is not running" - echo "Start it with: sudo systemctl start pymc-repeater" + echo "Identity key saved to file." + echo "" + if systemctl is-active --quiet pymc-repeater 2>/dev/null; then + read -p "Restart pymc-repeater service now? (yes/no): " RESTART + if [ "$RESTART" = "yes" ]; then + systemctl restart pymc-repeater + echo "✓ Service restarted" + echo "" + echo "Check logs for new identity:" + echo " sudo journalctl -u pymc-repeater -f | grep -i 'identity\|hash'" + else + echo "Remember to restart the service:" + echo " sudo systemctl restart pymc-repeater" + fi + else + echo "Note: pymc-repeater service is not running" + echo "Start it with: sudo systemctl start pymc-repeater" + fi fi diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f65a1de --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + pymc-repeater: + build: . + container_name: pymc-repeater + restart: unless-stopped + ports: + - 8000:8000 + devices: + - /dev/spidev0.0 + - /dev/gpiochip0 + cap_add: + - SYS_RAWIO + volumes: + - ./config.yaml:/etc/pymc_repeater/config.yaml + - ./data:/var/lib/pymc_repeater diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..8d3689f --- /dev/null +++ b/dockerfile @@ -0,0 +1,35 @@ +FROM python:3.12-slim-bookworm + +ENV INSTALL_DIR=/opt/pymc_repeater \ + CONFIG_DIR=/etc/pymc_repeater \ + DATA_DIR=/var/lib/pymc_repeater \ + PYTHONUNBUFFERED=1 \ + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYMC_REPEATER=1.0.5 + +# Install runtime dependencies only +RUN apt-get update && apt-get install -y \ + libffi-dev \ + python3-rrdtool \ + jq \ + wget \ + swig \ + git \ + build-essential \ + python3-dev \ + && rm -rf /var/lib/apt/lists/* + +# Create runtime directories +RUN mkdir -p ${INSTALL_DIR} ${CONFIG_DIR} ${DATA_DIR} + +WORKDIR ${INSTALL_DIR} + +# Copy source +COPY repeater ./repeater +COPY pyproject.toml . + +# Install package +RUN pip install --no-cache-dir . + +EXPOSE 8000 + +ENTRYPOINT ["python3", "-m", "repeater.main", "--config", "/etc/pymc_repeater/config.yaml"] diff --git a/manage.sh b/manage.sh index e5f9ac1..6c8dd02 100755 --- a/manage.sh +++ b/manage.sh @@ -8,12 +8,31 @@ CONFIG_DIR="/etc/pymc_repeater" LOG_DIR="/var/log/pymc_repeater" SERVICE_USER="repeater" SERVICE_NAME="pymc-repeater" +SILENT_MODE="${PYMC_SILENT:-${SILENT:-}}" + +is_silent_flag() { + case "${1:-}" in + --silent|-y|silent) return 0 ;; + *) return 1 ;; + esac +} + +is_interactive_flag() { + case "${1:-}" in + --interactive|-i|interactive) return 0 ;; + *) return 1 ;; + esac +} # Check if we're running in an interactive terminal if [ ! -t 0 ] || [ -z "$TERM" ]; then - echo "Error: This script requires an interactive terminal." - echo "Please run from SSH or a local terminal, not via file manager." - exit 1 + if [[ "$1" =~ ^(upgrade|start|stop|restart)$ ]] && ! is_interactive_flag "$2"; then + : + else + echo "Error: This script requires an interactive terminal." + echo "Please run from SSH or a local terminal, not via file manager." + exit 1 + fi fi # Check if whiptail is available, fallback to dialog @@ -125,7 +144,7 @@ show_main_menu() { ;; "upgrade") if is_installed; then - upgrade_repeater + upgrade_repeater "false" else show_error "pyMC Repeater is not installed!\n\nUse 'install' first." fi @@ -148,19 +167,22 @@ show_main_menu() { configure_radio ;; "start") - manage_service "start" + manage_service "start" "false" ;; "stop") - manage_service "stop" + manage_service "stop" "false" ;; "restart") - manage_service "restart" + manage_service "restart" "false" ;; "logs") clear - echo "=== Live Logs (Press Ctrl+C to return) ===" + echo -e "\033[1;36m╔══════════════════════════════════════════════════════════════════════╗\033[0m" + echo -e "\033[1;36m║\033[0m \033[1;37mpyMC Repeater - Live Logs\033[0m \033[1;36m║\033[0m" + echo -e "\033[1;36m║\033[0m \033[0;90m(Press Ctrl+C to return)\033[0m \033[1;36m║\033[0m" + echo -e "\033[1;36m╚══════════════════════════════════════════════════════════════════════╝\033[0m" echo "" - journalctl -u "$SERVICE_NAME" -f + journalctl -u "$SERVICE_NAME" -f -o cat --no-hostname | sed -e 's/.*ERROR.*/\x1b[1;31m&\x1b[0m/' -e 's/.*CRITICAL.*/\x1b[1;41;37m&\x1b[0m/' -e 's/.*WARNING.*/\x1b[1;33m&\x1b[0m/' -e 's/.*INFO.*/\x1b[0;32m&\x1b[0m/' -e 's/.*DEBUG.*/\x1b[0;36m&\x1b[0m/' ;; "status") show_detailed_status @@ -182,9 +204,16 @@ install_repeater() { # Welcome screen $DIALOG --backtitle "pyMC Repeater Management" --title "Welcome" --msgbox "\nWelcome to pyMC Repeater Setup\n\nThis installer will configure your Linux system as a LoRa mesh network repeater.\n\nPress OK to continue..." 12 70 - # SPI Check - Universal approach that works on all boards + # SPI Check - Universal approach that works on all boards (skip for CH341 USB-SPI adapter) SPI_MISSING=0 - if ! ls /dev/spidev* >/dev/null 2>&1; then + USES_CH341=0 + if [ -f "$CONFIG_DIR/config.yaml" ]; then + if grep -q "radio_type:.*sx1262_ch341" "$CONFIG_DIR/config.yaml" 2>/dev/null; then + USES_CH341=1 + fi + fi + + if [ "$USES_CH341" -eq 0 ] && ! ls /dev/spidev* >/dev/null 2>&1; then # SPI devices not found, check if we're on a Raspberry Pi and can enable it CONFIG_FILE="" if [ -f "/boot/firmware/config.txt" ]; then @@ -226,26 +255,37 @@ install_repeater() { SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # Installation progress - ( - echo "0"; echo "# Creating service user..." + echo "" + echo "═══════════════════════════════════════════════════════════════" + echo " Installing pyMC Repeater" + echo "═══════════════════════════════════════════════════════════════" + echo "" + + echo ">>> Creating service user..." if ! id "$SERVICE_USER" &>/dev/null; then useradd --system --home /var/lib/pymc_repeater --shell /sbin/nologin "$SERVICE_USER" fi echo "10"; echo "# Adding user to hardware groups..." - usermod -a -G gpio,i2c,spi "$SERVICE_USER" 2>/dev/null || true - usermod -a -G dialout "$SERVICE_USER" 2>/dev/null || true + for grp in plugdev dialout gpio i2c spi; do + getent group "$grp" >/dev/null 2>&1 && usermod -a -G "$grp" "$SERVICE_USER" 2>/dev/null || true + done echo "20"; echo "# Creating directories..." mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater echo "25"; echo "# Installing system dependencies..." apt-get update -qq - apt-get install -y libffi-dev jq pip python3-rrdtool wget swig build-essential python3-dev + DEBIAN_FRONTEND=noninteractive apt-get install -y libffi-dev libusb-1.0-0 sudo jq pip python3-rrdtool wget swig build-essential python3-dev + # Install polkit (package name varies by distro version) + DEBIAN_FRONTEND=noninteractive apt-get install -y policykit-1 2>/dev/null \ + || DEBIAN_FRONTEND=noninteractive apt-get install -y polkitd pkexec 2>/dev/null \ + || echo " Warning: Could not install polkit (sudo fallback will be used)" pip install --break-system-packages setuptools_scm >/dev/null 2>&1 || true # Install mikefarah yq v4 if not already installed if ! command -v yq &> /dev/null || [[ "$(yq --version 2>&1)" != *"mikefarah/yq"* ]]; then + echo ">>> Installing yq..." YQ_VERSION="v4.40.5" YQ_BINARY="yq_linux_arm64" if [[ "$(uname -m)" == "x86_64" ]]; then @@ -253,18 +293,17 @@ install_repeater() { elif [[ "$(uname -m)" == "armv7"* ]]; then YQ_BINARY="yq_linux_arm" fi - wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" && chmod +x /usr/local/bin/yq + wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" 2>/dev/null && chmod +x /usr/local/bin/yq fi echo "28"; echo "# Generating version file..." cd "$SCRIPT_DIR" # Generate version file using setuptools_scm before copying if [ -d .git ]; then - git fetch --tags 2>/dev/null || true + git fetch --tags >/dev/null 2>&1 || true # Write the version file that will be copied - GENERATED_VERSION=$(python3 -m setuptools_scm 2>&1 || echo "unknown (setuptools_scm not available)") - python3 -c "from setuptools_scm import get_version; get_version(write_to='repeater/_version.py')" 2>&1 || echo " Warning: Could not generate _version.py file" - echo " Generated version: $GENERATED_VERSION" + python3 -m setuptools_scm >/dev/null 2>&1 || true + python3 -c "from setuptools_scm import get_version; get_version(write_to='repeater/_version.py')" >/dev/null 2>&1 || true fi # Clean up stale bytecode in source directory before copying @@ -297,6 +336,13 @@ install_repeater() { cp "$SCRIPT_DIR/pymc-repeater.service" /etc/systemd/system/ systemctl daemon-reload + echo "58"; echo "# Installing udev rules for CH341..." + if [ -f "$SCRIPT_DIR/../pyMC_core/99-ch341.rules" ]; then + cp "$SCRIPT_DIR/../pyMC_core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules + udevadm control --reload-rules 2>/dev/null || true + udevadm trigger 2>/dev/null || true + fi + echo "65"; echo "# Setting permissions..." chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater chmod 750 "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater @@ -307,6 +353,7 @@ install_repeater() { chown -R "$SERVICE_USER:$SERVICE_USER" /var/lib/pymc_repeater/.config # Configure polkit for passwordless service restart + echo ">>> Configuring polkit for service management..." mkdir -p /etc/polkit-1/rules.d cat > /etc/polkit-1/rules.d/10-pymc-repeater.rules <<'EOF' polkit.addRule(function(action, subject) { @@ -319,6 +366,15 @@ polkit.addRule(function(action, subject) { EOF chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules + # Also configure sudoers as fallback for service restart + echo ">>> Configuring sudoers for service management..." + mkdir -p /etc/sudoers.d + cat > /etc/sudoers.d/pymc-repeater <<'EOF' +# Allow repeater user to manage the pymc-repeater service without password +repeater ALL=(root) NOPASSWD: /usr/bin/systemctl restart pymc-repeater, /usr/bin/systemctl stop pymc-repeater, /usr/bin/systemctl start pymc-repeater, /usr/bin/systemctl status pymc-repeater +EOF + chmod 0440 /etc/sudoers.d/pymc-repeater + echo "75"; echo "# Starting service..." systemctl enable "$SERVICE_NAME" @@ -394,6 +450,23 @@ EOF echo " • Set admin password" echo " 3. Log in to your configured repeater" echo "" + # Container detection: warn about host-side udev rules + if [ -f /run/host/container-manager ] || [ -n "${container:-}" ] || grep -qsai 'container=' /proc/1/environ 2>/dev/null || [ -f /.dockerenv ]; then + echo "═══════════════════════════════════════════════════════════════" + echo " ⚠ CONTAINER ENVIRONMENT DETECTED" + echo "═══════════════════════════════════════════════════════════════" + echo "" + echo " USB device udev rules do NOT work inside containers." + echo " You MUST install the CH341 udev rule on the HOST machine:" + echo "" + echo " echo 'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"1a86\", ATTR{idProduct}==\"5512\", MODE=\"0666\"' \\" + echo " | sudo tee /etc/udev/rules.d/99-ch341.rules" + echo " sudo udevadm control --reload-rules" + echo " sudo udevadm trigger --subsystem-match=usb --action=change" + echo "" + echo " Then unplug and replug the CH341 USB adapter." + echo "" + fi echo "═══════════════════════════════════════════════════════════════" echo "" read -p "Press Enter to return to main menu..." || true @@ -472,17 +545,29 @@ reset_repeater() { # Upgrade function upgrade_repeater() { + local silent="${1:-false}" if [ "$EUID" -ne 0 ]; then - show_error "Upgrade requires root privileges.\n\nPlease run: sudo $0" - return + if [[ "$silent" == "true" ]]; then + echo "Upgrade requires root privileges. Please run: sudo $0 upgrade" + else + show_error "Upgrade requires root privileges.\n\nPlease run: sudo $0" + fi + return 1 fi local current_version=$(get_version) - if ask_yes_no "Confirm Upgrade" "Current version: $current_version\n\nThis will upgrade pyMC Repeater while preserving your configuration.\n\nContinue?"; then + if [[ "$silent" != "true" ]]; then + if ! ask_yes_no "Confirm Upgrade" "Current version: $current_version\n\nThis will upgrade pyMC Repeater while preserving your configuration.\n\nContinue?"; then + return 0 + fi # Show info that upgrade is starting show_info "Upgrading" "Starting upgrade process...\n\nThis may take a few minutes.\nProgress will be shown in the terminal." + else + echo "Starting upgrade process..." + echo "Current version: $current_version" + fi echo "=== Upgrade Progress ===" echo "[1/9] Stopping service..." @@ -497,7 +582,11 @@ upgrade_repeater() { echo "[3/9] Updating system dependencies..." apt-get update -qq - apt-get install -y libffi-dev jq pip python3-rrdtool wget swig build-essential python3-dev + apt-get install -y libffi-dev libusb-1.0-0 sudo jq pip python3-rrdtool wget swig build-essential python3-dev + # Install polkit (package name varies by distro version) + apt-get install -y policykit-1 2>/dev/null \ + || apt-get install -y polkitd pkexec 2>/dev/null \ + || echo " Warning: Could not install polkit (sudo fallback will be used)" pip install --break-system-packages setuptools_scm >/dev/null 2>&1 || true # Install mikefarah yq v4 if not already installed @@ -553,6 +642,22 @@ upgrade_repeater() { echo " ⚠ Configuration validation failed, keeping existing config" fi + echo "[5.5/9] Ensuring user groups and udev rules..." + for grp in plugdev dialout gpio i2c spi; do + getent group "$grp" >/dev/null 2>&1 && usermod -a -G "$grp" "$SERVICE_USER" 2>/dev/null || true + done + # Install/update CH341 udev rules + SCRIPT_DIR_UPGRADE="$(cd "$(dirname "$0")" && pwd)" + if [ -f "$SCRIPT_DIR_UPGRADE/../pyMC_core/99-ch341.rules" ]; then + cp "$SCRIPT_DIR_UPGRADE/../pyMC_core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules + udevadm control --reload-rules 2>/dev/null || true + udevadm trigger 2>/dev/null || true + echo " ✓ CH341 udev rules updated" + elif [ -f /etc/udev/rules.d/99-ch341.rules ]; then + echo " ✓ CH341 udev rules already present" + fi + echo " ✓ User groups updated" + echo "[6/9] Fixing permissions..." chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater 2>/dev/null || true chmod 750 "$CONFIG_DIR" "$LOG_DIR" 2>/dev/null || true @@ -572,6 +677,13 @@ polkit.addRule(function(action, subject) { }); EOF chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules + # Also configure sudoers as fallback for service restart + mkdir -p /etc/sudoers.d + cat > /etc/sudoers.d/pymc-repeater <<'EOF' +# Allow repeater user to manage the pymc-repeater service without password +repeater ALL=(root) NOPASSWD: /usr/bin/systemctl restart pymc-repeater, /usr/bin/systemctl stop pymc-repeater, /usr/bin/systemctl start pymc-repeater, /usr/bin/systemctl status pymc-repeater +EOF + chmod 0440 /etc/sudoers.d/pymc-repeater echo " ✓ Permissions updated" echo "[7/9] Reloading systemd..." @@ -632,13 +744,33 @@ EOF if is_running; then echo " ✓ Service is running" - show_info "Upgrade Complete" "Upgrade completed successfully!\n\nVersion: $current_version → $new_version\n\n✓ Service is running\n✓ Configuration preserved" + # Container detection: warn about host-side udev rules + local container_note="" + if [ -f /run/host/container-manager ] || [ -n "${container:-}" ] || grep -qsai 'container=' /proc/1/environ 2>/dev/null || [ -f /.dockerenv ]; then + container_note="\n\n⚠ CONTAINER DETECTED:\nUSB udev rules must be set on the HOST, not here.\nSee documentation for CH341 host-side setup." + fi + if [[ "$silent" == "true" ]]; then + echo "Upgrade completed successfully!" + echo "Version: $current_version -> $new_version" + echo "✓ Service is running" + echo "✓ Configuration preserved" + if [[ -n "$container_note" ]]; then + echo "$container_note" + fi + else + show_info "Upgrade Complete" "Upgrade completed successfully!\n\nVersion: $current_version → $new_version\n\n✓ Service is running\n✓ Configuration preserved${container_note}" + fi else echo " ✗ Service failed to start" - show_error "Upgrade completed but service failed to start!\n\nVersion updated: $current_version → $new_version\n\nCheck logs from the main menu for details." + if [[ "$silent" == "true" ]]; then + echo "Upgrade completed but service failed to start!" + echo "Version updated: $current_version -> $new_version" + echo "Check logs from the main menu for details." + else + show_error "Upgrade completed but service failed to start!\n\nVersion updated: $current_version → $new_version\n\nCheck logs from the main menu for details." + fi fi echo "=== Upgrade Complete ===" - fi } # Radio Configuration function @@ -689,8 +821,13 @@ uninstall_repeater() { fi if ask_yes_no "Confirm Uninstall" "This will completely remove pyMC Repeater including:\n\n- Service and files\n- Configuration (backup will be created)\n- Logs and data\n\nThis action cannot be undone!\n\nContinue?"; then - ( - echo "0"; echo "# Stopping and disabling service..." + echo "" + echo "═══════════════════════════════════════════════════════════════" + echo " Uninstalling pyMC Repeater" + echo "═══════════════════════════════════════════════════════════════" + echo "" + + echo ">>> Stopping and disabling service..." systemctl stop "$SERVICE_NAME" 2>/dev/null || true systemctl disable "$SERVICE_NAME" 2>/dev/null || true @@ -703,6 +840,10 @@ uninstall_repeater() { rm -f /etc/systemd/system/pymc-repeater.service systemctl daemon-reload + echo "50"; echo "# Removing polkit and sudoers rules..." + rm -f /etc/polkit-1/rules.d/10-pymc-repeater.rules + rm -f /etc/sudoers.d/pymc-repeater + echo "60"; echo "# Removing installation..." rm -rf "$INSTALL_DIR" rm -rf "$CONFIG_DIR" @@ -724,15 +865,24 @@ uninstall_repeater() { # Service management manage_service() { local action=$1 + local silent="${2:-false}" if [ "$EUID" -ne 0 ]; then - show_error "Service management requires root privileges.\n\nPlease run: sudo $0" - return + if [[ "$silent" == "true" ]]; then + echo "Service management requires root privileges. Please run: sudo $0 $action" + else + show_error "Service management requires root privileges.\n\nPlease run: sudo $0" + fi + return 1 fi if ! service_exists; then - show_error "Service is not installed." - return + if [[ "$silent" == "true" ]]; then + echo "Service is not installed." + else + show_error "Service is not installed." + fi + return 1 fi case $action in @@ -742,21 +892,43 @@ manage_service() { fi systemctl start "$SERVICE_NAME" if is_running; then - show_info "Service Started" "\n✓ pyMC Repeater service has been started successfully." + if [[ "$silent" == "true" ]]; then + echo "✓ pyMC Repeater service has been started successfully." + else + show_info "Service Started" "\n✓ pyMC Repeater service has been started successfully." + fi else - show_error "Failed to start service!\n\nCheck logs for details." + if [[ "$silent" == "true" ]]; then + echo "Failed to start service!" + echo "Check logs for details." + else + show_error "Failed to start service!\n\nCheck logs for details." + fi fi ;; "stop") systemctl stop "$SERVICE_NAME" - show_info "Service Stopped" "\n✓ pyMC Repeater service has been stopped." + if [[ "$silent" == "true" ]]; then + echo "✓ pyMC Repeater service has been stopped." + else + show_info "Service Stopped" "\n✓ pyMC Repeater service has been stopped." + fi ;; "restart") systemctl restart "$SERVICE_NAME" if is_running; then - show_info "Service Restarted" "\n✓ pyMC Repeater service has been restarted successfully." + if [[ "$silent" == "true" ]]; then + echo "✓ pyMC Repeater service has been restarted successfully." + else + show_info "Service Restarted" "\n✓ pyMC Repeater service has been restarted successfully." + fi else - show_error "Failed to restart service!\n\nCheck logs for details." + if [[ "$silent" == "true" ]]; then + echo "Failed to restart service!" + echo "Check logs for details." + else + show_error "Failed to restart service!\n\nCheck logs for details." + fi fi ;; esac @@ -849,7 +1021,14 @@ validate_and_update_config() { # - Adds missing keys from the left operand (example config) local temp_merged="${config_file}.merged" - if "$YQ_CMD" eval-all '. as $item ireduce ({}; . * $item)' "$updated_example" "$config_file" > "$temp_merged" 2>/dev/null; then + # Strip comments from user config before merge to prevent comment accumulation. + # yq preserves comments from both files, so each upgrade cycle would duplicate + # the header and inline comments. We keep only the example's comments. + local stripped_user="${config_file}.stripped" + "$YQ_CMD" eval '... comments=""' "$config_file" > "$stripped_user" 2>/dev/null || cp "$config_file" "$stripped_user" + + if "$YQ_CMD" eval-all '. as $item ireduce ({}; . * $item)' "$updated_example" "$stripped_user" > "$temp_merged" 2>/dev/null; then + rm -f "$stripped_user" # Verify the merged file is valid YAML if "$YQ_CMD" eval '.' "$temp_merged" > /dev/null 2>&1; then mv "$temp_merged" "$config_file" @@ -864,7 +1043,7 @@ validate_and_update_config() { fi else echo " ✗ Config merge failed, keeping original" - rm -f "$temp_merged" + rm -f "$temp_merged" "$stripped_user" return 1 fi } @@ -877,12 +1056,12 @@ if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then echo "" echo "Actions:" echo " install - Install pyMC Repeater" - echo " upgrade - Upgrade existing installation" + echo " upgrade - Upgrade existing installation (CLI is silent by default; use --interactive to show dialogs)" echo " uninstall - Remove pyMC Repeater" echo " config - Configure radio settings" - echo " start - Start the service" - echo " stop - Stop the service" - echo " restart - Restart the service" + echo " start - Start the service (CLI is silent by default; use --interactive to show dialogs)" + echo " stop - Stop the service (CLI is silent by default; use --interactive to show dialogs)" + echo " restart - Restart the service (CLI is silent by default; use --interactive to show dialogs)" echo " logs - View live logs" echo " status - Show status" echo " debug - Show debug information" @@ -914,7 +1093,11 @@ case "$1" in exit 0 ;; "upgrade") - upgrade_repeater + silent_mode="true" + if is_interactive_flag "${2:-}" || [[ "$SILENT_MODE" == "0" || "$SILENT_MODE" == "false" ]]; then + silent_mode="false" + fi + upgrade_repeater "$silent_mode" exit 0 ;; "uninstall") @@ -926,14 +1109,21 @@ case "$1" in exit 0 ;; "start"|"stop"|"restart") - manage_service "$1" + silent_mode="true" + if is_interactive_flag "${2:-}" || [[ "$SILENT_MODE" == "0" || "$SILENT_MODE" == "false" ]]; then + silent_mode="false" + fi + manage_service "$1" "$silent_mode" exit 0 ;; "logs") clear - echo "=== Live Logs (Press Ctrl+C to return) ===" + echo -e "\033[1;36m╔══════════════════════════════════════════════════════════════════════╗\033[0m" + echo -e "\033[1;36m║\033[0m \033[1;37mpyMC Repeater - Live Logs\033[0m \033[1;36m║\033[0m" + echo -e "\033[1;36m║\033[0m \033[0;90m(Press Ctrl+C to return)\033[0m \033[1;36m║\033[0m" + echo -e "\033[1;36m╚══════════════════════════════════════════════════════════════════════╝\033[0m" echo "" - journalctl -u "$SERVICE_NAME" -f + journalctl -u "$SERVICE_NAME" -f -o cat --no-hostname | sed -e 's/.*ERROR.*/\x1b[1;31m&\x1b[0m/' -e 's/.*CRITICAL.*/\x1b[1;41;37m&\x1b[0m/' -e 's/.*WARNING.*/\x1b[1;33m&\x1b[0m/' -e 's/.*INFO.*/\x1b[0;32m&\x1b[0m/' -e 's/.*DEBUG.*/\x1b[0;36m&\x1b[0m/' ;; "status") show_detailed_status diff --git a/pymc-repeater.service b/pymc-repeater.service index 8c07d40..80b0456 100644 --- a/pymc-repeater.service +++ b/pymc-repeater.service @@ -28,9 +28,9 @@ StandardOutput=journal StandardError=journal SyslogIdentifier=pymc-repeater -# Security (relaxed for proper operation) -NoNewPrivileges=true +# Security (relaxed for service self-restart via sudo) ReadWritePaths=/var/log/pymc_repeater /var/lib/pymc_repeater /etc/pymc_repeater +SupplementaryGroups=plugdev dialout [Install] WantedBy=multi-user.target diff --git a/radio-presets.json b/radio-presets.json index 99090fd..d173566 100644 --- a/radio-presets.json +++ b/radio-presets.json @@ -25,6 +25,14 @@ "bandwidth": "250", "coding_rate": "5" }, + { + "title": "Australia: NSW (Wide)", + "description": "915.800MHz / SF11 / BW250 / CR5", + "frequency": "915.800", + "spreading_factor": "11", + "bandwidth": "250", + "coding_rate": "5" + }, { "title": "Australia (Narrow)", "description": "916.575MHz / SF7 / BW62.5 / CR8", diff --git a/radio-settings.json b/radio-settings.json index 390ab8d..e3f74ad 100644 --- a/radio-settings.json +++ b/radio-settings.json @@ -48,24 +48,8 @@ "use_dio3_tcxo": true, "use_dio2_rf": true }, - "pimesh-1w-usa": { - "name": "PiMesh-1W (USA)", - "bus_id": 0, - "cs_id": 0, - "cs_pin": 21, - "reset_pin": 18, - "busy_pin": 20, - "irq_pin": 16, - "txen_pin": 13, - "rxen_pin": 12, - "txled_pin": -1, - "rxled_pin": -1, - "tx_power": 30, - "use_dio3_tcxo": true, - "preamble_length": 17 - }, - "pimesh-1w-uk": { - "name": "PiMesh-1W (UK)", + "pimesh-1w-v1": { + "name": "PiMesh-1W (V1)", "bus_id": 0, "cs_id": 0, "cs_pin": 21, @@ -80,6 +64,24 @@ "use_dio3_tcxo": true, "preamble_length": 17 }, + "pimesh-1w-v2": { + "name": "PiMesh-1W (V2)", + "bus_id": 0, + "cs_id": 0, + "cs_pin": -1, + "reset_pin": 18, + "busy_pin": 5, + "irq_pin": 6, + "txen_pin": -1, + "rxen_pin": -1, + "txled_pin": -1, + "rxled_pin": -1, + "en_pin": 26, + "tx_power": 22, + "use_dio3_tcxo": true, + "use_dio2_rf": true, + "preamble_length": 17 + }, "meshadv-mini": { "name": "MeshAdv Mini", "bus_id": 0, @@ -146,6 +148,24 @@ "use_dio3_tcxo": true, "preamble_length": 17 }, + "femtofox-2W-SX": { + "name": "FemtoFox SX1262 (2W)", + "bus_id": 0, + "cs_id": 0, + "cs_pin": 16, + "gpio_chip": 1, + "use_gpiod_backend": true, + "reset_pin": 25, + "busy_pin": 22, + "irq_pin": 23, + "txen_pin": -1, + "rxen_pin": 24, + "txled_pin": -1, + "rxled_pin": -1, + "tx_power": 8, + "use_dio2_rf": true, + "use_dio3_tcxo": true + }, "nebrahat": { "name": "NebraHat-2W", "bus_id": 0, @@ -185,6 +205,45 @@ "dio3_tcxo_voltage": 1.8, "preamble_length": 17, "is_waveshare": false + }, + "ultrapeater-e22": { + "name": "Zindello Industries UltraPeater E22", + "bus_id": 0, + "cs_id": 0, + "cs_pin": 16, + "reset_pin": 22, + "busy_pin": 11, + "irq_pin": 10, + "txen_pin": 20, + "rxen_pin": 21, + "txled_pin": 8, + "rxled_pin": 1, + "tx_power": 22, + "use_dio2_rf": false, + "use_dio3_tcxo": true, + "preamble_length": 17, + "use_gpiod_backend": true, + "gpio_chip": 1 + }, + "ultrapeater-e22p": { + "name": "Zindello Industries UltraPeater E22P", + "bus_id": 0, + "cs_id": 0, + "cs_pin": 16, + "reset_pin": 22, + "busy_pin": 11, + "irq_pin": 10, + "txen_pin": 20, + "rxen_pin": -1, + "en_pin": 21, + "txled_pin": 8, + "rxled_pin": 1, + "tx_power": 22, + "use_dio2_rf": false, + "use_dio3_tcxo": true, + "preamble_length": 17, + "use_gpiod_backend": true, + "gpio_chip": 1 } } } diff --git a/repeater/config.py b/repeater/config.py index 78dcb67..e6e38fd 100644 --- a/repeater/config.py +++ b/repeater/config.py @@ -156,13 +156,18 @@ def update_global_flood_policy(allow: bool, config_path: Optional[str] = None) - def _load_or_create_identity_key(path: Optional[str] = None) -> bytes: if path is None: - # Follow XDG spec - xdg_config_home = os.environ.get("XDG_CONFIG_HOME") - if xdg_config_home: - config_dir = Path(xdg_config_home) / "pymc_repeater" + # Check system-wide location first (matches config.yaml location) + system_key_path = Path("/etc/pymc_repeater/identity.key") + if system_key_path.exists(): + key_path = system_key_path else: - config_dir = Path.home() / ".config" / "pymc_repeater" - key_path = config_dir / "identity.key" + # Follow XDG spec + xdg_config_home = os.environ.get("XDG_CONFIG_HOME") + if xdg_config_home: + config_dir = Path(xdg_config_home) / "pymc_repeater" + else: + config_dir = Path.home() / ".config" / "pymc_repeater" + key_path = config_dir / "identity.key" else: key_path = Path(path) @@ -173,8 +178,8 @@ def _load_or_create_identity_key(path: Optional[str] = None) -> bytes: with open(key_path, "rb") as f: encoded = f.read() key = base64.b64decode(encoded) - if len(key) != 32: - raise ValueError(f"Invalid key length: {len(key)}, expected 32") + if len(key) not in (32, 64): + raise ValueError(f"Invalid key length: {len(key)}, expected 32 or 64") logger.info(f"Loaded existing identity key from {key_path}") return key except Exception as e: @@ -249,6 +254,7 @@ def get_radio_for_board(board_config: dict): "rxen_pin": _parse_int(spi_config["rxen_pin"]), "txled_pin": _parse_int(spi_config.get("txled_pin", -1), default=-1), "rxled_pin": _parse_int(spi_config.get("rxled_pin", -1), default=-1), + "en_pin": _parse_int(spi_config.get("en_pin", -1), default=-1), "use_dio3_tcxo": spi_config.get("use_dio3_tcxo", False), "dio3_tcxo_voltage": float(spi_config.get("dio3_tcxo_voltage", 1.8)), "use_dio2_rf": spi_config.get("use_dio2_rf", False), diff --git a/repeater/config_manager.py b/repeater/config_manager.py index b4c13f8..e15eea1 100644 --- a/repeater/config_manager.py +++ b/repeater/config_manager.py @@ -94,6 +94,13 @@ class ConfigManager: self.daemon.repeater_handler.reload_runtime_config() logger.info("Reloaded RepeaterHandler runtime config") + # Also reload advert_helper config if repeater section changed + if self.daemon and hasattr(self.daemon, 'advert_helper') and self.daemon.advert_helper: + if 'repeater' in sections: + if hasattr(self.daemon.advert_helper, 'reload_config'): + self.daemon.advert_helper.reload_config() + logger.info("Reloaded AdvertHelper config") + return True except Exception as e: diff --git a/repeater/data_acquisition/sqlite_handler.py b/repeater/data_acquisition/sqlite_handler.py index b8aa4b0..5302f0d 100644 --- a/repeater/data_acquisition/sqlite_handler.py +++ b/repeater/data_acquisition/sqlite_handler.py @@ -83,6 +83,16 @@ class SQLiteHandler: """ ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS crc_errors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp REAL NOT NULL, + count INTEGER NOT NULL DEFAULT 1 + ) + """ + ) + conn.execute( """ CREATE TABLE IF NOT EXISTS transport_keys ( @@ -126,6 +136,9 @@ class SQLiteHandler: conn.execute( "CREATE INDEX IF NOT EXISTS idx_noise_timestamp ON noise_floor(timestamp)" ) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_crc_errors_timestamp ON crc_errors(timestamp)" + ) conn.execute( "CREATE INDEX IF NOT EXISTS idx_transport_keys_name ON transport_keys(name)" ) @@ -683,6 +696,54 @@ class SQLiteHandler: except Exception as e: logger.error(f"Failed to store noise floor in SQLite: {e}") + def store_crc_errors(self, record: dict): + """Store a CRC error batch (delta count since last poll).""" + try: + with sqlite3.connect(self.sqlite_path) as conn: + conn.execute(""" + INSERT INTO crc_errors (timestamp, count) + VALUES (?, ?) + """, ( + record.get("timestamp", time.time()), + record.get("count", 1) + )) + except Exception as e: + logger.error(f"Failed to store CRC errors in SQLite: {e}") + + def get_crc_error_count(self, hours: int = 24) -> int: + """Return total CRC errors within the given time window.""" + try: + cutoff = time.time() - (hours * 3600) + with sqlite3.connect(self.sqlite_path) as conn: + row = conn.execute( + "SELECT COALESCE(SUM(count), 0) FROM crc_errors WHERE timestamp > ?", + (cutoff,) + ).fetchone() + return row[0] if row else 0 + except Exception as e: + logger.error(f"Failed to get CRC error count: {e}") + return 0 + + def get_crc_error_history(self, hours: int = 24, limit: int = None) -> list: + """Return CRC error records within the given time window (chronological).""" + try: + cutoff = time.time() - (hours * 3600) + with sqlite3.connect(self.sqlite_path) as conn: + conn.row_factory = sqlite3.Row + query = """ + SELECT timestamp, count + FROM crc_errors + WHERE timestamp > ? + ORDER BY timestamp DESC + """ + if limit: + query += f" LIMIT {int(limit)}" + rows = conn.execute(query, (cutoff,)).fetchall() + return [{"timestamp": r["timestamp"], "count": r["count"]} for r in reversed(rows)] + except Exception as e: + logger.error(f"Failed to get CRC error history: {e}") + return [] + def get_packet_stats(self, hours: int = 24) -> dict: try: cutoff = time.time() - (hours * 3600) @@ -1101,11 +1162,14 @@ class SQLiteHandler: result = conn.execute("DELETE FROM noise_floor WHERE timestamp < ?", (cutoff,)) noise_deleted = result.rowcount + result = conn.execute("DELETE FROM crc_errors WHERE timestamp < ?", (cutoff,)) + crc_deleted = result.rowcount + conn.commit() - if packets_deleted > 0 or adverts_deleted > 0 or noise_deleted > 0: + if packets_deleted > 0 or adverts_deleted > 0 or noise_deleted > 0 or crc_deleted > 0: logger.info( - f"Cleaned up {packets_deleted} old packets, {adverts_deleted} old adverts, {noise_deleted} old noise measurements" + f"Cleaned up {packets_deleted} old packets, {adverts_deleted} old adverts, {noise_deleted} old noise measurements, {crc_deleted} old CRC error records" ) except Exception as e: diff --git a/repeater/data_acquisition/storage_collector.py b/repeater/data_acquisition/storage_collector.py index 750b0e6..494aa3e 100644 --- a/repeater/data_acquisition/storage_collector.py +++ b/repeater/data_acquisition/storage_collector.py @@ -216,6 +216,18 @@ class StorageCollector: self.sqlite_handler.store_noise_floor(noise_record) self.mqtt_handler.publish(noise_record, "noise_floor") + def record_crc_errors(self, count: int): + """Record a batch of CRC errors detected since last poll.""" + crc_record = {"timestamp": time.time(), "count": count} + self.sqlite_handler.store_crc_errors(crc_record) + self.mqtt_handler.publish(crc_record, "crc_errors") + + def get_crc_error_count(self, hours: int = 24) -> int: + return self.sqlite_handler.get_crc_error_count(hours) + + def get_crc_error_history(self, hours: int = 24, limit: int = None) -> list: + return self.sqlite_handler.get_crc_error_history(hours, limit) + def get_packet_stats(self, hours: int = 24) -> dict: return self.sqlite_handler.get_packet_stats(hours) diff --git a/repeater/engine.py b/repeater/engine.py index 21ff1f7..b6c380e 100644 --- a/repeater/engine.py +++ b/repeater/engine.py @@ -28,6 +28,20 @@ logger = logging.getLogger("RepeaterHandler") NOISE_FLOOR_INTERVAL = 30.0 # seconds +LOOP_DETECT_OFF = "off" +LOOP_DETECT_MINIMAL = "minimal" +LOOP_DETECT_MODERATE = "moderate" +LOOP_DETECT_STRICT = "strict" + +# Thresholds for 1-byte path hashes loop detection. +# Count how many times our own hash already exists in the incoming FLOOD path. +# If occurrences >= threshold, treat as loop and drop. +LOOP_DETECT_MAX_COUNTERS = { + LOOP_DETECT_MINIMAL: 4, + LOOP_DETECT_MODERATE: 2, + LOOP_DETECT_STRICT: 1, +} + class RepeaterHandler(BaseHandler): @@ -57,6 +71,9 @@ class RepeaterHandler(BaseHandler): "send_advert_interval_hours", 10 ) self.last_advert_time = time.time() + self.loop_detect_mode = self._normalize_loop_detect_mode( + config.get("mesh", {}).get("loop_detect", LOOP_DETECT_OFF) + ) radio = dispatcher.radio if dispatcher else None if radio: @@ -97,6 +114,7 @@ class RepeaterHandler(BaseHandler): self.last_noise_measurement = time.time() self.noise_floor_interval = NOISE_FLOOR_INTERVAL # 30 seconds self._background_task = None + self._last_crc_error_count = 0 # Track radio counter for delta persistence # Cache transport keys for efficient lookup self._transport_keys_cache = None @@ -456,6 +474,34 @@ class RepeaterHandler(BaseHandler): return True, "" + def _normalize_loop_detect_mode(self, mode) -> str: + if isinstance(mode, str): + normalized = mode.strip().lower() + if normalized in { + LOOP_DETECT_OFF, + LOOP_DETECT_MINIMAL, + LOOP_DETECT_MODERATE, + LOOP_DETECT_STRICT, + }: + return normalized + return LOOP_DETECT_OFF + + def _get_loop_detect_mode(self) -> str: + return self.loop_detect_mode + + def _is_flood_looped(self, packet: Packet, mode: Optional[str] = None) -> bool: + mode = mode or self._get_loop_detect_mode() + if mode == LOOP_DETECT_OFF: + return False + + max_counter = LOOP_DETECT_MAX_COUNTERS.get(mode) + if max_counter is None: + return False + + path = packet.path or bytearray() + local_count = sum(1 for hop in path if hop == self.local_hash) + return local_count >= max_counter + def _check_transport_codes(self, packet: Packet) -> Tuple[bool, str]: if not self.storage: @@ -576,10 +622,17 @@ class RepeaterHandler(BaseHandler): packet.drop_reason = "Global flood policy disabled" return None + mode = self._get_loop_detect_mode() + if self._is_flood_looped(packet, mode): + packet.drop_reason = f"FLOOD loop detected ({mode})" + return None + # Suppress duplicates if self.is_duplicate(packet): packet.drop_reason = "Duplicate" return None + + self.mark_seen(packet) if packet.path is None: packet.path = bytearray() @@ -598,12 +651,22 @@ class RepeaterHandler(BaseHandler): packet.path.extend(self.local_hash_bytes[:hash_size]) packet.path_len = PathUtils.encode_path_len(hash_size, hop_count + 1) - self.mark_seen(packet) - return packet def direct_forward(self, packet: Packet) -> Optional[Packet]: + # Validate packet (empty payload, oversized path, etc.) + valid, reason = self.validate_packet(packet) + if not valid: + packet.drop_reason = reason + return None + + # Check if packet is marked do-not-retransmit + if packet.is_marked_do_not_retransmit(): + if not packet.drop_reason: + packet.drop_reason = "Marked do not retransmit" + return None + hash_size = packet.get_path_hash_size() hop_count = packet.get_path_hash_count() @@ -622,13 +685,13 @@ class RepeaterHandler(BaseHandler): packet.drop_reason = "Duplicate" return None + self.mark_seen(packet) + original_path = list(packet.path) # Remove first hash entry (hash_size bytes) packet.path = bytearray(packet.path[hash_size:]) packet.path_len = PathUtils.encode_path_len(hash_size, hop_count - 1) - self.mark_seen(packet) - return packet @staticmethod @@ -798,6 +861,10 @@ class RepeaterHandler(BaseHandler): # Get current noise floor from radio noise_floor_dbm = self.get_noise_floor() + # Get CRC error count from radio hardware + radio = self.dispatcher.radio if self.dispatcher else None + crc_error_count = getattr(radio, "crc_error_count", 0) if radio else 0 + # Get neighbors from database neighbors = self.storage.get_neighbors() if self.storage else {} @@ -814,6 +881,7 @@ class RepeaterHandler(BaseHandler): "neighbors": neighbors, "uptime_seconds": uptime_seconds, "noise_floor_dbm": noise_floor_dbm, + "crc_error_count": crc_error_count, # Add configuration data "config": { "node_name": repeater_config.get("node_name", "Unknown"), @@ -828,6 +896,9 @@ class RepeaterHandler(BaseHandler): "longitude": repeater_config.get("longitude", 0.0), "max_flood_hops": repeater_config.get("max_flood_hops", 3), "advert_interval_minutes": repeater_config.get("advert_interval_minutes", 120), + "advert_rate_limit": repeater_config.get("advert_rate_limit", {}), + "advert_penalty_box": repeater_config.get("advert_penalty_box", {}), + "advert_adaptive": repeater_config.get("advert_adaptive", {}), }, "radio": self.config.get( "radio", {} @@ -862,6 +933,7 @@ class RepeaterHandler(BaseHandler): # Check noise floor recording (every 30 seconds) if current_time - self.last_noise_measurement >= self.noise_floor_interval: await self._record_noise_floor_async() + await self._record_crc_errors_async() self.last_noise_measurement = current_time # Check advert sending (every N hours) @@ -900,6 +972,22 @@ class RepeaterHandler(BaseHandler): except Exception as e: logger.error(f"Error recording noise floor: {e}") + async def _record_crc_errors_async(self): + """Persist CRC error delta from the radio hardware counter.""" + if not self.storage: + return + + try: + radio = self.dispatcher.radio if self.dispatcher else None + current = getattr(radio, "crc_error_count", 0) if radio else 0 + delta = current - self._last_crc_error_count + if delta > 0: + self.storage.record_crc_errors(delta) + logger.debug(f"Recorded {delta} CRC errors (total: {current})") + self._last_crc_error_count = current + except Exception as e: + logger.error(f"Error recording CRC errors: {e}") + async def _send_periodic_advert_async(self): logger.info( f"Periodic advert timer triggered (interval: {self.send_advert_interval_hours}h)" @@ -931,6 +1019,9 @@ class RepeaterHandler(BaseHandler): self.score_threshold = repeater_config.get("score_threshold", 0.3) self.send_advert_interval_hours = repeater_config.get("send_advert_interval_hours", 10) self.cache_ttl = repeater_config.get("cache_ttl", 60) + self.loop_detect_mode = self._normalize_loop_detect_mode( + self.config.get("mesh", {}).get("loop_detect", LOOP_DETECT_OFF) + ) # Note: Radio config changes require restart as they affect hardware # Note: Airtime manager has its own config reference that gets updated diff --git a/repeater/handler_helpers/advert.py b/repeater/handler_helpers/advert.py index 3a6fc91..4c825bb 100644 --- a/repeater/handler_helpers/advert.py +++ b/repeater/handler_helpers/advert.py @@ -2,21 +2,41 @@ Advertisement packet handling helper for pyMC Repeater. This module processes advertisement packets for neighbor tracking and discovery. +Includes adaptive rate limiting based on mesh activity. """ import asyncio import logging import time +from enum import Enum +from typing import Dict, Optional, Tuple from pymc_core.node.handlers.advert import AdvertHandler logger = logging.getLogger("AdvertHelper") +class MeshActivityTier(Enum): + """Mesh activity levels for adaptive rate limiting.""" + QUIET = "quiet" + NORMAL = "normal" + BUSY = "busy" + CONGESTED = "congested" + + +# Tier multipliers for rate limit scaling +TIER_MULTIPLIERS = { + MeshActivityTier.QUIET: 0.0, # No rate limiting + MeshActivityTier.NORMAL: 0.5, # Light limiting + MeshActivityTier.BUSY: 1.0, # Standard limiting + MeshActivityTier.CONGESTED: 2.0, # Aggressive limiting +} + + class AdvertHelper: """Helper class for processing advertisement packets in the repeater.""" - def __init__(self, local_identity, storage, log_fn=None): + def __init__(self, local_identity, storage, config=None, log_fn=None): """ Initialize the advert helper. @@ -27,6 +47,7 @@ class AdvertHelper: """ self.local_identity = local_identity self.storage = storage + self.config = config or {} # Create AdvertHandler internally as a parsing utility self.advert_handler = AdvertHandler(log_fn=log_fn or logger.info) @@ -34,6 +55,421 @@ class AdvertHelper: # Cache for tracking known neighbors (avoid repeated database queries) self._known_neighbors = set() + repeater_cfg = self.config.get("repeater", {}) + + # --- Adaptive mode config --- + adaptive_cfg = repeater_cfg.get("advert_adaptive", {}) + self._adaptive_enabled = bool(adaptive_cfg.get("enabled", True)) + self._ewma_alpha = max(0.01, min(1.0, float(adaptive_cfg.get("ewma_alpha", 0.1)))) + self._tier_hysteresis_seconds = max(0.0, float(adaptive_cfg.get("hysteresis_seconds", 300.0))) + + # Tier thresholds (packets per minute) + thresholds = adaptive_cfg.get("thresholds", {}) + self._threshold_normal = float(thresholds.get("normal", 1.0)) + self._threshold_busy = float(thresholds.get("busy", 5.0)) + self._threshold_congested = float(thresholds.get("congested", 15.0)) + + # --- Base rate limit config (scaled by tier) --- + rate_cfg = repeater_cfg.get("advert_rate_limit", {}) + self._rate_limit_enabled = bool(rate_cfg.get("enabled", True)) + self._base_bucket_capacity = max(1.0, float(rate_cfg.get("bucket_capacity", 2))) + self._base_refill_tokens = max(0.1, float(rate_cfg.get("refill_tokens", 1.0))) + self._base_refill_interval = max(1.0, float(rate_cfg.get("refill_interval_seconds", 36000.0))) + self._base_min_interval = max(0.0, float(rate_cfg.get("min_interval_seconds", 3600.0))) + + # --- Penalty box config --- + penalty_cfg = repeater_cfg.get("advert_penalty_box", {}) + self._penalty_enabled = bool(penalty_cfg.get("enabled", True)) + self._penalty_violation_threshold = max(1, int(penalty_cfg.get("violation_threshold", 2))) + self._penalty_decay_seconds = max(1.0, float(penalty_cfg.get("violation_decay_seconds", 43200.0))) + self._penalty_base_seconds = max(1.0, float(penalty_cfg.get("base_penalty_seconds", 21600.0))) + self._penalty_multiplier = max(1.0, float(penalty_cfg.get("penalty_multiplier", 2.0))) + self._penalty_max_seconds = max( + self._penalty_base_seconds, + float(penalty_cfg.get("max_penalty_seconds", 86400.0)), + ) + + # --- Per-pubkey state --- + self._bucket_state: Dict[str, dict] = {} + self._penalty_until: Dict[str, float] = {} + self._violation_state: Dict[str, dict] = {} + + # --- Adaptive metrics state --- + self._adverts_ewma = 0.0 # EWMA of adverts per minute + self._packets_ewma = 0.0 # EWMA of total packets per minute + self._duplicates_ewma = 0.0 # EWMA of duplicate ratio + self._last_metrics_update = time.time() + self._metrics_window_seconds = 60.0 + self._adverts_in_window = 0 + self._packets_in_window = 0 + self._duplicates_in_window = 0 + + # Current activity tier with hysteresis + self._current_tier = MeshActivityTier.NORMAL + self._tier_since = time.time() + self._pending_tier: Optional[MeshActivityTier] = None + self._pending_tier_since = 0.0 + + # Stats counters + self._stats_adverts_allowed = 0 + self._stats_adverts_dropped = 0 + self._stats_tier_changes = 0 + + # Recent drops tracking (keep last 20) + self._recent_drops = [] + self._max_recent_drops = 20 + + # Memory management + self._last_cleanup = time.time() + self._cleanup_interval_seconds = 3600.0 # Clean up every hour + self._bucket_state_retention_seconds = 604800.0 # Keep inactive pubkeys for 7 days + self._max_tracked_pubkeys = 10000 # Hard limit on tracked pubkeys + + logger.info( + f"Advert limiter: adaptive={self._adaptive_enabled}, " + f"rate_limit={self._rate_limit_enabled}, " + f"bucket={self._base_bucket_capacity:.1f}, " + f"penalty={self._penalty_enabled}" + ) + + # ------------------------------------------------------------------------- + # Memory management + # ------------------------------------------------------------------------- + + def _cleanup_old_state(self, now: float) -> None: + """Clean up old/expired entries to prevent unbounded memory growth.""" + # 1. Remove expired penalties + expired_penalties = [pk for pk, until in self._penalty_until.items() if until < now] + for pk in expired_penalties: + del self._penalty_until[pk] + + # 2. Remove old bucket states for inactive pubkeys + inactive_pubkeys = [ + pk for pk, state in self._bucket_state.items() + if now - state.get("last_seen", 0) > self._bucket_state_retention_seconds + ] + for pk in inactive_pubkeys: + del self._bucket_state[pk] + # Also clean up related violation state + if pk in self._violation_state: + del self._violation_state[pk] + + # 3. Decay old violations based on decay time + for pk, vstate in list(self._violation_state.items()): + last_violation = vstate.get("last_violation", 0) + if now - last_violation > self._penalty_decay_seconds: + # Reset violation count after decay period + vstate["count"] = 0 + + # 4. Hard limit: if we're tracking too many pubkeys, remove oldest inactive ones + if len(self._bucket_state) > self._max_tracked_pubkeys: + # Sort by last_seen and remove oldest 10% + sorted_pubkeys = sorted( + self._bucket_state.items(), + key=lambda x: x[1].get("last_seen", 0) + ) + to_remove = int(len(sorted_pubkeys) * 0.1) + for pk, _ in sorted_pubkeys[:to_remove]: + del self._bucket_state[pk] + if pk in self._violation_state: + del self._violation_state[pk] + if pk in self._penalty_until: + del self._penalty_until[pk] + + # 5. Limit known neighbors set to prevent unbounded growth + if len(self._known_neighbors) > 1000: + # Clear the oldest half (simple approach - could be more sophisticated) + self._known_neighbors = set(list(self._known_neighbors)[500:]) + + if expired_penalties or inactive_pubkeys: + logger.debug( + f"Cleaned up {len(expired_penalties)} expired penalties, " + f"{len(inactive_pubkeys)} inactive pubkeys. " + f"Tracking: {len(self._bucket_state)} buckets, " + f"{len(self._penalty_until)} penalties, " + f"{len(self._known_neighbors)} neighbors" + ) + + # ------------------------------------------------------------------------- + # Adaptive tier calculation + # ------------------------------------------------------------------------- + + def _update_metrics_window(self, now: float, is_advert: bool = True, is_duplicate: bool = False) -> None: + """Update rolling metrics window and EWMA.""" + elapsed = now - self._last_metrics_update + + if elapsed >= self._metrics_window_seconds: + # Calculate rates for window + adverts_per_min = (self._adverts_in_window / elapsed) * 60.0 + packets_per_min = (self._packets_in_window / elapsed) * 60.0 + dup_ratio = ( + self._duplicates_in_window / max(1, self._packets_in_window) + ) + + # Update EWMA + alpha = self._ewma_alpha + self._adverts_ewma = alpha * adverts_per_min + (1 - alpha) * self._adverts_ewma + self._packets_ewma = alpha * packets_per_min + (1 - alpha) * self._packets_ewma + self._duplicates_ewma = alpha * dup_ratio + (1 - alpha) * self._duplicates_ewma + + # Reset window + self._adverts_in_window = 0 + self._packets_in_window = 0 + self._duplicates_in_window = 0 + self._last_metrics_update = now + + # Periodic cleanup + if now - self._last_cleanup >= self._cleanup_interval_seconds: + self._cleanup_old_state(now) + self._last_cleanup = now + + # Count this event + if is_advert: + self._adverts_in_window += 1 + self._packets_in_window += 1 + if is_duplicate: + self._duplicates_in_window += 1 + + def _calculate_target_tier(self) -> MeshActivityTier: + """Determine target tier based on current EWMA metrics.""" + # Combined activity score (adverts + packets weighted) + activity = self._adverts_ewma + (self._packets_ewma * 0.1) + + if activity >= self._threshold_congested: + return MeshActivityTier.CONGESTED + elif activity >= self._threshold_busy: + return MeshActivityTier.BUSY + elif activity >= self._threshold_normal: + return MeshActivityTier.NORMAL + else: + return MeshActivityTier.QUIET + + def _update_tier(self, now: float) -> None: + """Update current tier with hysteresis to prevent flapping.""" + if not self._adaptive_enabled: + return + + target = self._calculate_target_tier() + + if target == self._current_tier: + # Stable, clear pending + self._pending_tier = None + return + + if self._pending_tier != target: + # New pending tier + self._pending_tier = target + self._pending_tier_since = now + return + + # Check hysteresis + if (now - self._pending_tier_since) >= self._tier_hysteresis_seconds: + old_tier = self._current_tier + self._current_tier = target + self._tier_since = now + self._pending_tier = None + self._stats_tier_changes += 1 + logger.info(f"Mesh activity tier changed: {old_tier.value} → {target.value}") + + def get_current_tier(self) -> MeshActivityTier: + """Get current mesh activity tier.""" + return self._current_tier + + def _get_effective_limits(self) -> Tuple[float, float, float, float]: + """Get effective rate limits scaled by current tier.""" + if not self._adaptive_enabled: + return ( + self._base_bucket_capacity, + self._base_refill_tokens, + self._base_refill_interval, + self._base_min_interval, + ) + + multiplier = TIER_MULTIPLIERS.get(self._current_tier, 1.0) + + if multiplier == 0.0: + # QUIET mode: effectively disable rate limiting + return (100.0, 100.0, 1.0, 0.0) + + # Scale intervals UP (stricter) as multiplier increases + return ( + self._base_bucket_capacity, + self._base_refill_tokens, + self._base_refill_interval * multiplier, + self._base_min_interval * multiplier, + ) + + def _refill_tokens_if_needed(self, pubkey: str, now: float) -> dict: + """Refill token bucket using effective (tier-scaled) limits.""" + bucket_cap, refill_tokens, refill_interval, _ = self._get_effective_limits() + + state = self._bucket_state.get(pubkey) + if state is None: + state = { + "tokens": bucket_cap, + "last_refill": now, + "last_seen": 0.0, + } + self._bucket_state[pubkey] = state + return state + + elapsed = now - state["last_refill"] + if elapsed <= 0: + return state + + refill_steps = elapsed / refill_interval + if refill_steps > 0: + state["tokens"] = min( + bucket_cap, + state["tokens"] + (refill_steps * refill_tokens), + ) + state["last_refill"] = now + return state + + def _record_violation_and_maybe_penalize(self, pubkey: str, now: float) -> None: + if not self._penalty_enabled: + return + + state = self._violation_state.get(pubkey) + if state is None: + state = {"count": 0, "last_violation": 0.0} + self._violation_state[pubkey] = state + + if (now - state["last_violation"]) > self._penalty_decay_seconds: + state["count"] = 0 + + state["count"] += 1 + state["last_violation"] = now + + if state["count"] < self._penalty_violation_threshold: + return + + level = state["count"] - self._penalty_violation_threshold + penalty_seconds = min( + self._penalty_max_seconds, + self._penalty_base_seconds * (self._penalty_multiplier**level), + ) + new_until = now + penalty_seconds + old_until = self._penalty_until.get(pubkey, 0.0) + + if new_until > old_until: + self._penalty_until[pubkey] = new_until + logger.warning( + f"Advert penalty activated for {pubkey[:16]}... " + f"({penalty_seconds:.1f}s, violations={state['count']})" + ) + + def _allow_advert(self, pubkey: str, now: float) -> Tuple[bool, str]: + """Check if advert is allowed using adaptive tier-scaled limits.""" + # Update metrics and tier + self._update_metrics_window(now, is_advert=True) + self._update_tier(now) + + if not self._rate_limit_enabled: + self._stats_adverts_allowed += 1 + return True, "" + + # QUIET tier bypasses rate limiting + if self._adaptive_enabled and self._current_tier == MeshActivityTier.QUIET: + self._stats_adverts_allowed += 1 + return True, "" + + penalty_until = self._penalty_until.get(pubkey, 0.0) + if now < penalty_until: + remaining = penalty_until - now + self._stats_adverts_dropped += 1 + return False, f"advert penalty box active ({remaining:.1f}s remaining)" + + state = self._refill_tokens_if_needed(pubkey, now) + _, _, _, min_interval = self._get_effective_limits() + + last_seen = float(state.get("last_seen", 0.0)) + if min_interval > 0 and last_seen > 0: + since_last = now - last_seen + if since_last < min_interval: + self._record_violation_and_maybe_penalize(pubkey, now) + self._stats_adverts_dropped += 1 + return ( + False, + f"advert min-interval hit ({since_last:.2f}s < {min_interval:.2f}s)", + ) + + if state["tokens"] < 1.0: + self._record_violation_and_maybe_penalize(pubkey, now) + self._stats_adverts_dropped += 1 + return False, "advert rate limit exceeded" + + state["tokens"] -= 1.0 + state["last_seen"] = now + self._stats_adverts_allowed += 1 + return True, "" + + def record_packet_seen(self, is_duplicate: bool = False) -> None: + """Record a packet seen for metrics (called by router for non-advert packets).""" + now = time.time() + self._update_metrics_window(now, is_advert=False, is_duplicate=is_duplicate) + + def get_rate_limit_stats(self) -> dict: + """Get comprehensive rate limiting and adaptive tier statistics.""" + now = time.time() + bucket_cap, refill_tokens, refill_interval, min_interval = self._get_effective_limits() + + # Active penalties + active_penalties = { + pk[:16]: round(until - now, 1) + for pk, until in self._penalty_until.items() + if until > now + } + + # Per-pubkey bucket states + bucket_summary = {} + for pk, state in self._bucket_state.items(): + bucket_summary[pk[:16]] = { + "tokens": round(state["tokens"], 2), + "last_seen_ago": round(now - state["last_seen"], 1) if state["last_seen"] > 0 else None, + } + + return { + "adaptive": { + "enabled": self._adaptive_enabled, + "current_tier": self._current_tier.value, + "tier_since": round(now - self._tier_since, 1), + "pending_tier": self._pending_tier.value if self._pending_tier else None, + "tier_changes": self._stats_tier_changes, + }, + "metrics": { + "adverts_per_min_ewma": round(self._adverts_ewma, 2), + "packets_per_min_ewma": round(self._packets_ewma, 2), + "duplicate_ratio_ewma": round(self._duplicates_ewma, 3), + }, + "effective_limits": { + "bucket_capacity": bucket_cap, + "refill_tokens": refill_tokens, + "refill_interval_seconds": round(refill_interval, 1), + "min_interval_seconds": round(min_interval, 1), + }, + "stats": { + "adverts_allowed": self._stats_adverts_allowed, + "adverts_dropped": self._stats_adverts_dropped, + "drop_rate": round( + self._stats_adverts_dropped / max(1, self._stats_adverts_allowed + self._stats_adverts_dropped), + 3, + ), + }, + "active_penalties": active_penalties, + "tracked_pubkeys": len(self._bucket_state), + "bucket_states": bucket_summary, + "recent_drops": [ + { + "pubkey": drop["pubkey"], + "name": drop["name"], + "reason": drop["reason"], + "seconds_ago": round(now - drop["timestamp"], 1) + } + for drop in reversed(self._recent_drops) # Most recent first + ], + } + async def process_advert_packet(self, packet, rssi: int, snr: float) -> None: """ Process an incoming advertisement packet. @@ -64,6 +500,34 @@ class AdvertHelper: pubkey = advert_data["public_key"] node_name = advert_data["name"] contact_type = advert_data["contact_type"] + + # Per-pubkey rate limiting (token bucket + penalty box) + now = time.time() + allowed, reason = self._allow_advert(pubkey, now) + if not allowed: + logger.warning(f"Dropping advert from '{node_name}' ({pubkey[:16]}...): {reason}") + packet.mark_do_not_retransmit() + packet.drop_reason = reason + + # Track recent drop (deduplicate by pubkey) + pubkey_short = pubkey[:16] + + # Remove any existing entry for this pubkey + self._recent_drops = [d for d in self._recent_drops if d["pubkey"] != pubkey_short] + + # Add the new drop entry + self._recent_drops.append({ + "pubkey": pubkey_short, + "name": node_name, + "reason": reason, + "timestamp": now + }) + + # Keep only last N drops + if len(self._recent_drops) > self._max_recent_drops: + self._recent_drops.pop(0) + + return # Skip our own adverts if self.local_identity: @@ -78,7 +542,7 @@ class AdvertHelper: route_type = packet.header & PH_ROUTE_MASK # Check if this is a new neighbor (run DB read in thread to avoid blocking event loop) - current_time = time.time() + current_time = now if pubkey not in self._known_neighbors: # Only check database if not in cache if self.storage: @@ -129,3 +593,46 @@ class AdvertHelper: except Exception as e: logger.error(f"Error processing advert packet: {e}", exc_info=True) + + def reload_config(self) -> None: + """Reload rate limiting configuration from self.config (called after live config updates).""" + try: + repeater_cfg = self.config.get("repeater", {}) + + # Adaptive mode config + adaptive_cfg = repeater_cfg.get("advert_adaptive", {}) + self._adaptive_enabled = bool(adaptive_cfg.get("enabled", True)) + self._ewma_alpha = max(0.01, min(1.0, float(adaptive_cfg.get("ewma_alpha", 0.1)))) + self._tier_hysteresis_seconds = max(0.0, float(adaptive_cfg.get("hysteresis_seconds", 300.0))) + + thresholds = adaptive_cfg.get("thresholds", {}) + self._threshold_normal = float(thresholds.get("normal", 1.0)) + self._threshold_busy = float(thresholds.get("busy", 5.0)) + self._threshold_congested = float(thresholds.get("congested", 15.0)) + + # Base rate limit config + rate_cfg = repeater_cfg.get("advert_rate_limit", {}) + self._rate_limit_enabled = bool(rate_cfg.get("enabled", True)) + self._base_bucket_capacity = max(1.0, float(rate_cfg.get("bucket_capacity", 2))) + self._base_refill_tokens = max(0.1, float(rate_cfg.get("refill_tokens", 1.0))) + self._base_refill_interval = max(1.0, float(rate_cfg.get("refill_interval_seconds", 36000.0))) + self._base_min_interval = max(0.0, float(rate_cfg.get("min_interval_seconds", 3600.0))) + + # Penalty box config + penalty_cfg = repeater_cfg.get("advert_penalty_box", {}) + self._penalty_enabled = bool(penalty_cfg.get("enabled", True)) + self._penalty_violation_threshold = max(1, int(penalty_cfg.get("violation_threshold", 2))) + self._penalty_decay_seconds = max(1.0, float(penalty_cfg.get("violation_decay_seconds", 43200.0))) + self._penalty_base_seconds = max(1.0, float(penalty_cfg.get("base_penalty_seconds", 21600.0))) + self._penalty_multiplier = max(1.0, float(penalty_cfg.get("penalty_multiplier", 2.0))) + self._penalty_max_seconds = max( + self._penalty_base_seconds, + float(penalty_cfg.get("max_penalty_seconds", 86400.0)), + ) + + logger.info( + f"Advert limiter config reloaded: adaptive={self._adaptive_enabled}, " + f"rate_limit={self._rate_limit_enabled}, bucket={self._base_bucket_capacity:.1f}" + ) + except Exception as e: + logger.error(f"Error reloading advert limiter config: {e}") diff --git a/repeater/main.py b/repeater/main.py index 16d3982..3f9321f 100644 --- a/repeater/main.py +++ b/repeater/main.py @@ -163,6 +163,7 @@ class RepeaterDaemon: self.advert_helper = AdvertHelper( local_identity=self.local_identity, storage=self.repeater_handler.storage if self.repeater_handler else None, + config=self.config, log_fn=logger.info, ) logger.info("Advert processing helper initialized") @@ -931,10 +932,28 @@ class RepeaterDaemon: except Exception as e: logger.debug(f"CH341 reset skipped/failed: {e}") + @staticmethod + def _detect_container() -> bool: + """Detect if running inside an LXC/Docker/systemd-nspawn container.""" + try: + with open("/proc/1/environ", "rb") as f: + if b"container=" in f.read(): + return True + except (OSError, PermissionError): + pass + return os.path.exists("/run/host/container-manager") + async def run(self): logger.info("Repeater daemon started") + # Warn if running inside a container (udev rules won't work here) + if os.path.exists("/.dockerenv") or os.environ.get("container") or self._detect_container(): + logger.warning( + "Container environment detected. " + "USB device udev rules must be configured on the HOST, not inside this container." + ) + try: await self.initialize() diff --git a/repeater/service_utils.py b/repeater/service_utils.py index 5e465ee..14a1481 100644 --- a/repeater/service_utils.py +++ b/repeater/service_utils.py @@ -14,31 +14,62 @@ def restart_service() -> Tuple[bool, str]: """ Restart the pymc-repeater service via systemctl. - Uses polkit for authentication (requires proper polkit rules configured). - NoNewPrivileges systemd flag prevents sudo from working. + Tries polkit-based restart first (plain systemctl), then falls back + to sudo-based restart (requires sudoers.d rule installed by manage.sh). Returns: Tuple[bool, str]: (success, message) """ + # Try polkit-based restart first (works on bare metal / VMs with polkit running) try: result = subprocess.run( ["systemctl", "restart", "pymc-repeater"], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: - logger.info("Service restart command executed successfully") + logger.info("Service restart via polkit succeeded") return True, "Service restart initiated" + + stderr = result.stderr or "" + if "Access denied" in stderr or "authorization" in stderr.lower(): + logger.info("Polkit denied restart, trying sudo fallback...") else: - error_msg = result.stderr or "Unknown error" - logger.error(f"Service restart failed: {error_msg}") - return False, f"Restart failed: {error_msg}" + # Some other error, still try sudo + logger.warning(f"systemctl restart failed ({result.returncode}): {stderr.strip()}") except subprocess.TimeoutExpired: + # Timeout likely means it's restarting - that's success logger.warning("Service restart command timed out (service may be restarting)") return True, "Service restart initiated (timeout - likely restarting)" except FileNotFoundError: logger.error("systemctl not found") return False, "systemctl not available" except Exception as e: - logger.error(f"Error executing restart command: {e}") + logger.warning(f"Polkit restart attempt failed: {e}") + + # Fallback: use sudo (requires /etc/sudoers.d/pymc-repeater rule) + try: + result = subprocess.run( + ['sudo', '--non-interactive', 'systemctl', 'restart', 'pymc-repeater'], + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode == 0: + logger.info("Service restart via sudo succeeded") + return True, "Service restart initiated" + else: + error_msg = result.stderr or "Unknown error" + logger.error(f"Service restart via sudo failed: {error_msg}") + return False, f"Restart failed: {error_msg}" + + except subprocess.TimeoutExpired: + logger.warning("Sudo restart timed out (service likely restarting)") + return True, "Service restart initiated (timeout - likely restarting)" + except FileNotFoundError: + logger.error("sudo not found - cannot restart service") + return False, "Neither polkit nor sudo available for service restart" + except Exception as e: + logger.error(f"Error executing sudo restart: {e}") return False, f"Restart command failed: {str(e)}" diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index 3e1a422..dcbfc5b 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -47,6 +47,7 @@ logger = logging.getLogger("HTTPServer") # POST /api/set_duty_cycle {"enabled": true|false} - Enable/disable duty cycle # POST /api/update_duty_cycle_config {"enabled": true, "on_time": 300, "off_time": 60} - Update duty cycle config # POST /api/update_radio_config - Update radio configuration +# POST /api/update_advert_rate_limit_config - Update advert rate limiting settings # Packets # GET /api/packet_stats?hours=24 - Get packet statistics @@ -75,6 +76,7 @@ logger = logging.getLogger("HTTPServer") # Adverts & Contacts # GET /api/adverts_by_contact_type?contact_type=X&limit=100&hours=24 - Get adverts by contact type # GET /api/advert?advert_id=123 - Get specific advert +# GET /api/advert_rate_limit_stats - Get advert rate limiting and adaptive tier stats # Transport Keys # GET /api/transport_keys - List all transport keys @@ -525,8 +527,8 @@ class APIEndpoints: time.sleep(2) # Give time for response to be sent try: - # Use systemctl without sudo - polkit rules allow the repeater user to restart the service - subprocess.run(["systemctl", "restart", "pymc-repeater"], check=False) + from repeater.service_utils import restart_service + restart_service() except Exception as e: logger.error(f"Failed to restart service: {e}") @@ -728,6 +730,170 @@ class APIEndpoints: logger.error(f"Error updating duty cycle config: {e}") return self._error(str(e)) + @cherrypy.expose + @cherrypy.tools.json_out() + @cherrypy.tools.json_in() + def update_advert_rate_limit_config(self): + """Update advert rate limiting configuration using ConfigManager. + + POST /api/update_advert_rate_limit_config + Body: { + "rate_limit_enabled": true, + "bucket_capacity": 2, + "refill_tokens": 1, + "refill_interval_seconds": 36000, + "min_interval_seconds": 3600, + "penalty_enabled": true, + "violation_threshold": 2, + "violation_decay_seconds": 43200, + "base_penalty_seconds": 21600, + "penalty_multiplier": 2.0, + "max_penalty_seconds": 86400, + "adaptive_enabled": true, + "ewma_alpha": 0.1, + "hysteresis_seconds": 300, + "quiet_max": 0.05, + "normal_max": 0.20, + "busy_max": 0.50 + } + """ + self._set_cors_headers() + + if cherrypy.request.method == "OPTIONS": + return "" + + try: + self._require_post() + data = cherrypy.request.json or {} + + applied = [] + + # Ensure config sections exist + if "repeater" not in self.config: + self.config["repeater"] = {} + if "advert_rate_limit" not in self.config["repeater"]: + self.config["repeater"]["advert_rate_limit"] = {} + if "advert_penalty_box" not in self.config["repeater"]: + self.config["repeater"]["advert_penalty_box"] = {} + if "advert_adaptive" not in self.config["repeater"]: + self.config["repeater"]["advert_adaptive"] = {"thresholds": {}} + + rate_cfg = self.config["repeater"]["advert_rate_limit"] + penalty_cfg = self.config["repeater"]["advert_penalty_box"] + adaptive_cfg = self.config["repeater"]["advert_adaptive"] + + # Rate limit settings + if "rate_limit_enabled" in data: + rate_cfg["enabled"] = bool(data["rate_limit_enabled"]) + applied.append(f"rate_limit={'enabled' if rate_cfg['enabled'] else 'disabled'}") + + if "bucket_capacity" in data: + cap = max(1, int(data["bucket_capacity"])) + rate_cfg["bucket_capacity"] = cap + applied.append(f"bucket_capacity={cap}") + + if "refill_tokens" in data: + tokens = max(1, int(data["refill_tokens"])) + rate_cfg["refill_tokens"] = tokens + applied.append(f"refill_tokens={tokens}") + + if "refill_interval_seconds" in data: + interval = max(60, int(data["refill_interval_seconds"])) + rate_cfg["refill_interval_seconds"] = interval + applied.append(f"refill_interval={interval}s") + + if "min_interval_seconds" in data: + min_int = max(0, int(data["min_interval_seconds"])) + rate_cfg["min_interval_seconds"] = min_int + applied.append(f"min_interval={min_int}s") + + # Penalty box settings + if "penalty_enabled" in data: + penalty_cfg["enabled"] = bool(data["penalty_enabled"]) + applied.append(f"penalty={'enabled' if penalty_cfg['enabled'] else 'disabled'}") + + if "violation_threshold" in data: + thresh = max(1, int(data["violation_threshold"])) + penalty_cfg["violation_threshold"] = thresh + applied.append(f"violation_threshold={thresh}") + + if "violation_decay_seconds" in data: + decay = max(60, int(data["violation_decay_seconds"])) + penalty_cfg["violation_decay_seconds"] = decay + applied.append(f"violation_decay={decay}s") + + if "base_penalty_seconds" in data: + base = max(60, int(data["base_penalty_seconds"])) + penalty_cfg["base_penalty_seconds"] = base + applied.append(f"base_penalty={base}s") + + if "penalty_multiplier" in data: + mult = max(1.0, float(data["penalty_multiplier"])) + penalty_cfg["penalty_multiplier"] = mult + applied.append(f"penalty_multiplier={mult}") + + if "max_penalty_seconds" in data: + max_pen = max(60, int(data["max_penalty_seconds"])) + penalty_cfg["max_penalty_seconds"] = max_pen + applied.append(f"max_penalty={max_pen}s") + + # Adaptive settings + if "adaptive_enabled" in data: + adaptive_cfg["enabled"] = bool(data["adaptive_enabled"]) + applied.append(f"adaptive={'enabled' if adaptive_cfg['enabled'] else 'disabled'}") + + if "ewma_alpha" in data: + alpha = max(0.01, min(1.0, float(data["ewma_alpha"]))) + adaptive_cfg["ewma_alpha"] = alpha + applied.append(f"ewma_alpha={alpha}") + + if "hysteresis_seconds" in data: + hyst = max(0, int(data["hysteresis_seconds"])) + adaptive_cfg["hysteresis_seconds"] = hyst + applied.append(f"hysteresis={hyst}s") + + # Adaptive thresholds + if "thresholds" not in adaptive_cfg: + adaptive_cfg["thresholds"] = {} + + if "quiet_max" in data: + adaptive_cfg["thresholds"]["quiet_max"] = float(data["quiet_max"]) + applied.append(f"quiet_max={data['quiet_max']}") + + if "normal_max" in data: + adaptive_cfg["thresholds"]["normal_max"] = float(data["normal_max"]) + applied.append(f"normal_max={data['normal_max']}") + + if "busy_max" in data: + adaptive_cfg["thresholds"]["busy_max"] = float(data["busy_max"]) + applied.append(f"busy_max={data['busy_max']}") + + if not applied: + return self._error("No valid settings provided") + + # Save to config file and live update daemon + result = self.config_manager.update_and_save( + updates={}, + live_update=True, + live_update_sections=['repeater'] + ) + + logger.info(f"Advert rate limit config updated: {', '.join(applied)}") + + return self._success({ + "applied": applied, + "persisted": result.get("saved", False), + "live_update": result.get("live_updated", False), + "restart_required": False, + "message": "Advert rate limit settings applied immediately." + }) + + except cherrypy.HTTPError: + raise + except Exception as e: + logger.error(f"Error updating advert rate limit config: {e}") + return self._error(str(e)) + @cherrypy.expose @cherrypy.tools.json_out() def check_pymc_console(self): @@ -1511,6 +1677,40 @@ class APIEndpoints: logger.error(f"Error fetching noise floor chart data: {e}") return self._error(e) + @cherrypy.expose + @cherrypy.tools.json_out() + def crc_error_count(self, hours: int = 24): + """Return total CRC errors within the given time window.""" + try: + storage = self._get_storage() + hours = int(hours) + count = storage.get_crc_error_count(hours=hours) + return self._success({ + "crc_error_count": count, + "hours": hours + }) + except Exception as e: + logger.error(f"Error fetching CRC error count: {e}") + return self._error(e) + + @cherrypy.expose + @cherrypy.tools.json_out() + def crc_error_history(self, hours: int = 24, limit: int = None): + """Return CRC error records within the given time window.""" + try: + storage = self._get_storage() + hours = int(hours) + limit = int(limit) if limit else None + history = storage.get_crc_error_history(hours=hours, limit=limit) + return self._success({ + "history": history, + "hours": hours, + "count": len(history) + }) + except Exception as e: + logger.error(f"Error fetching CRC error history: {e}") + return self._error(e) + @cherrypy.expose def cad_calibration_stream(self): cherrypy.response.headers["Content-Type"] = "text/event-stream" @@ -1596,6 +1796,28 @@ class APIEndpoints: logger.error(f"Error getting adverts by contact type: {e}") return self._error(e) + @cherrypy.expose + @cherrypy.tools.json_out() + def advert_rate_limit_stats(self): + """Get advert rate limiting statistics and adaptive tier info.""" + try: + if not self.daemon_instance or not hasattr(self.daemon_instance, 'advert_helper'): + return self._error("Advert helper not available") + + advert_helper = self.daemon_instance.advert_helper + if not advert_helper: + return self._error("Advert helper not initialized") + + if not hasattr(advert_helper, 'get_rate_limit_stats'): + return self._error("Rate limit stats not supported by this advert helper version") + + stats = advert_helper.get_rate_limit_stats() + return self._success(stats) + + except Exception as e: + logger.error(f"Error getting advert rate limit stats: {e}") + return self._error(e) + @cherrypy.expose @cherrypy.tools.json_out() @cherrypy.tools.json_in() diff --git a/repeater/web/html/assets/CADCalibration-Dc9AglAf.js b/repeater/web/html/assets/CADCalibration-Dc9AglAf.js index 4b3b75c..10815b3 100644 --- a/repeater/web/html/assets/CADCalibration-Dc9AglAf.js +++ b/repeater/web/html/assets/CADCalibration-Dc9AglAf.js @@ -1 +1,5 @@ +<<<<<<<< HEAD:repeater/web/html/assets/CADCalibration-Dc9AglAf.js import{a as G,M as K,c as Q,r as o,o as W,P as X,b as g,e as a,g as k,i as F,t as l,k as h,n as ee,L as T,Y as te,Z as ae,p as f,x as se}from"./index-DyUIpN7m.js";import{P as M}from"./plotly.min-DO11Gp-n.js";import"./_commonjsHelpers-CqkleIqs.js";const oe={class:"p-6 space-y-6"},re={class:"glass-card rounded-[15px] p-6"},le={class:"flex justify-center"},ne={class:"flex gap-4"},ie=["disabled"],ce=["disabled"],de={class:"glass-card rounded-[15px] p-6 space-y-4"},ue={class:"text-content-primary dark:text-content-primary"},ve={key:0,class:"p-4 bg-primary/10 border border-primary/30 rounded-lg"},pe={class:"text-content-primary dark:text-primary"},me={class:"space-y-2"},be={class:"w-full bg-white/10 rounded-full h-2"},ge={class:"text-content-secondary dark:text-content-muted text-sm"},fe={class:"grid grid-cols-2 md:grid-cols-4 gap-4"},xe={class:"glass-card rounded-[15px] p-4 text-center"},ye={class:"text-2xl font-bold text-primary"},_e={class:"glass-card rounded-[15px] p-4 text-center"},ke={class:"text-2xl font-bold text-primary"},he={class:"glass-card rounded-[15px] p-4 text-center"},Ce={class:"text-2xl font-bold text-primary"},we={class:"glass-card rounded-[15px] p-4 text-center"},Re={class:"text-2xl font-bold text-primary"},Se={key:0,class:"glass-card rounded-[15px] p-6 space-y-4"},De={key:0,class:"p-4 bg-accent-green/10 border border-accent-green/30 rounded-lg"},Ae={class:"text-content-primary dark:text-content-primary mb-4"},Be={key:1,class:"p-4 bg-secondary/20 border border-secondary/40 rounded-lg"},Ee=G({name:"CADCalibrationView",__name:"CADCalibration",setup(Fe){const m=K(),I=Q(()=>document.documentElement.classList.contains("dark")),P=()=>{const e=I.value;return{title:e?"#F9FAFB":"#111827",subtitle:e?"#9CA3AF":"#6B7280",axis:e?"#D1D5DB":"#374151",tick:e?"#9CA3AF":"#6B7280",grid:e?"rgba(148, 163, 184, 0.1)":"rgba(107, 114, 128, 0.15)",zeroline:e?"rgba(148, 163, 184, 0.2)":"rgba(107, 114, 128, 0.25)",line:e?"rgba(148, 163, 184, 0.3)":"rgba(107, 114, 128, 0.35)",colorbarBorder:e?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.15)",markerLine:e?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.15)"}},u=o(!1),C=o(null),r=o(null),v=o({}),n=o(null),$=o([]),N=o({}),d=o("Ready to start calibration"),x=o(0),b=o(0),w=o(0),R=o(0),S=o(0),D=o(0),i=o(null),A=o(!1),B=o(!1),y=o(!1),_=o(!1);let c=null;const O={responsive:!0,displayModeBar:!0,modeBarButtonsToRemove:["pan2d","select2d","lasso2d","autoScale2d"],displaylogo:!1,toImageButtonOptions:{format:"png",filename:"cad-calibration-heatmap",height:600,width:800,scale:2}};function V(){const e=P(),t=[{x:[],y:[],z:[],mode:"markers",type:"scatter",marker:{size:12,color:[],colorscale:[[0,"rgba(75, 85, 99, 0.4)"],[.1,"rgba(6, 182, 212, 0.3)"],[.5,"rgba(6, 182, 212, 0.6)"],[1,"rgba(16, 185, 129, 0.9)"]],showscale:!0,colorbar:{title:{text:"Detection Rate (%)",font:{color:e.axis,size:14}},tickfont:{color:e.tick},bgcolor:"rgba(0,0,0,0)",bordercolor:e.colorbarBorder,borderwidth:1,thickness:15},line:{color:e.markerLine,width:1}},hovertemplate:"Peak: %{x}
Min: %{y}
Detection Rate: %{marker.color:.1f}%
",name:"Test Results"}],s={title:{text:`CAD Detection Rate
Channel Activity Detection Calibration`,font:{color:e.title,size:18},x:.5},xaxis:{title:{text:"CAD Peak Threshold",font:{color:e.axis,size:14}},tickfont:{color:e.tick},gridcolor:e.grid,zerolinecolor:e.zeroline,linecolor:e.line},yaxis:{title:{text:"CAD Min Threshold",font:{color:e.axis,size:14}},tickfont:{color:e.tick},gridcolor:e.grid,zerolinecolor:e.zeroline,linecolor:e.line},plot_bgcolor:"rgba(0, 0, 0, 0)",paper_bgcolor:"rgba(0, 0, 0, 0)",font:{color:e.title,family:"Inter, system-ui, sans-serif"},margin:{l:80,r:80,t:100,b:80},showlegend:!1};M.newPlot("plotly-chart",t,s,O)}function j(){if(Object.keys(v.value).length===0)return;const e=Object.values(v.value),t=[],s=[],p=[];for(const E of e)t.push(E.det_peak),s.push(E.det_min),p.push(E.detection_rate);const q={x:[t],y:[s],"marker.color":[p],hovertemplate:"Peak: %{x}
Min: %{y}
Detection Rate: %{marker.color:.1f}%
Status: Tested
"};M.restyle("plotly-chart",q,[0])}async function U(){try{const s=await T.post("/cad-calibration-start",{samples:10,delay_ms:50});if(s.success)u.value=!0,C.value=Date.now(),m.setCadCalibrationRunning(!0),v.value={},$.value=[],N.value={},n.value=null,A.value=!1,B.value=!1,y.value=!1,_.value=!1,w.value=0,R.value=0,S.value=0,D.value=0,x.value=0,b.value=0,c=setInterval(()=>{C.value&&(D.value=Math.floor((Date.now()-C.value)/1e3))},1e3),L();else throw new Error(s.error||"Failed to start calibration")}catch(s){d.value=`Error: ${s instanceof Error?s.message:"Unknown error"}`}}async function z(){try{(await T.post("/cad-calibration-stop")).success&&(u.value=!1,m.setCadCalibrationRunning(!1),r.value&&(r.value.close(),r.value=null),c&&(clearInterval(c),c=null))}catch(e){console.error("Failed to stop calibration:",e)}}function L(){r.value&&r.value.close();const e=te(),t=e?`?token=${encodeURIComponent(e)}`:"";r.value=new EventSource(`${ae}/api/cad-calibration-stream${t}`),r.value.onmessage=function(s){try{const p=JSON.parse(s.data);H(p)}catch(p){console.error("Failed to parse SSE data:",p)}},r.value.onerror=function(s){console.error("SSE connection error:",s),u.value||r.value&&(r.value.close(),r.value=null)}}function H(e){switch(e.type){case"status":d.value=e.message||"Status update",e.test_ranges&&(i.value=e.test_ranges,A.value=!0);break;case"progress":x.value=e.current||0,b.value=e.total||0,w.value=e.current||0;break;case"result":if(e.det_peak!==void 0&&e.det_min!==void 0&&e.detection_rate!==void 0&&e.detections!==void 0&&e.samples!==void 0){const t=`${e.det_peak}_${e.det_min}`;v.value[t]={det_peak:e.det_peak,det_min:e.det_min,detection_rate:e.detection_rate,detections:e.detections,samples:e.samples},j(),J()}break;case"complete":case"completed":u.value=!1,d.value=e.message||"Calibration completed",m.setCadCalibrationRunning(!1),Y(),r.value&&(r.value.close(),r.value=null),c&&(clearInterval(c),c=null);break;case"error":d.value=`Error: ${e.message}`,m.setCadCalibrationRunning(!1),z();break}}function J(){const e=Object.values(v.value).map(t=>t.detection_rate);e.length!==0&&(R.value=Math.max(...e),S.value=e.reduce((t,s)=>t+s,0)/e.length)}function Y(){B.value=!0;let e=null,t=0;for(const s of Object.values(v.value))s.detection_rate>t&&(t=s.detection_rate,e=s);n.value=e,e&&t>0?(y.value=!0,_.value=!1):(y.value=!1,_.value=!0)}async function Z(){if(!n.value){d.value="Error: No calibration results to save";return}try{const e=await T.post("/save_cad_settings",{peak:n.value.det_peak,min_val:n.value.det_min,detection_rate:n.value.detection_rate});if(e.success)d.value=`Settings saved! Peak=${n.value.det_peak}, Min=${n.value.det_min} applied to configuration.`;else throw new Error(e.error||"Failed to save settings")}catch(e){d.value=`Error: Failed to save settings: ${e instanceof Error?e.message:"Unknown error"}`}}return W(()=>{V()}),X(()=>{r.value&&r.value.close(),c&&clearInterval(c),m.setCadCalibrationRunning(!1),document.getElementById("plotly-chart")&&M.purge("plotly-chart")}),(e,t)=>(f(),g("div",oe,[t[14]||(t[14]=a("div",null,[a("h1",{class:"text-2xl font-bold text-content-primary dark:text-content-primary"},"CAD Calibration Tool"),a("p",{class:"text-content-secondary dark:text-content-muted mt-2"},"Channel Activity Detection calibration")],-1)),a("div",re,[a("div",le,[a("div",ne,[a("button",{onClick:U,disabled:u.value,class:"flex items-center gap-3 px-6 py-3 bg-accent-green/10 hover:bg-accent-green/20 disabled:bg-gray-500/10 text-accent-green disabled:text-gray-400 rounded-lg border border-accent-green/30 disabled:border-gray-500/20 transition-colors disabled:cursor-not-allowed"},t[0]||(t[0]=[F('
Start Calibration
Begin testing
',2)]),8,ie),a("button",{onClick:z,disabled:!u.value,class:"flex items-center gap-3 px-6 py-3 bg-accent-red/10 hover:bg-accent-red/20 disabled:bg-gray-500/10 text-accent-red disabled:text-gray-400 rounded-lg border border-accent-red/30 disabled:border-gray-500/20 transition-colors disabled:cursor-not-allowed"},t[1]||(t[1]=[F('
Stop
Halt calibration
',2)]),8,ce)])])]),a("div",de,[a("div",ue,l(d.value),1),A.value&&i.value?(f(),g("div",ve,[a("div",pe,[t[2]||(t[2]=a("strong",null,"Configuration:",-1)),h(" SF"+l(i.value.spreading_factor)+" | Peak: "+l(i.value.peak_min)+" - "+l(i.value.peak_max)+" | Min: "+l(i.value.min_min)+" - "+l(i.value.min_max)+" | "+l((i.value.peak_max-i.value.peak_min+1)*(i.value.min_max-i.value.min_min+1))+" tests ",1)])])):k("",!0),a("div",me,[a("div",be,[a("div",{class:"bg-gradient-to-r from-primary to-accent-green h-2 rounded-full transition-all duration-300",style:ee({width:b.value>0?`${x.value/b.value*100}%`:"0%"})},null,4)]),a("div",ge,l(x.value)+" / "+l(b.value)+" tests completed",1)])]),a("div",fe,[a("div",xe,[a("div",ye,l(w.value),1),t[3]||(t[3]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Tests Completed",-1))]),a("div",_e,[a("div",ke,l(R.value.toFixed(1))+"%",1),t[4]||(t[4]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Best Detection Rate",-1))]),a("div",he,[a("div",Ce,l(S.value.toFixed(1))+"%",1),t[5]||(t[5]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Average Rate",-1))]),a("div",we,[a("div",Re,l(D.value)+"s",1),t[6]||(t[6]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Elapsed Time",-1))])]),t[15]||(t[15]=a("div",{class:"glass-card rounded-[15px] p-6"},[a("div",{id:"plotly-chart",class:"w-full h-96"})],-1)),B.value?(f(),g("div",Se,[t[13]||(t[13]=a("h3",{class:"text-xl font-bold text-content-primary dark:text-content-primary"},"Calibration Results",-1)),y.value&&n.value?(f(),g("div",De,[t[11]||(t[11]=a("h4",{class:"font-medium text-accent-green mb-2"},"Optimal Settings Found:",-1)),a("p",Ae,[t[7]||(t[7]=h(" Peak: ",-1)),a("strong",null,l(n.value.det_peak),1),t[8]||(t[8]=h(", Min: ",-1)),a("strong",null,l(n.value.det_min),1),t[9]||(t[9]=h(", Rate: ",-1)),a("strong",null,l(n.value.detection_rate.toFixed(1))+"%",1)]),a("div",{class:"flex justify-center"},[a("button",{onClick:Z,class:"flex items-center gap-3 px-6 py-3 bg-primary/20 hover:bg-primary/30 text-primary rounded-lg border border-primary/50 transition-colors"},t[10]||(t[10]=[F('
Save Settings
Apply to configuration
',2)]))])])):k("",!0),_.value?(f(),g("div",Be,t[12]||(t[12]=[a("h4",{class:"font-medium text-secondary mb-2"},"No Optimal Settings Found",-1),a("p",{class:"text-content-secondary dark:text-content-muted"},"All tested combinations showed low detection rates. Consider running calibration again or adjusting test parameters.",-1)]))):k("",!0)])):k("",!0)]))}}),Ie=se(Ee,[["__scopeId","data-v-c30e5f38"]]);export{Ie as default}; +======== +import{a as G,M as K,c as Q,r as o,o as W,P as X,b as g,e as a,g as k,i as F,t as l,k as h,n as ee,L as T,Y as te,Z as ae,p as f,x as se}from"./index-BfUIlcDy.js";import{P as M}from"./plotly.min-DO11Gp-n.js";import"./_commonjsHelpers-CqkleIqs.js";const oe={class:"p-6 space-y-6"},re={class:"glass-card rounded-[15px] p-6"},le={class:"flex justify-center"},ne={class:"flex gap-4"},ie=["disabled"],ce=["disabled"],de={class:"glass-card rounded-[15px] p-6 space-y-4"},ue={class:"text-content-primary dark:text-content-primary"},ve={key:0,class:"p-4 bg-primary/10 border border-primary/30 rounded-lg"},pe={class:"text-content-primary dark:text-primary"},me={class:"space-y-2"},be={class:"w-full bg-white/10 rounded-full h-2"},ge={class:"text-content-secondary dark:text-content-muted text-sm"},fe={class:"grid grid-cols-2 md:grid-cols-4 gap-4"},xe={class:"glass-card rounded-[15px] p-4 text-center"},ye={class:"text-2xl font-bold text-primary"},_e={class:"glass-card rounded-[15px] p-4 text-center"},ke={class:"text-2xl font-bold text-primary"},he={class:"glass-card rounded-[15px] p-4 text-center"},Ce={class:"text-2xl font-bold text-primary"},we={class:"glass-card rounded-[15px] p-4 text-center"},Re={class:"text-2xl font-bold text-primary"},Se={key:0,class:"glass-card rounded-[15px] p-6 space-y-4"},De={key:0,class:"p-4 bg-accent-green/10 border border-accent-green/30 rounded-lg"},Ae={class:"text-content-primary dark:text-content-primary mb-4"},Be={key:1,class:"p-4 bg-secondary/20 border border-secondary/40 rounded-lg"},Ee=G({name:"CADCalibrationView",__name:"CADCalibration",setup(Fe){const m=K(),I=Q(()=>document.documentElement.classList.contains("dark")),P=()=>{const e=I.value;return{title:e?"#F9FAFB":"#111827",subtitle:e?"#9CA3AF":"#6B7280",axis:e?"#D1D5DB":"#374151",tick:e?"#9CA3AF":"#6B7280",grid:e?"rgba(148, 163, 184, 0.1)":"rgba(107, 114, 128, 0.15)",zeroline:e?"rgba(148, 163, 184, 0.2)":"rgba(107, 114, 128, 0.25)",line:e?"rgba(148, 163, 184, 0.3)":"rgba(107, 114, 128, 0.35)",colorbarBorder:e?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.15)",markerLine:e?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.15)"}},u=o(!1),C=o(null),r=o(null),v=o({}),n=o(null),$=o([]),N=o({}),d=o("Ready to start calibration"),x=o(0),b=o(0),w=o(0),R=o(0),S=o(0),D=o(0),i=o(null),A=o(!1),B=o(!1),y=o(!1),_=o(!1);let c=null;const O={responsive:!0,displayModeBar:!0,modeBarButtonsToRemove:["pan2d","select2d","lasso2d","autoScale2d"],displaylogo:!1,toImageButtonOptions:{format:"png",filename:"cad-calibration-heatmap",height:600,width:800,scale:2}};function V(){const e=P(),t=[{x:[],y:[],z:[],mode:"markers",type:"scatter",marker:{size:12,color:[],colorscale:[[0,"rgba(75, 85, 99, 0.4)"],[.1,"rgba(6, 182, 212, 0.3)"],[.5,"rgba(6, 182, 212, 0.6)"],[1,"rgba(16, 185, 129, 0.9)"]],showscale:!0,colorbar:{title:{text:"Detection Rate (%)",font:{color:e.axis,size:14}},tickfont:{color:e.tick},bgcolor:"rgba(0,0,0,0)",bordercolor:e.colorbarBorder,borderwidth:1,thickness:15},line:{color:e.markerLine,width:1}},hovertemplate:"Peak: %{x}
Min: %{y}
Detection Rate: %{marker.color:.1f}%
",name:"Test Results"}],s={title:{text:`CAD Detection Rate
Channel Activity Detection Calibration`,font:{color:e.title,size:18},x:.5},xaxis:{title:{text:"CAD Peak Threshold",font:{color:e.axis,size:14}},tickfont:{color:e.tick},gridcolor:e.grid,zerolinecolor:e.zeroline,linecolor:e.line},yaxis:{title:{text:"CAD Min Threshold",font:{color:e.axis,size:14}},tickfont:{color:e.tick},gridcolor:e.grid,zerolinecolor:e.zeroline,linecolor:e.line},plot_bgcolor:"rgba(0, 0, 0, 0)",paper_bgcolor:"rgba(0, 0, 0, 0)",font:{color:e.title,family:"Inter, system-ui, sans-serif"},margin:{l:80,r:80,t:100,b:80},showlegend:!1};M.newPlot("plotly-chart",t,s,O)}function j(){if(Object.keys(v.value).length===0)return;const e=Object.values(v.value),t=[],s=[],p=[];for(const E of e)t.push(E.det_peak),s.push(E.det_min),p.push(E.detection_rate);const q={x:[t],y:[s],"marker.color":[p],hovertemplate:"Peak: %{x}
Min: %{y}
Detection Rate: %{marker.color:.1f}%
Status: Tested
"};M.restyle("plotly-chart",q,[0])}async function U(){try{const s=await T.post("/cad-calibration-start",{samples:10,delay_ms:50});if(s.success)u.value=!0,C.value=Date.now(),m.setCadCalibrationRunning(!0),v.value={},$.value=[],N.value={},n.value=null,A.value=!1,B.value=!1,y.value=!1,_.value=!1,w.value=0,R.value=0,S.value=0,D.value=0,x.value=0,b.value=0,c=setInterval(()=>{C.value&&(D.value=Math.floor((Date.now()-C.value)/1e3))},1e3),L();else throw new Error(s.error||"Failed to start calibration")}catch(s){d.value=`Error: ${s instanceof Error?s.message:"Unknown error"}`}}async function z(){try{(await T.post("/cad-calibration-stop")).success&&(u.value=!1,m.setCadCalibrationRunning(!1),r.value&&(r.value.close(),r.value=null),c&&(clearInterval(c),c=null))}catch(e){console.error("Failed to stop calibration:",e)}}function L(){r.value&&r.value.close();const e=te(),t=e?`?token=${encodeURIComponent(e)}`:"";r.value=new EventSource(`${ae}/api/cad-calibration-stream${t}`),r.value.onmessage=function(s){try{const p=JSON.parse(s.data);H(p)}catch(p){console.error("Failed to parse SSE data:",p)}},r.value.onerror=function(s){console.error("SSE connection error:",s),u.value||r.value&&(r.value.close(),r.value=null)}}function H(e){switch(e.type){case"status":d.value=e.message||"Status update",e.test_ranges&&(i.value=e.test_ranges,A.value=!0);break;case"progress":x.value=e.current||0,b.value=e.total||0,w.value=e.current||0;break;case"result":if(e.det_peak!==void 0&&e.det_min!==void 0&&e.detection_rate!==void 0&&e.detections!==void 0&&e.samples!==void 0){const t=`${e.det_peak}_${e.det_min}`;v.value[t]={det_peak:e.det_peak,det_min:e.det_min,detection_rate:e.detection_rate,detections:e.detections,samples:e.samples},j(),J()}break;case"complete":case"completed":u.value=!1,d.value=e.message||"Calibration completed",m.setCadCalibrationRunning(!1),Y(),r.value&&(r.value.close(),r.value=null),c&&(clearInterval(c),c=null);break;case"error":d.value=`Error: ${e.message}`,m.setCadCalibrationRunning(!1),z();break}}function J(){const e=Object.values(v.value).map(t=>t.detection_rate);e.length!==0&&(R.value=Math.max(...e),S.value=e.reduce((t,s)=>t+s,0)/e.length)}function Y(){B.value=!0;let e=null,t=0;for(const s of Object.values(v.value))s.detection_rate>t&&(t=s.detection_rate,e=s);n.value=e,e&&t>0?(y.value=!0,_.value=!1):(y.value=!1,_.value=!0)}async function Z(){if(!n.value){d.value="Error: No calibration results to save";return}try{const e=await T.post("/save_cad_settings",{peak:n.value.det_peak,min_val:n.value.det_min,detection_rate:n.value.detection_rate});if(e.success)d.value=`Settings saved! Peak=${n.value.det_peak}, Min=${n.value.det_min} applied to configuration.`;else throw new Error(e.error||"Failed to save settings")}catch(e){d.value=`Error: Failed to save settings: ${e instanceof Error?e.message:"Unknown error"}`}}return W(()=>{V()}),X(()=>{r.value&&r.value.close(),c&&clearInterval(c),m.setCadCalibrationRunning(!1),document.getElementById("plotly-chart")&&M.purge("plotly-chart")}),(e,t)=>(f(),g("div",oe,[t[14]||(t[14]=a("div",null,[a("h1",{class:"text-2xl font-bold text-content-primary dark:text-content-primary"},"CAD Calibration Tool"),a("p",{class:"text-content-secondary dark:text-content-muted mt-2"},"Channel Activity Detection calibration")],-1)),a("div",re,[a("div",le,[a("div",ne,[a("button",{onClick:U,disabled:u.value,class:"flex items-center gap-3 px-6 py-3 bg-accent-green/10 hover:bg-accent-green/20 disabled:bg-gray-500/10 text-accent-green disabled:text-gray-400 rounded-lg border border-accent-green/30 disabled:border-gray-500/20 transition-colors disabled:cursor-not-allowed"},t[0]||(t[0]=[F('
Start Calibration
Begin testing
',2)]),8,ie),a("button",{onClick:z,disabled:!u.value,class:"flex items-center gap-3 px-6 py-3 bg-accent-red/10 hover:bg-accent-red/20 disabled:bg-gray-500/10 text-accent-red disabled:text-gray-400 rounded-lg border border-accent-red/30 disabled:border-gray-500/20 transition-colors disabled:cursor-not-allowed"},t[1]||(t[1]=[F('
Stop
Halt calibration
',2)]),8,ce)])])]),a("div",de,[a("div",ue,l(d.value),1),A.value&&i.value?(f(),g("div",ve,[a("div",pe,[t[2]||(t[2]=a("strong",null,"Configuration:",-1)),h(" SF"+l(i.value.spreading_factor)+" | Peak: "+l(i.value.peak_min)+" - "+l(i.value.peak_max)+" | Min: "+l(i.value.min_min)+" - "+l(i.value.min_max)+" | "+l((i.value.peak_max-i.value.peak_min+1)*(i.value.min_max-i.value.min_min+1))+" tests ",1)])])):k("",!0),a("div",me,[a("div",be,[a("div",{class:"bg-gradient-to-r from-primary to-accent-green h-2 rounded-full transition-all duration-300",style:ee({width:b.value>0?`${x.value/b.value*100}%`:"0%"})},null,4)]),a("div",ge,l(x.value)+" / "+l(b.value)+" tests completed",1)])]),a("div",fe,[a("div",xe,[a("div",ye,l(w.value),1),t[3]||(t[3]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Tests Completed",-1))]),a("div",_e,[a("div",ke,l(R.value.toFixed(1))+"%",1),t[4]||(t[4]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Best Detection Rate",-1))]),a("div",he,[a("div",Ce,l(S.value.toFixed(1))+"%",1),t[5]||(t[5]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Average Rate",-1))]),a("div",we,[a("div",Re,l(D.value)+"s",1),t[6]||(t[6]=a("div",{class:"text-content-secondary dark:text-content-muted text-sm"},"Elapsed Time",-1))])]),t[15]||(t[15]=a("div",{class:"glass-card rounded-[15px] p-6"},[a("div",{id:"plotly-chart",class:"w-full h-96"})],-1)),B.value?(f(),g("div",Se,[t[13]||(t[13]=a("h3",{class:"text-xl font-bold text-content-primary dark:text-content-primary"},"Calibration Results",-1)),y.value&&n.value?(f(),g("div",De,[t[11]||(t[11]=a("h4",{class:"font-medium text-accent-green mb-2"},"Optimal Settings Found:",-1)),a("p",Ae,[t[7]||(t[7]=h(" Peak: ",-1)),a("strong",null,l(n.value.det_peak),1),t[8]||(t[8]=h(", Min: ",-1)),a("strong",null,l(n.value.det_min),1),t[9]||(t[9]=h(", Rate: ",-1)),a("strong",null,l(n.value.detection_rate.toFixed(1))+"%",1)]),a("div",{class:"flex justify-center"},[a("button",{onClick:Z,class:"flex items-center gap-3 px-6 py-3 bg-primary/20 hover:bg-primary/30 text-primary rounded-lg border border-primary/50 transition-colors"},t[10]||(t[10]=[F('
Save Settings
Apply to configuration
',2)]))])])):k("",!0),_.value?(f(),g("div",Be,t[12]||(t[12]=[a("h4",{class:"font-medium text-secondary mb-2"},"No Optimal Settings Found",-1),a("p",{class:"text-content-secondary dark:text-content-muted"},"All tested combinations showed low detection rates. Consider running calibration again or adjusting test parameters.",-1)]))):k("",!0)])):k("",!0)]))}}),Ie=se(Ee,[["__scopeId","data-v-c30e5f38"]]);export{Ie as default}; +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/CADCalibration-DsDrJRMz.js diff --git a/repeater/web/html/assets/Configuration-CtwSeU2Z.js b/repeater/web/html/assets/Configuration-CtwSeU2Z.js new file mode 100644 index 0000000..d8f7806 --- /dev/null +++ b/repeater/web/html/assets/Configuration-CtwSeU2Z.js @@ -0,0 +1,2 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/leaflet-src-BtisrQHC.js","assets/_commonjsHelpers-CqkleIqs.js"])))=>i.map(i=>d[i]); +import{a as G,M as re,c as B,r as m,D as te,b as r,g as L,e,t as c,F as K,w as S,v as V,h as Z,q as oe,k as U,L as q,p as o,P as we,s as Y,E as ie,S as me,x as pe,f as O,y as de,d as _e,U as ce,j as D,l as ve,N as xe,V as be,T as Ce,i as Q,W as ne,o as ae,u as J,X as $e,Q as ee}from"./index-BfUIlcDy.js";/* empty css */import{_ as Me}from"./ConfirmDialog.vue_vue_type_script_setup_true_lang-K0cwbhb6.js";import{g as Ae,s as Se}from"./preferences-DtwbSSgO.js";const je={class:"space-y-4"},Te={key:0,class:"bg-green-100 dark:bg-green-500/20 border border-green-500/50 rounded-lg p-3"},Ne={class:"text-green-600 dark:text-green-400 text-sm"},Be={key:1,class:"bg-red-100 dark:bg-red-500/20 border border-red-500/50 rounded-lg p-3"},Ee={class:"text-red-600 dark:text-red-400 text-sm"},Le={class:"flex justify-end gap-2"},Fe=["disabled"],Pe=["disabled"],ze={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},Ve={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},De={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Ie={key:1,class:"flex items-center gap-2"},He={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Ue={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Re={key:1},Ke=["value"],Oe={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},qe={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},We={key:1},Ge=["value"],Qe={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Ye={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Xe={key:1,class:"flex items-center gap-2"},Je={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Ze={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},et={key:1},tt={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 gap-1"},ot={class:"text-content-primary dark:text-content-primary font-mono text-sm"},rt={key:2,class:"bg-yellow-500/10 dark:bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-3"},st=G({__name:"RadioSettings",setup(I){const _=re(),d=B(()=>_.stats?.config?.radio||{}),v=m(!1),k=m(!1),i=m(null),u=m(null),s=m(0),p=m(0),y=m(0),h=m(0),N=m(0),M=m(0),$=[{value:7.8,label:"7.8 kHz"},{value:10.4,label:"10.4 kHz"},{value:15.6,label:"15.6 kHz"},{value:20.8,label:"20.8 kHz"},{value:31.25,label:"31.25 kHz"},{value:41.7,label:"41.7 kHz"},{value:62.5,label:"62.5 kHz"},{value:125,label:"125 kHz"},{value:250,label:"250 kHz"},{value:500,label:"500 kHz"}];te(d,C=>{C&&!v.value&&(s.value=C.frequency?Number((C.frequency/1e6).toFixed(3)):0,p.value=C.spreading_factor??0,y.value=C.bandwidth?Number((C.bandwidth/1e3).toFixed(1)):0,h.value=C.tx_power??0,N.value=C.coding_rate??0,M.value=C.preamble_length??0)},{immediate:!0});const n=B(()=>{const C=d.value.frequency;return C?(C/1e6).toFixed(3)+" MHz":"Not set"}),t=B(()=>{const C=d.value.bandwidth;return C?(C/1e3).toFixed(1)+" kHz":"Not set"}),a=B(()=>{const C=d.value.tx_power;return C!==void 0?C+" dBm":"Not set"}),A=B(()=>{const C=d.value.coding_rate;return C?"4/"+C:"Not set"}),b=B(()=>{const C=d.value.preamble_length;return C?C+" symbols":"Not set"}),x=B(()=>d.value.spreading_factor??"Not set"),g=()=>{v.value=!0,i.value=null,u.value=null},z=()=>{v.value=!1,i.value=null;const C=d.value;s.value=C.frequency?Number((C.frequency/1e6).toFixed(3)):0,p.value=C.spreading_factor??0,y.value=C.bandwidth?Number((C.bandwidth/1e3).toFixed(1)):0,h.value=C.tx_power??0,N.value=C.coding_rate??0,M.value=C.preamble_length??0},W=async()=>{k.value=!0,i.value=null,u.value=null;try{const C={};s.value&&(C.frequency=s.value*1e6),p.value&&(C.spreading_factor=p.value),y.value&&(C.bandwidth=y.value*1e3),h.value&&(C.tx_power=h.value),N.value&&(C.coding_rate=N.value);const P=(await q.post("/update_radio_config",C)).data;P.message||P.persisted?(u.value=P.message||"Settings saved successfully",v.value=!1,await _.fetchStats(),setTimeout(()=>{u.value=null},3e3)):P.error?i.value=P.error:i.value="Unknown response from server"}catch(C){console.error("Failed to update radio settings:",C);const j=C;i.value=j.response?.data?.error||"Failed to update settings"}finally{k.value=!1}};return(C,j)=>(o(),r("div",je,[u.value?(o(),r("div",Te,[e("p",Ne,c(u.value),1)])):L("",!0),i.value?(o(),r("div",Be,[e("p",Ee,c(i.value),1)])):L("",!0),e("div",Le,[v.value?(o(),r(K,{key:1},[e("button",{onClick:z,disabled:k.value,class:"px-3 sm:px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"}," Cancel ",8,Fe),e("button",{onClick:W,disabled:k.value,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"},c(k.value?"Saving...":"Save Changes"),9,Pe)],64)):(o(),r("button",{key:0,onClick:g,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm"}," Edit Settings "))]),e("div",ze,[e("div",Ve,[j[6]||(j[6]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Frequency",-1)),v.value?(o(),r("div",Ie,[S(e("input",{"onUpdate:modelValue":j[0]||(j[0]=P=>s.value=P),type:"number",step:"0.001",min:"100",max:"1000",class:"w-32 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,s.value,void 0,{number:!0}]]),j[5]||(j[5]=e("span",{class:"text-content-muted dark:text-content-muted text-sm"},"MHz",-1))])):(o(),r("div",De,c(n.value),1))]),e("div",He,[j[7]||(j[7]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Spreading Factor",-1)),v.value?(o(),r("div",Re,[S(e("select",{"onUpdate:modelValue":j[1]||(j[1]=P=>p.value=P),class:"px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},[(o(),r(K,null,Z([5,6,7,8,9,10,11,12],P=>e("option",{key:P,value:P},c(P),9,Ke)),64))],512),[[oe,p.value,void 0,{number:!0}]])])):(o(),r("div",Ue,c(x.value),1))]),e("div",Oe,[j[8]||(j[8]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Bandwidth",-1)),v.value?(o(),r("div",We,[S(e("select",{"onUpdate:modelValue":j[2]||(j[2]=P=>y.value=P),class:"px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},[(o(),r(K,null,Z($,P=>e("option",{key:P.value,value:P.value},c(P.label),9,Ge)),64))],512),[[oe,y.value,void 0,{number:!0}]])])):(o(),r("div",qe,c(t.value),1))]),e("div",Qe,[j[10]||(j[10]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"TX Power",-1)),v.value?(o(),r("div",Xe,[S(e("input",{"onUpdate:modelValue":j[3]||(j[3]=P=>h.value=P),type:"number",min:"2",max:"30",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,h.value,void 0,{number:!0}]]),j[9]||(j[9]=e("span",{class:"text-content-muted dark:text-content-muted text-sm"},"dBm",-1))])):(o(),r("div",Ye,c(a.value),1))]),e("div",Je,[j[12]||(j[12]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Coding Rate",-1)),v.value?(o(),r("div",et,[S(e("select",{"onUpdate:modelValue":j[4]||(j[4]=P=>N.value=P),class:"px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},j[11]||(j[11]=[e("option",{value:5},"4/5",-1),e("option",{value:6},"4/6",-1),e("option",{value:7},"4/7",-1),e("option",{value:8},"4/8",-1)]),512),[[oe,N.value,void 0,{number:!0}]])])):(o(),r("div",Ze,c(A.value),1))]),e("div",tt,[j[13]||(j[13]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Preamble Length",-1)),e("span",ot,c(b.value),1)])]),v.value?(o(),r("div",rt,j[14]||(j[14]=[e("p",{class:"text-yellow-700 dark:text-yellow-400 text-xs"},[e("strong",null,"Note:"),U(" Radio hardware changes (frequency, bandwidth, spreading factor, coding rate) may require a service restart to apply. ")],-1)]))):L("",!0)]))}}),nt={class:"glass-card border border-stroke-subtle dark:border-white/20 rounded-[15px] w-full max-w-3xl max-h-[90vh] flex flex-col shadow-2xl"},at={class:"flex-1 relative min-h-[400px]"},lt={class:"p-6 border-t border-stroke-subtle dark:border-stroke/10 space-y-4"},dt={class:"grid grid-cols-2 gap-4"},it=G({__name:"LocationPicker",props:{isOpen:{type:Boolean},latitude:{},longitude:{}},emits:["close","select"],setup(I,{emit:_}){const d=I,v=_,k=m(null),i=m(d.latitude||0),u=m(d.longitude||0);let s=null,p=null;const y=async()=>{if(k.value){h();try{const n=(await me(async()=>{const{default:b}=await import("./leaflet-src-BtisrQHC.js").then(x=>x.l);return{default:b}},__vite__mapDeps([0,1]))).default;delete n.Icon.Default.prototype._getIconUrl,n.Icon.Default.mergeOptions({iconRetinaUrl:"https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png",iconUrl:"https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png",shadowUrl:"https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png"}),await ie();const t=i.value||0,a=u.value||0,A=t===0&&a===0?2:13;s=n.map(k.value).setView([t,a],A);try{const b=n.tileLayer("https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",{maxZoom:19,attribution:'© OpenStreetMap contributors © CARTO',errorTileUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="}),x=n.tileLayer("https://{s}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png",{maxZoom:19,attribution:"",errorTileUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="});b.addTo(s),x.addTo(s)}catch(b){console.warn("Error loading tiles:",b)}(t!==0||a!==0)&&(p=n.marker([t,a]).addTo(s)),s.on("click",b=>{i.value=b.latlng.lat,u.value=b.latlng.lng,p?p.setLatLng(b.latlng):p=n.marker(b.latlng).addTo(s)}),setTimeout(()=>{s?.invalidateSize()},200)}catch(n){console.error("Failed to initialize map:",n)}}},h=()=>{s&&(s.remove(),s=null,p=null)};te(()=>d.isOpen,async n=>{n?(await ie(),await y()):h()}),te(()=>[d.latitude,d.longitude],([n,t])=>{i.value=n,u.value=t});const N=()=>{v("select",{latitude:i.value,longitude:u.value}),v("close")},M=()=>{v("close")},$=()=>{navigator.geolocation?navigator.geolocation.getCurrentPosition(async n=>{if(i.value=n.coords.latitude,u.value=n.coords.longitude,s){s.setView([i.value,u.value],13);const t=(await me(async()=>{const{default:a}=await import("./leaflet-src-BtisrQHC.js").then(A=>A.l);return{default:a}},__vite__mapDeps([0,1]))).default;p?p.setLatLng([i.value,u.value]):p=t.marker([i.value,u.value]).addTo(s)}},n=>{console.error("Error getting location:",n),alert("Unable to get current location. Please check browser permissions.")}):alert("Geolocation is not supported by this browser.")};return we(()=>{h()}),(n,t)=>n.isOpen?(o(),r("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm",onClick:Y(M,["self"])},[e("div",nt,[e("div",{class:"flex items-center justify-between p-6 border-b border-stroke-subtle dark:border-stroke/10"},[t[3]||(t[3]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary"},"Select Location",-1)),e("button",{onClick:M,class:"text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors"},t[2]||(t[2]=[e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),e("div",at,[e("div",{ref_key:"mapContainer",ref:k,class:"absolute inset-0 rounded-b-[15px] overflow-hidden"},null,512)]),e("div",lt,[e("div",dt,[e("div",null,[t[4]||(t[4]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-muted mb-2"},"Latitude",-1)),S(e("input",{"onUpdate:modelValue":t[0]||(t[0]=a=>i.value=a),type:"number",step:"0.000001",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary focus:outline-none focus:border-primary",readonly:""},null,512),[[V,i.value,void 0,{number:!0}]])]),e("div",null,[t[5]||(t[5]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-muted mb-2"},"Longitude",-1)),S(e("input",{"onUpdate:modelValue":t[1]||(t[1]=a=>u.value=a),type:"number",step:"0.000001",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary focus:outline-none focus:border-primary",readonly:""},null,512),[[V,u.value,void 0,{number:!0}]])])]),e("div",{class:"flex gap-3"},[e("button",{onClick:$,class:"flex-1 px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors text-sm flex items-center justify-center gap-2"},t[6]||(t[6]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"}),e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 11a3 3 0 11-6 0 3 3 0 016 0z"})],-1),U(" Use Current Location ",-1)])),e("button",{onClick:M,class:"px-6 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors text-sm"}," Cancel "),e("button",{onClick:N,class:"px-6 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm"}," Select Location ")]),t[7]||(t[7]=e("p",{class:"text-content-muted dark:text-content-muted text-xs text-center"},"Click on the map to select a location",-1))])])])):L("",!0)}}),ct=pe(it,[["__scopeId","data-v-186d3c86"]]),ut={class:"space-y-4"},mt={key:0,class:"bg-green-100 dark:bg-green-500/10 border border-green-300 dark:border-green-500/30 rounded-lg p-3"},pt={class:"text-green-700 dark:text-green-400 text-sm"},vt={key:1,class:"bg-red-100 dark:bg-red-500/10 border border-red-300 dark:border-red-500/30 rounded-lg p-3"},xt={class:"text-red-700 dark:text-red-400 text-sm"},bt={class:"flex justify-end gap-2"},kt=["disabled"],gt=["disabled"],yt={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},ft={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},ht={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm break-all"},wt={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},_t={class:"text-content-primary dark:text-content-primary font-mono text-xs break-all"},Ct={class:"flex flex-col sm:flex-row sm:justify-between sm:items-start py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},$t={class:"text-content-primary dark:text-content-primary font-mono text-xs break-all sm:text-right sm:max-w-xs"},Mt={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},At={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},St={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},jt={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Tt={key:0,class:"flex justify-end"},Nt={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Bt={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Et={class:"flex flex-col py-2 gap-2"},Lt={class:"flex flex-col sm:flex-row sm:justify-between sm:items-start gap-1"},Ft={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm sm:ml-4"},Pt={key:1,class:"flex items-center gap-2"},zt=G({__name:"RepeaterSettings",setup(I){const _=re(),d=B(()=>_.stats?.config||{}),v=B(()=>d.value.repeater||{}),k=B(()=>_.stats),i=m(!1),u=m(!1),s=m(null),p=m(null),y=m(!1),h=m(""),N=m(0),M=m(0),$=m(0);te([d,v],()=>{i.value||(h.value=d.value.node_name||"",N.value=v.value.latitude||0,M.value=v.value.longitude||0,$.value=v.value.send_advert_interval_hours||0)},{immediate:!0});const n=B(()=>d.value.node_name||"Not set"),t=B(()=>k.value?.local_hash||"Not available"),a=B(()=>{const F=k.value?.public_key;return!F||F==="Not set"?"Not set":F}),A=B(()=>{const F=v.value.latitude;return F&&F!==0?F.toFixed(6):"Not set"}),b=B(()=>{const F=v.value.longitude;return F&&F!==0?F.toFixed(6):"Not set"}),x=B(()=>{const F=v.value.mode;return F?F.charAt(0).toUpperCase()+F.slice(1):"Not set"}),g=B(()=>{const F=v.value.send_advert_interval_hours;return F===void 0?"Not set":F===0?"Disabled":F+" hour"+(F!==1?"s":"")}),z=()=>{i.value=!0,s.value=null,p.value=null},W=()=>{i.value=!1,s.value=null,h.value=d.value.node_name||"",N.value=v.value.latitude||0,M.value=v.value.longitude||0,$.value=v.value.send_advert_interval_hours||0},C=async()=>{u.value=!0,s.value=null,p.value=null;try{const F={};h.value&&(F.node_name=h.value),F.latitude=N.value,F.longitude=M.value,F.flood_advert_interval_hours=$.value;const w=(await q.post("/update_radio_config",F)).data;w.message||w.persisted?(p.value=w.message||"Settings saved successfully",i.value=!1,await _.fetchStats(),setTimeout(()=>{p.value=null},3e3)):w.error?s.value=w.error:s.value="Unknown response from server"}catch(F){console.error("Failed to update repeater settings:",F);const E=F;s.value=E.response?.data?.error||"Failed to update settings"}finally{u.value=!1}},j=()=>{y.value=!0},P=F=>{N.value=F.latitude,M.value=F.longitude};return(F,E)=>(o(),r("div",ut,[p.value?(o(),r("div",mt,[e("p",pt,c(p.value),1)])):L("",!0),s.value?(o(),r("div",vt,[e("p",xt,c(s.value),1)])):L("",!0),e("div",bt,[i.value?(o(),r(K,{key:1},[e("button",{onClick:W,disabled:u.value,class:"px-3 sm:px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"}," Cancel ",8,kt),e("button",{onClick:C,disabled:u.value,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"},c(u.value?"Saving...":"Save Changes"),9,gt)],64)):(o(),r("button",{key:0,onClick:z,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm"}," Edit Settings "))]),e("div",yt,[e("div",ft,[E[5]||(E[5]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Node Name",-1)),i.value?S((o(),r("input",{key:1,"onUpdate:modelValue":E[0]||(E[0]=w=>h.value=w),type:"text",maxlength:"50",class:"w-full sm:w-64 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary",placeholder:"Enter node name"},null,512)),[[V,h.value]]):(o(),r("div",ht,c(n.value),1))]),e("div",wt,[E[6]||(E[6]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Local Hash",-1)),e("span",_t,c(t.value),1)]),e("div",Ct,[E[7]||(E[7]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm flex-shrink-0"},"Public Key",-1)),e("span",$t,c(a.value),1)]),e("div",Mt,[E[8]||(E[8]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Latitude",-1)),i.value?S((o(),r("input",{key:1,"onUpdate:modelValue":E[1]||(E[1]=w=>N.value=w),type:"number",step:"0.000001",min:"-90",max:"90",class:"w-full sm:w-48 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512)),[[V,N.value,void 0,{number:!0}]]):(o(),r("div",At,c(A.value),1))]),e("div",St,[E[9]||(E[9]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Longitude",-1)),i.value?S((o(),r("input",{key:1,"onUpdate:modelValue":E[2]||(E[2]=w=>M.value=w),type:"number",step:"0.000001",min:"-180",max:"180",class:"w-full sm:w-48 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512)),[[V,M.value,void 0,{number:!0}]]):(o(),r("div",jt,c(b.value),1))]),i.value?(o(),r("div",Tt,[e("button",{onClick:j,class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm flex items-center gap-2",title:"Pick location on map"},E[10]||(E[10]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"}),e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 11a3 3 0 11-6 0 3 3 0 016 0z"})],-1),U(" Pick Location on Map ",-1)]))])):L("",!0),e("div",Nt,[E[11]||(E[11]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Mode",-1)),e("span",Bt,c(x.value),1)]),e("div",Et,[e("div",Lt,[E[13]||(E[13]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Periodic Advertisement Interval",-1)),i.value?(o(),r("div",Pt,[S(e("input",{"onUpdate:modelValue":E[3]||(E[3]=w=>$.value=w),type:"number",min:"0",max:"48",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,$.value,void 0,{number:!0}]]),E[12]||(E[12]=e("span",{class:"text-content-muted dark:text-content-muted text-sm"},"hours",-1))])):(o(),r("div",Ft,c(g.value),1))]),E[14]||(E[14]=e("span",{class:"text-content-muted dark:text-content-muted text-xs"},"How often the repeater sends an advertisement packet (0 = disabled, 3-48 hours)",-1))])]),O(ct,{"is-open":y.value,latitude:N.value,longitude:M.value,onClose:E[4]||(E[4]=w=>y.value=!1),onSelect:P},null,8,["is-open","latitude","longitude"])]))}}),Vt={class:"space-y-4"},Dt={key:0,class:"bg-green-100 dark:bg-green-500/20 border border-green-500 dark:border-green-500/50 rounded-lg p-3 text-green-700 dark:text-green-400 text-sm"},It={key:1,class:"bg-red-100 dark:bg-red-500/20 border border-red-500 dark:border-red-500/50 rounded-lg p-3 text-red-700 dark:text-red-400 text-sm"},Ht={class:"flex justify-end gap-2"},Ut=["disabled"],Rt=["disabled"],Kt={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},Ot={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},qt={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Wt={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 gap-1"},Gt={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Qt=G({__name:"DutyCycle",setup(I){const _=re(),d=B(()=>_.stats?.config?.duty_cycle||{}),v=B(()=>{const n=d.value.max_airtime_percent;return typeof n=="number"?n.toFixed(1)+"%":n&&typeof n=="object"&&"parsedValue"in n?(n.parsedValue||0).toFixed(1)+"%":"Not set"}),k=B(()=>d.value.enforcement_enabled?"Enabled":"Disabled"),i=m(!1),u=m(!1),s=m(""),p=m(""),y=m(0),h=m(!0),N=()=>{const n=d.value.max_airtime_percent;typeof n=="number"?y.value=n:n&&typeof n=="object"&&"parsedValue"in n?y.value=n.parsedValue||0:y.value=6,h.value=d.value.enforcement_enabled!==!1,i.value=!0,s.value="",p.value=""},M=()=>{i.value=!1,s.value="",p.value=""},$=async()=>{u.value=!0,p.value="",s.value="";try{const t=(await de.post("/api/update_duty_cycle_config",{max_airtime_percent:y.value,enforcement_enabled:h.value})).data;t.message||t.persisted?(s.value=t.message||"Settings saved successfully",i.value=!1,await _.fetchStats(),setTimeout(()=>{s.value=""},3e3)):p.value="Failed to save settings"}catch(n){console.error("Failed to save duty cycle settings:",n),p.value=n.response?.data?.error||"Failed to save settings"}finally{u.value=!1}};return(n,t)=>(o(),r("div",Vt,[s.value?(o(),r("div",Dt,c(s.value),1)):L("",!0),p.value?(o(),r("div",It,c(p.value),1)):L("",!0),e("div",Ht,[i.value?(o(),r(K,{key:1},[e("button",{onClick:M,disabled:u.value,class:"px-3 sm:px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"}," Cancel ",8,Ut),e("button",{onClick:$,disabled:u.value,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"},c(u.value?"Saving...":"Save Changes"),9,Rt)],64)):(o(),r("button",{key:0,onClick:N,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm"}," Edit Settings "))]),e("div",Kt,[e("div",Ot,[t[2]||(t[2]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Max Airtime %",-1)),i.value?S((o(),r("input",{key:1,"onUpdate:modelValue":t[0]||(t[0]=a=>y.value=a),type:"number",step:"0.1",min:"0.1",max:"100",class:"w-full sm:w-32 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512)),[[V,y.value,void 0,{number:!0}]]):(o(),r("div",qt,c(v.value),1))]),e("div",Wt,[t[4]||(t[4]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Enforcement",-1)),i.value?S((o(),r("select",{key:1,"onUpdate:modelValue":t[1]||(t[1]=a=>h.value=a),class:"w-full sm:w-32 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},t[3]||(t[3]=[e("option",{value:!0},"Enabled",-1),e("option",{value:!1},"Disabled",-1)]),512)),[[oe,h.value]]):(o(),r("div",Gt,c(k.value),1))])])]))}}),Yt={class:"space-y-4"},Xt={key:0,class:"bg-green-100 dark:bg-green-500/20 border border-green-500 dark:border-green-500/50 rounded-lg p-3 text-green-700 dark:text-green-400 text-sm"},Jt={key:1,class:"bg-red-100 dark:bg-red-500/20 border border-red-500 dark:border-red-500/50 rounded-lg p-3 text-red-700 dark:text-red-400 text-sm"},Zt={class:"flex justify-end gap-2"},eo=["disabled"],to=["disabled"],oo={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},ro={class:"flex flex-col py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-2"},so={class:"flex flex-col sm:flex-row sm:justify-between sm:items-start gap-1"},no={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm sm:ml-4"},ao={class:"flex flex-col py-2 gap-2"},lo={class:"flex flex-col sm:flex-row sm:justify-between sm:items-start gap-1"},io={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm sm:ml-4"},co=G({__name:"TransmissionDelays",setup(I){const _=re(),d=B(()=>_.stats?.config?.delays||{}),v=B(()=>{const n=d.value.tx_delay_factor;if(n&&typeof n=="object"&&n!==null&&"parsedValue"in n){const t=n.parsedValue;if(typeof t=="number")return t.toFixed(2)+"x"}return"Not set"}),k=B(()=>{const n=d.value.direct_tx_delay_factor;return typeof n=="number"?n.toFixed(2)+"s":"Not set"}),i=m(!1),u=m(!1),s=m(""),p=m(""),y=m(0),h=m(0),N=()=>{const n=d.value.tx_delay_factor;n&&typeof n=="object"&&"parsedValue"in n?y.value=n.parsedValue||1:typeof n=="number"?y.value=n:y.value=1;const t=d.value.direct_tx_delay_factor;h.value=typeof t=="number"?t:.5,i.value=!0,s.value="",p.value=""},M=()=>{i.value=!1,s.value="",p.value=""},$=async()=>{u.value=!0,p.value="",s.value="";try{const t=(await de.post("/api/update_radio_config",{tx_delay_factor:y.value,direct_tx_delay_factor:h.value})).data;t.message||t.persisted?(s.value=t.message||"Settings saved successfully",i.value=!1,await _.fetchStats(),setTimeout(()=>{s.value=""},3e3)):p.value="Failed to save settings"}catch(n){console.error("Failed to save delay settings:",n),p.value=n.response?.data?.error||"Failed to save settings"}finally{u.value=!1}};return(n,t)=>(o(),r("div",Yt,[s.value?(o(),r("div",Xt,c(s.value),1)):L("",!0),p.value?(o(),r("div",Jt,c(p.value),1)):L("",!0),e("div",Zt,[i.value?(o(),r(K,{key:1},[e("button",{onClick:M,disabled:u.value,class:"px-3 sm:px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"}," Cancel ",8,eo),e("button",{onClick:$,disabled:u.value,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"},c(u.value?"Saving...":"Save Changes"),9,to)],64)):(o(),r("button",{key:0,onClick:N,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm"}," Edit Settings "))]),e("div",oo,[e("div",ro,[e("div",so,[t[2]||(t[2]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Flood TX Delay Factor",-1)),i.value?S((o(),r("input",{key:1,"onUpdate:modelValue":t[0]||(t[0]=a=>y.value=a),type:"number",step:"0.1",min:"0",max:"5",class:"w-full sm:w-32 px-3 py-1.5 bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512)),[[V,y.value,void 0,{number:!0}]]):(o(),r("div",no,c(v.value),1))]),t[3]||(t[3]=e("span",{class:"text-content-muted dark:text-content-muted text-xs"},"Multiplier for flood packet transmission delays (collision avoidance)",-1))]),e("div",ao,[e("div",lo,[t[4]||(t[4]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Direct TX Delay Factor",-1)),i.value?S((o(),r("input",{key:1,"onUpdate:modelValue":t[1]||(t[1]=a=>h.value=a),type:"number",step:"0.1",min:"0",max:"5",class:"w-full sm:w-32 px-3 py-1.5 bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512)),[[V,h.value,void 0,{number:!0}]]):(o(),r("div",io,c(k.value),1))]),t[5]||(t[5]=e("span",{class:"text-content-muted dark:text-content-muted text-xs"},"Base delay for direct-routed packet transmission (seconds)",-1))])])]))}}),ke=_e("treeState",()=>{const I=ce(new Set),_=ce({value:null}),d=s=>{I.add(s)},v=s=>{I.delete(s)};return{expandedNodes:I,selectedNodeId:_,addExpandedNode:d,removeExpandedNode:v,isNodeExpanded:s=>I.has(s),setSelectedNode:s=>{_.value=s},toggleExpanded:s=>{I.has(s)?v(s):d(s)}}}),uo={class:"select-none"},mo={class:"flex-shrink-0"},po={key:0,class:"w-3.5 h-3.5 sm:w-4 sm:h-4 text-secondary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},vo={key:1,class:"w-3.5 h-3.5 sm:w-4 sm:h-4 text-accent-green",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},xo={key:0,class:"hidden sm:flex items-center gap-1 ml-2"},bo={class:"relative group"},ko=["title"],go={key:0,class:"text-xs font-mono text-white/50 bg-white/5 px-1.5 py-0.5 rounded border border-white/10"},yo={class:"flex justify-between items-start mb-4"},fo={class:"bg-black/20 border border-white/10 rounded-md p-4 mb-4"},ho={class:"text-sm font-mono text-white/80 break-all leading-relaxed"},wo={class:"flex items-center gap-1 sm:gap-2 ml-auto flex-shrink-0"},_o={key:0,class:"hidden sm:flex items-center gap-1"},Co=["title"],$o={key:1,class:"hidden sm:flex items-center gap-1"},Mo={key:2,class:"hidden sm:inline-block px-2 py-1 bg-white/10 text-white/60 text-xs rounded-full ml-1"},Ao={key:0,class:"space-y-1"},So=G({__name:"TreeNode",props:{node:{},selectedNodeId:{},level:{},disabled:{type:Boolean}},emits:["select"],setup(I,{emit:_}){const d=I,v=_,k=ke(),i=m(!1),u=B({get:()=>k.isNodeExpanded(d.node.id),set:t=>{t?k.addExpandedNode(d.node.id):k.removeExpandedNode(d.node.id)}}),s=B(()=>d.node.children.length>0);function p(t){if(!t)return"Never";const A=new Date().getTime()-t.getTime(),b=Math.floor(A/(1e3*60)),x=Math.floor(A/(1e3*60*60)),g=Math.floor(A/(1e3*60*60*24)),z=Math.floor(g/365);return b<60?`${b}m ago`:x<24?`${x}h ago`:g<365?`${g}d ago`:`${z}y ago`}function y(t){return t?t.length<=16?t:`${t.slice(0,8)}...${t.slice(-8)}`:"No key"}function h(){if(s.value){const t=!u.value;u.value=t}}function N(){v("select",d.node.id)}function M(t){v("select",t)}function $(t){t.stopPropagation(),i.value=!i.value}function n(t){t.stopPropagation(),d.node.transport_key&&window.navigator?.clipboard&&window.navigator.clipboard.writeText(d.node.transport_key)}return(t,a)=>{const A=be("TreeNode",!0);return o(),r("div",uo,[e("div",{class:D(["flex flex-wrap sm:flex-nowrap items-start sm:items-center gap-1 sm:gap-2 py-2 px-2 sm:px-3 rounded-lg cursor-pointer transition-all duration-200",d.disabled?"opacity-50 cursor-not-allowed":"hover:bg-white/5",t.selectedNodeId===t.node.id&&!d.disabled?"bg-primary/20 text-primary":"text-white/80 hover:text-white",`ml-${t.level*4}`]),onClick:a[3]||(a[3]=b=>!d.disabled&&N())},[e("div",{class:"flex-shrink-0 w-3 h-3 sm:w-4 sm:h-4 flex items-center justify-center",onClick:Y(h,["stop"])},[s.value?(o(),r("svg",{key:0,class:D(["w-2.5 h-2.5 sm:w-3 sm:h-3 transition-transform duration-200",u.value?"rotate-90":"rotate-0"]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},a[4]||(a[4]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5l7 7-7 7"},null,-1)]),2)):L("",!0)]),e("div",mo,[d.node.name.startsWith("#")?(o(),r("svg",po,a[5]||(a[5]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"},null,-1)]))):(o(),r("svg",vo,a[6]||(a[6]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"},null,-1)])))]),e("span",{class:D(["font-mono text-xs sm:text-sm transition-colors duration-200 break-all",t.selectedNodeId===t.node.id?"text-primary font-medium":""])},c(t.node.name),3),t.node.transport_key?(o(),r("div",xo,[e("div",bo,[e("button",{onClick:$,class:"p-1 rounded hover:bg-white/10 transition-colors",title:i.value?"Hide full key":"Show full key"},a[7]||(a[7]=[e("svg",{class:"w-3 h-3 text-white/60 hover:text-white/80",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"})],-1)]),8,ko),i.value?L("",!0):(o(),r("span",go,c(y(t.node.transport_key)),1)),i.value?(o(),r("div",{key:1,class:"fixed inset-0 z-[9998] flex items-center justify-center bg-black/70 backdrop-blur-md",onClick:a[2]||(a[2]=b=>i.value=!1)},[e("div",{class:"bg-black/20 border border-white/20 rounded-lg shadow-lg p-6 max-w-2xl w-full mx-4",onClick:a[1]||(a[1]=Y(()=>{},["stop"]))},[e("div",yo,[a[9]||(a[9]=e("h3",{class:"text-lg font-semibold text-white"},"Transport Key",-1)),e("button",{onClick:a[0]||(a[0]=b=>i.value=!1),class:"text-white/60 hover:text-white transition-colors"},a[8]||(a[8]=[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),e("div",fo,[e("div",ho,c(t.node.transport_key),1)]),e("div",{class:"flex justify-end"},[e("button",{onClick:n,class:"px-4 py-2 bg-accent-green/20 hover:bg-accent-green/30 border border-accent-green/50 text-accent-green rounded-lg transition-colors flex items-center gap-2",title:"Copy to clipboard"},a[10]||(a[10]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})],-1),U(" Copy Key ",-1)]))])])])):L("",!0)])])):L("",!0),e("div",wo,[t.node.last_used?(o(),r("div",_o,[a[11]||(a[11]=e("svg",{class:"w-3 h-3 text-white/40",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("span",{class:"text-xs text-white/50",title:t.node.last_used.toLocaleString()},c(p(t.node.last_used)),9,Co)])):(o(),r("div",$o,a[12]||(a[12]=[e("svg",{class:"w-3 h-3 text-white/30",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"})],-1),e("span",{class:"text-xs text-white/30 italic"},"Never",-1)]))),e("span",{class:D(["px-1.5 sm:px-2 py-0.5 text-[10px] sm:text-xs font-medium rounded-md transition-colors",t.node.floodPolicy==="allow"?"bg-accent-green/10 text-accent-green/90 border border-accent-green/20":"bg-accent-red/10 text-accent-red/90 border border-accent-red/20"])},c(t.node.floodPolicy==="allow"?"ALLOW":"DENY"),3),s.value?(o(),r("span",Mo," > "+c(t.node.children.length),1)):L("",!0)])],2),O(Ce,{"enter-active-class":"transition-all duration-300 ease-out","enter-from-class":"opacity-0 max-h-0 overflow-hidden","enter-to-class":"opacity-100 max-h-screen overflow-visible","leave-active-class":"transition-all duration-300 ease-in","leave-from-class":"opacity-100 max-h-screen overflow-visible","leave-to-class":"opacity-0 max-h-0 overflow-hidden"},{default:ve(()=>[u.value&&t.node.children.length>0?(o(),r("div",Ao,[(o(!0),r(K,null,Z(t.node.children,b=>(o(),xe(A,{key:b.id,node:b,"selected-node-id":t.selectedNodeId,level:t.level+1,disabled:d.disabled,onSelect:M},null,8,["node","selected-node-id","level","disabled"]))),128))])):L("",!0)]),_:1})])}}}),jo=pe(So,[["__scopeId","data-v-59e9974c"]]),To={class:"flex items-center justify-between mb-6"},No={class:"text-content-secondary dark:text-content-muted text-sm mt-1"},Bo={key:0},Eo={class:"text-primary font-mono"},Lo={key:1},Fo={for:"keyName",class:"block text-sm font-medium text-white mb-2"},Po={class:"flex items-center gap-2"},zo={key:0,class:"w-4 h-4 text-secondary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Vo={key:1,class:"w-4 h-4 text-accent-green",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Do={class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4"},Io={class:"flex items-center gap-3 mb-2"},Ho={class:"flex items-center gap-2"},Uo={key:0,class:"w-5 h-5 text-secondary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Ro={key:1,class:"w-5 h-5 text-accent-green",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Ko={class:"text-content-secondary dark:text-content-muted text-sm"},Oo={class:"grid grid-cols-2 gap-3"},qo={class:"relative cursor-pointer group"},Wo={class:"relative cursor-pointer group"},Go={class:"flex gap-3 pt-4"},Qo=["disabled"],Yo=G({__name:"AddKeyModal",props:{show:{type:Boolean},selectedNodeName:{},selectedNodeId:{}},emits:["close","add"],setup(I,{emit:_}){const d=I,v=_,k=m(""),i=m(""),u=m("allow"),s=B(()=>k.value.startsWith("#")),p=B(()=>({type:s.value?"Region":"Private Key",description:s.value?"Regional organizational key":"Individual assigned key"}));te(s,$=>{$?i.value="This will create a new region for organizing keys":i.value="This will create a new private key entry"},{immediate:!0});const y=B(()=>k.value.trim().length>0),h=()=>{y.value&&(v("add",{name:k.value.trim(),floodPolicy:u.value,parentId:d.selectedNodeId}),k.value="",i.value="",u.value="allow")},N=()=>{k.value="",i.value="",u.value="allow",v("close")},M=$=>{$.target===$.currentTarget&&N()};return($,n)=>$.show?(o(),r("div",{key:0,onClick:M,class:"fixed inset-0 bg-black/40 backdrop-blur-lg z-[99999] flex items-center justify-center p-4",style:{"backdrop-filter":"blur(8px) saturate(180%)",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"}},[e("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10",onClick:n[3]||(n[3]=Y(()=>{},["stop"]))},[e("div",To,[e("div",null,[n[5]||(n[5]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary"},"Add New Entry",-1)),e("p",No,[d.selectedNodeName?(o(),r("span",Bo,[n[4]||(n[4]=U(" Add to: ",-1)),e("span",Eo,c(d.selectedNodeName),1)])):(o(),r("span",Lo," Add to root level (#uk) "))])]),e("button",{onClick:N,class:"text-white/60 hover:text-white transition-colors"},n[6]||(n[6]=[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),e("form",{onSubmit:Y(h,["prevent"]),class:"space-y-4"},[e("div",null,[e("label",Fo,[e("div",Po,[s.value?(o(),r("svg",zo,n[7]||(n[7]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"},null,-1)]))):(o(),r("svg",Vo,n[8]||(n[8]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"},null,-1)]))),n[9]||(n[9]=U(" Region/Key Name ",-1))])]),S(e("input",{id:"keyName","onUpdate:modelValue":n[0]||(n[0]=t=>k.value=t),type:"text",placeholder:"Enter name (prefix with # for regions)",class:"w-full px-4 py-3 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/20 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/50 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-colors",autocomplete:"off"},null,512),[[V,k.value]])]),e("div",Do,[e("div",Io,[e("div",Ho,[s.value?(o(),r("svg",Uo,n[10]||(n[10]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"},null,-1)]))):(o(),r("svg",Ro,n[11]||(n[11]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1221 9z"},null,-1)]))),e("span",{class:D([s.value?"text-secondary":"text-accent-green","font-medium"])},c(p.value.type),3)]),e("div",{class:D(["flex-1 h-px",s.value?"bg-secondary/20":"bg-accent-green/20"])},null,2)]),e("p",Ko,c(p.value.description),1)]),e("div",null,[n[14]||(n[14]=e("label",{class:"block text-sm font-medium text-content-primary dark:text-content-primary mb-3"},[e("div",{class:"flex items-center gap-2"},[e("svg",{class:"w-4 h-4 text-primary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"})]),U(" Flood Policy ")])],-1)),e("div",Oo,[e("label",qo,[S(e("input",{type:"radio","onUpdate:modelValue":n[1]||(n[1]=t=>u.value=t),value:"allow",class:"sr-only"},null,512),[[ne,u.value]]),n[12]||(n[12]=Q('
Allow

Permit flooding

',1))]),e("label",Wo,[S(e("input",{type:"radio","onUpdate:modelValue":n[2]||(n[2]=t=>u.value=t),value:"deny",class:"sr-only"},null,512),[[ne,u.value]]),n[13]||(n[13]=Q('
Deny

Block flooding

',1))])])]),e("div",Go,[e("button",{type:"button",onClick:N,class:"flex-1 px-4 py-3 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 border border-stroke-subtle dark:border-stroke/20 text-content-primary dark:text-content-primary rounded-lg transition-colors"}," Cancel "),e("button",{type:"submit",disabled:!y.value,class:D(["flex-1 px-4 py-3 rounded-lg transition-colors font-medium",y.value?"bg-accent-green/20 hover:bg-accent-green/30 border border-accent-green/50 text-accent-green":"bg-background-mute dark:bg-stroke/5 border border-stroke-subtle dark:border-stroke/20 text-content-muted dark:text-content-muted cursor-not-allowed"])}," Add "+c(p.value.type),11,Qo)])],32)])])):L("",!0)}}),Xo={class:"flex items-center justify-between mb-6"},Jo={class:"text-content-secondary dark:text-content-muted text-sm mt-1"},Zo={class:"text-primary font-mono"},er={for:"keyName",class:"block text-sm font-medium text-content-secondary dark:text-content-primary mb-2"},tr={class:"flex items-center gap-2"},or={key:0,class:"w-4 h-4 text-secondary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},rr={key:1,class:"w-4 h-4 text-accent-green",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},sr={class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4"},nr={class:"flex items-center gap-3 mb-2"},ar={class:"flex items-center gap-2"},lr={key:0,class:"w-5 h-5 text-secondary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},dr={key:1,class:"w-5 h-5 text-accent-green",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},ir={class:"text-content-secondary dark:text-content-muted text-sm"},cr={key:0,class:"space-y-4"},ur={key:0,class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4"},mr={class:"bg-background-mute dark:bg-black/20 border border-stroke-subtle dark:border-stroke/10 rounded-md p-3"},pr={class:"text-xs font-mono text-content-primary dark:text-content-primary/80 break-all"},vr={key:1,class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4"},xr={class:"flex items-center justify-between"},br={class:"text-sm text-content-secondary dark:text-content-muted"},kr={class:"text-xs text-content-muted dark:text-content-muted"},gr={class:"grid grid-cols-2 gap-3"},yr={class:"relative cursor-pointer group"},fr={class:"relative cursor-pointer group"},hr={class:"flex gap-3 pt-4"},wr=["disabled"],_r=G({__name:"EditKeyModal",props:{show:{type:Boolean},node:{}},emits:["close","save","request-delete"],setup(I,{emit:_}){const d=I,v=_,k=m(""),i=m("allow"),u=B(()=>k.value.startsWith("#")),s=B(()=>({type:u.value?"Region":"Private Key",description:u.value?"Regional organizational key":"Individual assigned key"}));te(()=>d.node,t=>{t?(k.value=t.name,i.value=t.floodPolicy):(k.value="",i.value="allow")},{immediate:!0});const p=B(()=>k.value.trim().length>0&&d.node),y=t=>{const A=new Date().getTime()-t.getTime(),b=Math.floor(A/(1e3*60)),x=Math.floor(A/(1e3*60*60)),g=Math.floor(A/(1e3*60*60*24)),z=Math.floor(g/365);return b<60?`${b}m ago`:x<24?`${x}h ago`:g<365?`${g}d ago`:`${z}y ago`},h=t=>{window.navigator?.clipboard&&window.navigator.clipboard.writeText(t)},N=()=>{!p.value||!d.node||(v("save",{id:d.node.id,name:k.value.trim(),floodPolicy:i.value}),$())},M=()=>{d.node&&(v("request-delete",d.node),$())},$=()=>{v("close")},n=t=>{t.target===t.currentTarget&&$()};return(t,a)=>t.show?(o(),r("div",{key:0,onClick:n,class:"fixed inset-0 bg-black/50 backdrop-blur-lg z-[99999] flex items-center justify-center p-4",style:{"backdrop-filter":"blur(8px) saturate(180%)",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"}},[e("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-lg border border-stroke-subtle dark:border-white/10",onClick:a[4]||(a[4]=Y(()=>{},["stop"]))},[e("div",Xo,[e("div",null,[a[6]||(a[6]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary"},"Edit Entry",-1)),e("p",Jo,[a[5]||(a[5]=U(" Modify ",-1)),e("span",Zo,c(t.node?.name),1)])]),e("button",{onClick:$,class:"text-white/60 hover:text-white transition-colors"},a[7]||(a[7]=[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),e("form",{onSubmit:Y(N,["prevent"]),class:"space-y-4"},[e("div",null,[e("label",er,[e("div",tr,[u.value?(o(),r("svg",or,a[8]||(a[8]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"},null,-1)]))):(o(),r("svg",rr,a[9]||(a[9]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1721 9z"},null,-1)]))),a[10]||(a[10]=U(" Region/Key Name ",-1))])]),S(e("input",{id:"keyName","onUpdate:modelValue":a[0]||(a[0]=A=>k.value=A),type:"text",placeholder:"Enter name (prefix with # for regions)",class:"w-full px-4 py-3 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/20 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/50 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-colors",autocomplete:"off"},null,512),[[V,k.value]])]),e("div",sr,[e("div",nr,[e("div",ar,[u.value?(o(),r("svg",lr,a[11]||(a[11]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"},null,-1)]))):(o(),r("svg",dr,a[12]||(a[12]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1721 9z"},null,-1)]))),e("span",{class:D([u.value?"text-secondary":"text-accent-green","font-medium"])},c(s.value.type),3)]),e("div",{class:D(["flex-1 h-px",u.value?"bg-secondary/20":"bg-accent-green/20"])},null,2)]),e("p",ir,c(s.value.description),1)]),t.node?(o(),r("div",cr,[t.node.transport_key?(o(),r("div",ur,[a[14]||(a[14]=Q('
Transport Key
',1)),e("div",mr,[e("div",pr,c(t.node.transport_key),1),e("button",{onClick:a[1]||(a[1]=A=>h(t.node.transport_key||"")),class:"mt-2 text-xs text-accent-green hover:text-accent-green/80 flex items-center gap-1",title:"Copy to clipboard"},a[13]||(a[13]=[e("svg",{class:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})],-1),U(" Copy Key ",-1)]))])])):L("",!0),t.node.last_used?(o(),r("div",vr,[a[15]||(a[15]=e("div",{class:"flex items-center gap-2 mb-3"},[e("svg",{class:"w-4 h-4 text-primary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"})]),e("span",{class:"text-sm font-medium text-content-primary dark:text-content-primary"},"Last Used")],-1)),e("div",xr,[e("div",br,c(t.node.last_used.toLocaleDateString())+" at "+c(t.node.last_used.toLocaleTimeString()),1),e("div",kr,c(y(t.node.last_used)),1)])])):L("",!0)])):L("",!0),e("div",null,[a[18]||(a[18]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-primary mb-3"},[e("div",{class:"flex items-center gap-2"},[e("svg",{class:"w-4 h-4 text-primary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"})]),U(" Flood Policy ")])],-1)),e("div",gr,[e("label",yr,[S(e("input",{type:"radio","onUpdate:modelValue":a[2]||(a[2]=A=>i.value=A),value:"allow",class:"sr-only"},null,512),[[ne,i.value]]),a[16]||(a[16]=Q('
Allow

Permit flooding

',1))]),e("label",fr,[S(e("input",{type:"radio","onUpdate:modelValue":a[3]||(a[3]=A=>i.value=A),value:"deny",class:"sr-only"},null,512),[[ne,i.value]]),a[17]||(a[17]=Q('
Deny

Block flooding

',1))])])]),e("div",hr,[e("button",{type:"button",onClick:M,class:"px-4 py-3 bg-accent-red/20 hover:bg-accent-red/30 border border-accent-red/50 text-accent-red rounded-lg transition-colors"}," Delete "),e("button",{type:"button",onClick:$,class:"flex-1 px-4 py-3 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 border border-stroke-subtle dark:border-stroke/20 text-content-primary dark:text-content-primary rounded-lg transition-colors"}," Cancel "),e("button",{type:"submit",disabled:!p.value,class:D(["flex-1 px-4 py-3 rounded-lg transition-colors font-medium",p.value?"bg-accent-green/20 hover:bg-accent-green/30 border border-accent-green/50 text-accent-green":"bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/20 text-content-muted dark:text-content-muted/70 cursor-not-allowed"])}," Save Changes ",10,wr)])],32)])])):L("",!0)}}),Cr={class:"flex items-center gap-3 mb-6"},$r={class:"text-content-secondary dark:text-content-muted text-sm mt-1"},Mr={class:"text-accent-red font-mono"},Ar={key:0,class:"bg-accent-red/10 border border-accent-red/30 rounded-lg p-4 mb-6"},Sr={class:"flex items-start gap-3"},jr={class:"flex-1"},Tr={class:"text-accent-red font-medium text-sm mb-2"},Nr={class:"space-y-1 max-h-32 overflow-y-auto"},Br={key:0,class:"w-3 h-3 text-secondary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Er={key:1,class:"w-3 h-3 text-accent-green",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Lr={class:"font-mono"},Fr={key:0,class:"text-content-secondary dark:text-content-muted text-xs"},Pr={key:1,class:"mb-6"},zr={class:"mb-3"},Vr={class:"relative"},Dr={class:"space-y-2 max-h-40 overflow-y-auto border border-stroke-subtle dark:border-stroke/20 rounded-lg p-3 bg-gray-50 dark:bg-white/5"},Ir={key:0,class:"text-center py-4 text-content-secondary dark:text-content-muted text-sm"},Hr={class:"relative"},Ur=["value"],Rr={class:"flex items-center gap-2 flex-1"},Kr={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Or={key:0,class:"ml-auto px-2 py-0.5 bg-background-mute dark:bg-stroke/10 text-content-secondary dark:text-content-muted text-xs rounded-full"},qr={class:"flex gap-3"},Wr=G({__name:"DeleteConfirmModal",props:{show:{type:Boolean},node:{},allNodes:{}},emits:["close","delete-all","move-children"],setup(I,{emit:_}){const d=I,v=_,k=m(null),i=m(""),u=n=>{const t=[],a=A=>{for(const b of A.children)t.push(b),a(b)};return a(n),t},s=B(()=>d.node?u(d.node):[]),p=B(()=>{if(!d.node)return[];const n=new Set([d.node.id,...s.value.map(a=>a.id)]),t=a=>{const A=[];for(const b of a)b.name.startsWith("#")&&!n.has(b.id)&&A.push(b),b.children.length>0&&A.push(...t(b.children));return A};return t(d.allNodes)}),y=B(()=>{if(!i.value.trim())return p.value;const n=i.value.toLowerCase();return p.value.filter(t=>t.name.toLowerCase().includes(n))}),h=()=>{d.node&&(v("delete-all",d.node.id),M())},N=()=>{!d.node||!k.value||(v("move-children",{nodeId:d.node.id,targetParentId:k.value}),M())},M=()=>{k.value=null,i.value="",v("close")},$=n=>{n.target===n.currentTarget&&M()};return(n,t)=>n.show&&n.node?(o(),r("div",{key:0,onClick:$,class:"fixed inset-0 bg-black/80 backdrop-blur-lg z-[99999] flex items-center justify-center p-4",style:{"backdrop-filter":"blur(8px) saturate(180%)",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"}},[e("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-lg border border-stroke-subtle dark:border-white/10",onClick:t[2]||(t[2]=Y(()=>{},["stop"]))},[e("div",Cr,[t[6]||(t[6]=e("svg",{class:"w-6 h-6 text-accent-red",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"})],-1)),e("div",null,[t[4]||(t[4]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary"},"Confirm Deletion",-1)),e("p",$r,[t[3]||(t[3]=U(" Deleting ",-1)),e("span",Mr,c(n.node?.name),1)])]),e("button",{onClick:M,class:"ml-auto text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors"},t[5]||(t[5]=[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),s.value.length>0?(o(),r("div",Ar,[e("div",Sr,[t[9]||(t[9]=e("svg",{class:"w-5 h-5 text-accent-red flex-shrink-0 mt-0.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),e("div",jr,[e("h4",Tr," This will affect "+c(s.value.length)+" child "+c(s.value.length===1?"entry":"entries")+": ",1),e("div",Nr,[(o(!0),r(K,null,Z(s.value.slice(0,10),a=>(o(),r("div",{key:a.id,class:"flex items-center gap-2 text-xs text-content-secondary dark:text-content-primary/80"},[a.name.startsWith("#")?(o(),r("svg",Br,t[7]||(t[7]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"},null,-1)]))):(o(),r("svg",Er,t[8]||(t[8]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1721 9z"},null,-1)]))),e("span",Lr,c(a.name),1),e("span",{class:D(["px-1 py-0.5 text-xs rounded",a.floodPolicy==="allow"?"bg-accent-green/20 text-accent-green":"bg-accent-red/20 text-accent-red"])},c(a.floodPolicy),3)]))),128)),s.value.length>10?(o(),r("div",Fr," ...and "+c(s.value.length-10)+" more ",1)):L("",!0)])])])])):L("",!0),s.value.length>0&&p.value.length>0?(o(),r("div",Pr,[t[13]||(t[13]=e("h4",{class:"text-content-primary dark:text-content-primary font-medium text-sm mb-3"},"Move children to another region:",-1)),e("div",zr,[e("div",Vr,[t[10]||(t[10]=e("svg",{class:"absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-content-muted dark:text-content-muted",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})],-1)),S(e("input",{"onUpdate:modelValue":t[0]||(t[0]=a=>i.value=a),type:"text",placeholder:"Search regions...",class:"w-full pl-9 pr-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/20 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/50 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-colors text-sm"},null,512),[[V,i.value]])])]),e("div",Dr,[y.value.length===0?(o(),r("div",Ir,c(i.value?"No regions match your search":"No available regions"),1)):L("",!0),(o(!0),r(K,null,Z(y.value,a=>(o(),r("label",{key:a.id,class:"flex items-center gap-3 p-2 rounded cursor-pointer hover:bg-stroke-subtle dark:hover:bg-white/10 transition-colors group"},[e("div",Hr,[S(e("input",{type:"radio",value:a.id,"onUpdate:modelValue":t[1]||(t[1]=A=>k.value=A),class:"sr-only peer"},null,8,Ur),[[ne,k.value]]),t[11]||(t[11]=e("div",{class:"w-4 h-4 border-2 border-stroke dark:border-stroke/30 rounded-full group-hover:border-stroke dark:group-hover:border-stroke/50 peer-checked:border-primary peer-checked:bg-primary/20 transition-all"},[e("div",{class:"w-2 h-2 rounded-full bg-primary scale-0 peer-checked:scale-100 transition-transform absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"})],-1))]),e("div",Rr,[t[12]||(t[12]=e("svg",{class:"w-4 h-4 text-secondary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"})],-1)),e("span",Kr,c(a.name),1),a.children.length>0?(o(),r("span",Or,c(a.children.length),1)):L("",!0)])]))),128))])])):L("",!0),e("div",qr,[e("button",{onClick:M,class:"flex-1 px-4 py-3 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 border border-stroke-subtle dark:border-stroke/20 text-content-primary dark:text-content-primary rounded-lg transition-colors"}," Cancel "),s.value.length>0&&k.value?(o(),r("button",{key:0,onClick:N,class:"flex-1 px-4 py-3 bg-primary/20 hover:bg-primary/30 border border-primary/50 text-primary rounded-lg transition-colors"}," Move & Delete ")):L("",!0),e("button",{onClick:h,class:"flex-1 px-4 py-3 bg-accent-red/20 hover:bg-accent-red/30 border border-accent-red/50 text-accent-red rounded-lg transition-colors font-medium"},c(s.value.length>0?"Delete All":"Delete"),1)])])])):L("",!0)}}),Gr={class:"space-y-4 sm:space-y-6"},Qr={class:"flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3"},Yr={class:"flex gap-2 flex-wrap"},Xr=["disabled"],Jr=["disabled"],Zr=["disabled"],es={class:"glass-card rounded-[15px] p-3 sm:p-4 border border-stroke-subtle dark:border-stroke/10 bg-background-mute dark:bg-white/5"},ts={class:"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"},os={class:"flex items-center gap-2 sm:gap-3"},rs={class:"flex bg-background-mute dark:bg-stroke/5 rounded-lg border border-stroke-subtle dark:border-stroke/20 p-0.5 sm:p-1"},ss={class:"glass-card rounded-[15px] p-3 sm:p-6 border border-stroke-subtle dark:border-stroke/10"},ns={key:0,class:"flex items-center justify-center py-8"},as={key:1,class:"text-center py-8"},ls={class:"text-content-secondary dark:text-content-muted text-sm"},ds={key:2,class:"text-center py-8"},is={key:3,class:"space-y-2"},cs=G({name:"TransportKeys",__name:"TransportKeys",setup(I){const _=ke(),d=m(!1),v=m(!1),k=m(!1),i=m(null),u=m(null),s=m("deny"),p=m([]),y=m(!1),h=m(null),N=w=>{const f=new Map,H=[];return w.forEach(R=>{const se={id:R.id,name:R.name,floodPolicy:R.flood_policy,transport_key:R.transport_key,last_used:R.last_used?new Date(R.last_used*1e3):void 0,parent_id:R.parent_id,children:[]};f.set(R.id,se)}),f.forEach(R=>{R.parent_id&&f.has(R.parent_id)?f.get(R.parent_id).children.push(R):H.push(R)}),H},M=async()=>{try{y.value=!0,h.value=null;const w=await q.getTransportKeys();w.success&&w.data?p.value=N(w.data):h.value=w.error||"Failed to load transport keys"}catch(w){h.value=w instanceof Error?w.message:"Unknown error occurred",console.error("Error loading transport keys:",w)}finally{y.value=!1}};ae(()=>{M()});function $(w,f){for(const H of w){if(H.id===f)return H;if(H.children){const R=$(H.children,f);if(R)return R}}return null}function n(){const w=_.selectedNodeId.value;return w?$(p.value,w)?.name:void 0}function t(w){s.value==="deny"&&_.setSelectedNode(w)}function a(){s.value==="deny"&&(d.value=!0)}function A(){if(s.value==="deny"&&_.selectedNodeId.value){const w=$(p.value,_.selectedNodeId.value);w&&(u.value=w,k.value=!0)}}function b(){if(s.value==="deny"&&_.selectedNodeId.value){const w=$(p.value,_.selectedNodeId.value);w&&(i.value=w,v.value=!0)}}const x=async w=>{try{const f=await q.createTransportKey(w.name,w.floodPolicy,void 0,w.parentId,void 0);f.success?await M():(console.error("Failed to add transport key:",f.error),h.value=f.error||"Failed to add transport key")}catch(f){console.error("Error adding transport key:",f),h.value=f instanceof Error?f.message:"Unknown error occurred"}finally{d.value=!1}};function g(){d.value=!1}async function z(w){try{const f=w==="allow",H=await q.updateGlobalFloodPolicy(f);H.success?s.value=w:(console.error("Failed to update global flood policy:",H.error),h.value=H.error||"Failed to update global flood policy")}catch(f){console.error("Error updating global flood policy:",f),h.value=f instanceof Error?f.message:"Failed to update global flood policy"}}function W(){v.value=!1,i.value=null}async function C(w){try{const f=await q.updateTransportKey(w.id,w.name,w.floodPolicy);f.success?await M():(console.error("Failed to update transport key:",f.error),h.value=f.error||"Failed to update transport key")}catch(f){console.error("Error updating transport key:",f),h.value=f instanceof Error?f.message:"Unknown error occurred"}finally{W()}}function j(w){v.value=!1,i.value=null,u.value=w,k.value=!0}function P(){k.value=!1,u.value=null}async function F(w){try{const f=await q.deleteTransportKey(w);f.success?(await M(),_.setSelectedNode(null)):(console.error("Failed to delete transport key:",f.error),h.value=f.error||"Failed to delete transport key")}catch(f){console.error("Error deleting transport key:",f),h.value=f instanceof Error?f.message:"Unknown error occurred"}finally{P()}}async function E(w){try{const f=await q.deleteTransportKey(w.nodeId);f.success?(await M(),_.setSelectedNode(null)):(console.error("Failed to delete transport key:",f.error),h.value=f.error||"Failed to delete transport key")}catch(f){console.error("Error deleting transport key:",f),h.value=f instanceof Error?f.message:"Unknown error occurred"}finally{P()}}return(w,f)=>(o(),r("div",Gr,[e("div",Qr,[f[3]||(f[3]=e("div",null,[e("h3",{class:"text-base sm:text-lg font-semibold text-content-primary dark:text-content-primary mb-1 sm:mb-2"},"Regions/Keys"),e("p",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Manage regional key hierarchy")],-1)),e("div",Yr,[e("button",{onClick:a,disabled:s.value==="allow",class:D(["flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-3 py-1.5 sm:py-2 rounded-lg border transition-colors text-xs sm:text-sm",s.value==="allow"?"bg-background-mute dark:bg-white/5 text-content-muted dark:text-content-muted/70 border-stroke-subtle dark:border-stroke/20 cursor-not-allowed":"bg-accent-green/10 hover:bg-accent-green/20 text-accent-green border-accent-green/30"])},f[2]||(f[2]=[e("svg",{class:"w-3.5 h-3.5 sm:w-4 sm:h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),U(" Add ",-1)]),10,Xr),e("button",{onClick:b,disabled:!J(_).selectedNodeId.value||s.value==="allow",class:D(["px-2.5 sm:px-4 py-1.5 sm:py-2 rounded-lg border transition-colors text-xs sm:text-sm",!J(_).selectedNodeId.value||s.value==="allow"?"bg-background-mute dark:bg-stroke/10 text-content-muted dark:text-content-muted/70 border-stroke-subtle dark:border-stroke/20 cursor-not-allowed":"bg-accent-green/20 hover:bg-accent-green/30 text-accent-green border-accent-green/50"])}," Edit ",10,Jr),e("button",{onClick:A,disabled:!J(_).selectedNodeId.value||s.value==="allow",class:D(["px-2.5 sm:px-4 py-1.5 sm:py-2 rounded-lg border transition-colors text-xs sm:text-sm",!J(_).selectedNodeId.value||s.value==="allow"?"bg-background-mute dark:bg-stroke/10 text-content-muted dark:text-content-muted/70 border-stroke-subtle dark:border-stroke/20 cursor-not-allowed":"bg-accent-red/20 hover:bg-accent-red/30 text-accent-red border-accent-red/50"])}," Delete ",10,Zr)])]),e("div",es,[e("div",ts,[f[4]||(f[4]=e("div",null,[e("h4",{class:"text-xs sm:text-sm font-medium text-content-primary dark:text-content-primary mb-1"},"Global Flood Policy (*)"),e("p",{class:"text-content-secondary dark:text-content-muted text-[10px] sm:text-xs"},"Master control for repeater flooding")],-1)),e("div",os,[e("div",rs,[e("button",{onClick:f[0]||(f[0]=H=>z("deny")),class:D(["px-2 sm:px-3 py-1 text-[10px] sm:text-xs font-medium rounded transition-colors",s.value==="deny"?"bg-accent-red/20 text-accent-red border border-accent-red/50":"text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-secondary"])}," DENY ",2),e("button",{onClick:f[1]||(f[1]=H=>z("allow")),class:D(["px-2 sm:px-3 py-1 text-[10px] sm:text-xs font-medium rounded transition-colors",s.value==="allow"?"bg-accent-green/20 text-accent-green border border-accent-green/50":"text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-secondary"])}," ALLOW ",2)])])])]),e("div",ss,[y.value?(o(),r("div",ns,f[5]||(f[5]=[e("div",{class:"animate-spin rounded-full h-8 w-8 border-b-2 border-accent-green"},null,-1),e("span",{class:"ml-2 text-content-secondary dark:text-content-muted"},"Loading transport keys...",-1)]))):h.value?(o(),r("div",as,[f[6]||(f[6]=e("div",{class:"text-accent-red mb-2"},"⚠️ Error loading transport keys",-1)),e("div",ls,c(h.value),1),e("button",{onClick:M,class:"mt-4 px-4 py-2 bg-accent-green/20 hover:bg-accent-green/30 text-accent-green border border-accent-green/50 rounded-lg transition-colors"}," Retry ")])):p.value.length===0?(o(),r("div",ds,f[7]||(f[7]=[e("div",{class:"text-content-muted dark:text-content-muted mb-2"},"📝 No transport keys found",-1),e("div",{class:"text-content-muted dark:text-content-muted/60 text-sm"},"Add your first transport key to get started",-1)]))):(o(),r("div",is,[(o(!0),r(K,null,Z(p.value,H=>(o(),xe(jo,{key:H.id,node:H,"selected-node-id":J(_).selectedNodeId.value,level:0,disabled:s.value==="allow",onSelect:t},null,8,["node","selected-node-id","disabled"]))),128))]))]),O(Yo,{show:d.value,"selected-node-name":n(),"selected-node-id":J(_).selectedNodeId.value||void 0,onClose:g,onAdd:x},null,8,["show","selected-node-name","selected-node-id"]),O(_r,{show:v.value,node:i.value,onClose:W,onSave:C,onRequestDelete:j},null,8,["show","node"]),O(Wr,{show:k.value,node:u.value,"all-nodes":p.value,onClose:P,onDeleteAll:F,onMoveChildren:E},null,8,["show","node","all-nodes"])]))}}),us={class:"space-y-4 sm:space-y-6"},ms={class:"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"},ps={key:0,class:"bg-red-500/10 border border-red-500/30 rounded-lg p-4"},vs={class:"flex items-center gap-2 text-red-600 dark:text-red-400"},xs={key:1,class:"flex items-center justify-center py-12"},bs={key:2,class:"space-y-3"},ks={class:"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"},gs={class:"flex-1"},ys={class:"flex items-center gap-2 sm:gap-3"},fs={class:"min-w-0 flex-1"},hs={class:"text-content-primary dark:text-content-primary font-medium text-sm sm:text-base break-all"},ws={class:"flex flex-col sm:flex-row sm:items-center sm:gap-4 mt-1 text-xs text-content-secondary dark:text-content-muted"},_s={class:"truncate"},Cs={class:"truncate"},$s=["onClick","disabled"],Ms={key:3,class:"text-center py-12"},As={class:"bg-surface dark:bg-surface-elevated border border-stroke-subtle dark:border-stroke/20 rounded-[15px] p-6 max-w-md w-full shadow-2xl"},Ss={class:"space-y-4"},js={class:"flex justify-end gap-3 mt-6"},Ts=["disabled"],Ns=["disabled"],Bs={class:"bg-surface dark:bg-surface-elevated border border-stroke-subtle dark:border-stroke/20 rounded-[15px] p-6 max-w-lg w-full shadow-2xl"},Es={class:"space-y-4"},Ls={class:"flex gap-2"},Fs=["value"],Ps={class:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4"},zs={class:"block bg-blue-500/20 px-3 py-2 rounded text-xs text-blue-100 font-mono overflow-x-auto"},Vs=G({name:"APITokens",__name:"APITokens",setup(I){const _=m([]),d=m(!1),v=m(null),k=m(!1),i=m(""),u=m(null),s=m(!1),p=m(!1),y=m(null),h=async()=>{d.value=!0,v.value=null;try{const x=await q.get("/auth/tokens"),g=x.data||x;_.value=g.tokens||[]}catch(x){console.error("Failed to fetch API tokens:",x),v.value=x instanceof Error?x.message:"Failed to fetch tokens"}finally{d.value=!1}},N=async()=>{if(!i.value.trim()){v.value="Token name is required";return}d.value=!0,v.value=null;try{const x=await q.post("/auth/tokens",{name:i.value.trim()}),g=x.data||x;u.value=g.token||null,k.value=!1,s.value=!0,i.value="",await h()}catch(x){console.error("Failed to create API token:",x),v.value=x instanceof Error?x.message:"Failed to create token"}finally{d.value=!1}},M=(x,g)=>{y.value={id:x,name:g},p.value=!0},$=async()=>{if(y.value){d.value=!0,v.value=null;try{await q.delete(`/auth/tokens/${y.value.id}`),await h(),p.value=!1,y.value=null}catch(x){console.error("Failed to revoke API token:",x),v.value=x instanceof Error?x.message:"Failed to revoke token"}finally{d.value=!1}}},n=()=>{k.value=!1,i.value="",v.value=null},t=()=>{s.value=!1,u.value=null},a=()=>{u.value&&navigator.clipboard.writeText(u.value)},A=x=>x?new Date(x*1e3).toLocaleString():"Never",b=B(()=>`${window.location.origin}/api/stats`);return ae(()=>{h()}),(x,g)=>(o(),r(K,null,[e("div",us,[e("div",ms,[g[5]||(g[5]=e("div",null,[e("h2",{class:"text-lg sm:text-xl font-semibold text-content-primary dark:text-content-primary"},"API Tokens"),e("p",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm mt-1"},"Manage API tokens for machine-to-machine authentication")],-1)),e("button",{onClick:g[0]||(g[0]=z=>k.value=!0),class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors flex items-center justify-center gap-2 text-sm sm:text-base"},g[4]||(g[4]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),U(" Create Token ",-1)]))]),g[20]||(g[20]=Q('

API tokens are used for machine-to-machine authentication. Include the token in the X-API-Key header when making API requests.

Tokens are only shown once at creation. Store them securely.

',1)),v.value?(o(),r("div",ps,[e("div",vs,[g[6]||(g[6]=e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),U(" "+c(v.value),1)])])):L("",!0),d.value&&_.value.length===0?(o(),r("div",xs,g[7]||(g[7]=[e("div",{class:"text-center"},[e("div",{class:"animate-spin w-8 h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-primary rounded-full mx-auto mb-4"}),e("div",{class:"text-content-secondary dark:text-content-muted"},"Loading tokens...")],-1)]))):_.value.length>0?(o(),r("div",bs,[(o(!0),r(K,null,Z(_.value,z=>(o(),r("div",{key:z.id,class:"bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-3 sm:p-4 hover:bg-stroke-subtle dark:hover:bg-white/10 transition-colors"},[e("div",ks,[e("div",gs,[e("div",ys,[g[8]||(g[8]=e("svg",{class:"w-4 h-4 sm:w-5 sm:h-5 text-primary flex-shrink-0",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"})],-1)),e("div",fs,[e("h3",hs,c(z.name),1),e("div",ws,[e("span",_s,"Created: "+c(A(z.created_at)),1),e("span",Cs,"Last used: "+c(A(z.last_used)),1)])])])]),e("button",{onClick:W=>M(z.id,z.name),disabled:d.value,class:"w-full sm:w-auto px-3 py-1.5 bg-red-100 dark:bg-red-500/20 hover:bg-red-500/30 text-red-600 dark:text-red-400 rounded-lg border border-red-500/50 transition-colors disabled:opacity-50 text-sm"}," Revoke ",8,$s)])]))),128))])):(o(),r("div",Ms,[g[9]||(g[9]=e("svg",{class:"w-16 h-16 text-content-muted dark:text-content-muted/40 mx-auto mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"})],-1)),g[10]||(g[10]=e("h3",{class:"text-content-primary dark:text-content-primary font-medium mb-2"},"No API Tokens",-1)),g[11]||(g[11]=e("p",{class:"text-content-secondary dark:text-content-muted text-sm mb-4"},"Create a token to enable API access",-1)),e("button",{onClick:g[1]||(g[1]=z=>k.value=!0),class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors"}," Create Your First Token ")])),k.value?(o(),r("div",{key:4,class:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm",onClick:Y(n,["self"])},[e("div",As,[g[14]||(g[14]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary mb-4"},"Create API Token",-1)),e("div",Ss,[e("div",null,[g[12]||(g[12]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-muted mb-2"},"Token Name",-1)),S(e("input",{"onUpdate:modelValue":g[2]||(g[2]=z=>i.value=z),type:"text",placeholder:"e.g., Production Server, CI/CD Pipeline",class:"w-full px-4 py-2 bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-400 dark:placeholder-white/40 focus:outline-none focus:border-primary transition-colors",onKeydown:$e(N,["enter"])},null,544),[[V,i.value]]),g[13]||(g[13]=e("p",{class:"text-xs text-content-muted dark:text-content-muted mt-1"},"Give your token a descriptive name to identify its purpose",-1))]),e("div",js,[e("button",{onClick:n,disabled:d.value,class:"px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/10 transition-colors disabled:opacity-50"}," Cancel ",8,Ts),e("button",{onClick:N,disabled:d.value||!i.value.trim(),class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors disabled:opacity-50"},c(d.value?"Creating...":"Create Token"),9,Ns)])])])])):L("",!0),s.value&&u.value?(o(),r("div",{key:5,class:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm",onClick:Y(t,["self"])},[e("div",Bs,[g[19]||(g[19]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary mb-4"},"Token Created Successfully",-1)),e("div",Es,[g[18]||(g[18]=Q('
Save this token now! For security reasons, it will not be shown again.
',1)),e("div",null,[g[16]||(g[16]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-muted mb-2"},"Your API Token",-1)),e("div",Ls,[e("input",{value:u.value,readonly:"",class:"flex-1 px-4 py-2 bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary font-mono text-sm"},null,8,Fs),e("button",{onClick:a,class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors flex items-center gap-2",title:"Copy to clipboard"},g[15]||(g[15]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})],-1),U(" Copy ",-1)]))])]),e("div",Ps,[g[17]||(g[17]=e("p",{class:"text-sm text-blue-200 mb-2"},[e("strong",null,"Usage Example:")],-1)),e("code",zs,' curl -H "X-API-Key: '+c(u.value)+'" '+c(b.value),1)]),e("div",{class:"flex justify-end mt-6"},[e("button",{onClick:t,class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors"}," Done ")])])])])):L("",!0)]),O(Me,{show:p.value,title:"Revoke API Token",message:`Are you sure you want to revoke the token '${y.value?.name}'? This action cannot be undone.`,"confirm-text":"Revoke","cancel-text":"Cancel",variant:"danger",onConfirm:$,onClose:g[3]||(g[3]=z=>p.value=!1)},null,8,["show","message"])],64))}}),Ds={class:"space-y-6"},Is={class:"glass-card rounded-lg border border-stroke-subtle dark:border-stroke/10 p-6"},Hs={class:"space-y-4"},Us={class:"flex items-center justify-between"},Rs=["disabled"],Ks={class:"glass-card rounded-lg border border-stroke-subtle dark:border-stroke/10 p-6"},Os={class:"space-y-4"},qs={class:"space-y-3"},Ws=["checked","disabled"],Gs=["checked","disabled"],Qs={class:"flex items-start gap-3"},Ys={key:0,class:"w-5 h-5 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor"},Xs={key:1,class:"w-5 h-5 text-accent-cyan flex-shrink-0 mt-0.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor"},Js={class:"flex-1"},Zs={class:"text-sm font-medium text-content-primary dark:text-content-primary"},en={key:0,class:"text-xs text-green-600 dark:text-green-400 mt-1"},tn={key:1,class:"p-4 bg-amber-500/10 border border-amber-500/30 rounded-lg"},on={class:"flex items-start justify-between gap-3"},rn=["disabled"],sn={key:0,class:"animate-spin h-4 w-4",fill:"none",viewBox:"0 0 24 24"},nn={key:1,class:"w-4 h-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor"},an={class:"flex items-center space-x-2"},ln={key:0,class:"w-5 h-5 text-green-600 dark:text-green-400",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor"},dn={key:1,class:"w-5 h-5 text-red-600 dark:text-red-400",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor"},cn=G({name:"WebSettings",__name:"WebSettings",setup(I){const _=m(!1),d=m(""),v=m(!1),k=m(!1),i=m(!1),u=m(!1),s=m(!0),p=ce({cors_enabled:!1,use_default_frontend:!0}),y=B(()=>v.value?"bg-green-500/10 border-green-600/40 dark:border-green-500/30":"bg-red-500/10 border-red-500/30");async function h(){try{s.value=!0;const b=await q.get("/check_pymc_console");b.success&&b.data&&(u.value=b.data.exists,console.log("PyMC Console exists:",u.value))}catch(b){console.error("Failed to check PyMC Console:",b),u.value=!1}finally{s.value=!1}}async function N(){try{const b=await q.get("/stats");console.log("WebSettings: Full response:",b);let x=null;if(b.success&&b.data?x=b.data:b&&"version"in b&&(x=b),x){const g=x.config?.web||{};console.log("WebSettings: webConfig:",g),p.cors_enabled=g.cors_enabled===!0,console.log("WebSettings: Set cors_enabled to:",p.cors_enabled);const z=g.web_path;p.use_default_frontend=!z||z==="",console.log("WebSettings: Set use_default_frontend to:",p.use_default_frontend,"from web_path:",z)}}catch(b){console.error("Failed to load web settings:",b),a("Failed to load settings",!1)}}async function M(){_.value=!0,d.value="";try{const b={web:{cors_enabled:p.cors_enabled}};p.use_default_frontend?b.web.web_path=null:b.web.web_path="/opt/pymc_console/web/html";const x=await q.post("/update_web_config",b);x.success?(a("Settings saved successfully",!0),k.value=!0):a(x.error||"Failed to save settings",!1)}catch(b){console.error("Failed to save web settings:",b),a(b.message||"Failed to save settings",!1)}finally{_.value=!1}}async function $(){p.cors_enabled=!p.cors_enabled,await M()}async function n(){p.use_default_frontend=!0,await M()}async function t(){p.use_default_frontend=!1,await M()}function a(b,x){d.value=b,v.value=x,setTimeout(()=>{d.value=""},5e3)}async function A(){i.value=!0,d.value="";try{const b=await q.post("/restart_service",{});b.success?(a("Service restart initiated. Page will reload...",!0),k.value=!1,setTimeout(()=>{window.location.reload()},2e3)):a(b.error||"Failed to restart service",!1)}catch(b){b.code==="ERR_NETWORK"||b.message?.includes("Network error")?(a("Service restarting... Page will reload",!0),k.value=!1,setTimeout(()=>{window.location.reload()},3e3)):(console.error("Failed to restart service:",b),a(b.message||"Failed to restart service",!1))}finally{i.value=!1}}return ae(()=>{N(),h()}),(b,x)=>(o(),r("div",Ds,[e("div",Is,[x[1]||(x[1]=e("div",{class:"flex items-start justify-between mb-4"},[e("div",null,[e("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-1"},"CORS Settings"),e("p",{class:"text-sm text-content-secondary dark:text-content-muted"},"Control cross-origin resource sharing for API access")])],-1)),e("div",Hs,[e("div",Us,[x[0]||(x[0]=e("div",null,[e("label",{class:"text-sm font-medium text-content-primary dark:text-content-primary"},"Enable CORS"),e("p",{class:"text-xs text-content-secondary dark:text-content-muted mt-1"},"Allow web frontends from different origins to access the API")],-1)),e("button",{onClick:$,disabled:_.value,class:D(["relative inline-flex h-6 w-11 items-center rounded-full transition-colors border-2",p.cors_enabled?"bg-cyan-600 dark:bg-teal-500 border-cyan-600 dark:border-teal-500":"bg-gray-400 dark:bg-gray-600 border-gray-400 dark:border-gray-600",_.value?"opacity-50 cursor-not-allowed":"cursor-pointer"])},[e("span",{class:D(["inline-block h-4 w-4 transform rounded-full bg-white transition-transform shadow-lg",p.cors_enabled?"translate-x-5":"translate-x-0.5"])},null,2)],10,Rs)])])]),e("div",Ks,[x[11]||(x[11]=e("div",{class:"flex items-start justify-between mb-4"},[e("div",null,[e("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-1"},"Web Frontend"),e("p",{class:"text-sm text-content-secondary dark:text-content-muted"},"Choose which web interface to use")])],-1)),e("div",Os,[e("div",qs,[e("label",{class:D(["flex items-start space-x-3 p-4 bg-background-mute dark:bg-background/30 rounded-lg border-2 cursor-pointer transition-all",p.use_default_frontend?"border-accent-cyan bg-accent-cyan/10":"border-stroke-subtle dark:border-stroke/10 hover:border-accent-cyan/50"])},[e("input",{type:"radio",name:"frontend",checked:p.use_default_frontend,onChange:n,disabled:_.value,class:"mt-1 h-4 w-4 text-accent-cyan focus:ring-accent-cyan focus:ring-offset-background"},null,40,Ws),x[2]||(x[2]=e("div",{class:"flex-1"},[e("div",{class:"text-sm font-medium text-content-primary dark:text-content-primary"},"Default Frontend"),e("div",{class:"text-xs text-content-secondary dark:text-content-muted mt-1"},"Built-in pyMC Repeater web interface"),e("div",{class:"text-xs text-content-muted dark:text-content-muted/60 mt-1 font-mono"},"Built-in")],-1))],2),e("label",{class:D(["flex items-start space-x-3 p-4 bg-background-mute dark:bg-background/30 rounded-lg border-2 cursor-pointer transition-all",p.use_default_frontend?"border-stroke-subtle dark:border-stroke/10 hover:border-accent-cyan/50":"border-accent-cyan bg-accent-cyan/10"])},[e("input",{type:"radio",name:"frontend",checked:!p.use_default_frontend,onChange:t,disabled:_.value,class:"mt-1 h-4 w-4 text-accent-cyan focus:ring-accent-cyan focus:ring-offset-background"},null,40,Gs),x[3]||(x[3]=Q('
PyMC Console
@Treehouse⚡
Alternative web interface for pyMC Repeater
/opt/pymc_console/web/html
',1))],2)]),s.value?L("",!0):(o(),r("div",{key:0,class:D(["p-4 rounded-lg border",u.value?"bg-green-500/5 border-green-500/20":"bg-accent-cyan/5 border-accent-cyan/20"])},[e("div",Qs,[u.value?(o(),r("svg",Ys,x[4]||(x[4]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)]))):(o(),r("svg",Xs,x[5]||(x[5]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)]))),e("div",Js,[e("h4",Zs,c(u.value?"PyMC Console has been detected":"PyMC Console Not Installed"),1),u.value?(o(),r("p",en,x[6]||(x[6]=[U(" PyMC Console is installed at ",-1),e("code",{class:"text-green-700 dark:text-green-300"},"/opt/pymc_console/web/html",-1)]))):(o(),r(K,{key:1},[x[7]||(x[7]=Q('

PyMC Console must be installed at /opt/pymc_console/web/html before selecting this option.

PyMC Console Install Instructions ',2))],64))])])],2)),k.value?(o(),r("div",tn,[e("div",on,[x[10]||(x[10]=Q('

Service restart required

Web frontend changes will take effect after restarting the pymc-repeater service.

',1)),e("button",{onClick:A,disabled:i.value,class:"px-4 py-2 bg-amber-500 hover:bg-amber-600 disabled:bg-amber-500/50 text-white font-medium rounded-lg transition-colors disabled:cursor-not-allowed flex items-center gap-2 whitespace-nowrap"},[i.value?(o(),r("svg",sn,x[8]||(x[8]=[e("circle",{class:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor","stroke-width":"4"},null,-1),e("path",{class:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"},null,-1)]))):(o(),r("svg",nn,x[9]||(x[9]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))),U(" "+c(i.value?"Restarting...":"Restart Now"),1)],8,rn)])])):L("",!0)])]),d.value?(o(),r("div",{key:0,class:D(["p-4 rounded-lg border",y.value])},[e("div",an,[v.value?(o(),r("svg",ln,x[12]||(x[12]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 13l4 4L19 7"},null,-1)]))):(o(),r("svg",dn,x[13]||(x[13]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"},null,-1)]))),e("span",{class:D(v.value?"text-green-600 dark:text-green-400":"text-red-600 dark:text-red-400")},c(d.value),3)])],2)):L("",!0)]))}}),un={class:"space-y-4"},mn={key:0,class:"bg-green-100 dark:bg-green-500/20 border border-green-500 dark:border-green-500/50 rounded-lg p-3 text-green-700 dark:text-green-400 text-sm"},pn={key:1,class:"bg-red-100 dark:bg-red-500/20 border border-red-500 dark:border-red-500/50 rounded-lg p-3 text-red-700 dark:text-red-400 text-sm"},vn={class:"flex justify-between items-center"},xn={class:"flex gap-2"},bn=["disabled"],kn={class:"flex gap-2"},gn=["disabled"],yn=["disabled"],fn={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},hn={key:0,class:"flex items-center justify-center py-4"},wn={key:1,class:"text-center py-4"},_n={class:"grid grid-cols-2 sm:grid-cols-4 gap-3"},Cn={class:"text-center p-2 bg-white dark:bg-white/5 rounded-lg"},$n={class:"text-center p-2 bg-white dark:bg-white/5 rounded-lg"},Mn={class:"text-lg font-mono text-content-primary dark:text-content-primary"},An={class:"text-center p-2 bg-white dark:bg-white/5 rounded-lg"},Sn={class:"text-lg font-mono text-green-600 dark:text-green-400"},jn={class:"text-center p-2 bg-white dark:bg-white/5 rounded-lg"},Tn={class:"text-lg font-mono text-red-600 dark:text-red-400"},Nn={key:0,class:"mt-2 p-2 bg-red-50 dark:bg-red-500/10 rounded-lg border border-red-200 dark:border-red-500/30"},Bn={key:1,class:"mt-2 p-2 bg-orange-50 dark:bg-orange-500/10 rounded-lg border border-orange-200 dark:border-orange-500/30"},En={class:"font-medium"},Ln={class:"font-mono text-[10px] opacity-70"},Fn={class:"text-[10px]"},Pn={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},zn={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Vn={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Dn={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},In={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Hn={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Un={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Rn={key:1,class:"flex items-center gap-2"},Kn={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 gap-1"},On={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},qn={key:1,class:"flex items-center gap-2"},Wn={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},Gn={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Qn={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Yn={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Xn={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},Jn={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},Zn={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},ea={key:1,class:"flex items-center gap-2"},ta={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},oa={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},ra={key:1,class:"flex items-center gap-2"},sa={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 gap-1"},na={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},aa={key:1,class:"flex items-center gap-2"},la={class:"bg-background-mute dark:bg-white/5 rounded-lg p-3 sm:p-4 space-y-3"},da={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},ia={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},ca={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center py-2 border-b border-stroke-subtle dark:border-stroke/10 gap-1"},ua={key:0,class:"text-content-primary dark:text-content-primary font-mono text-sm"},ma={key:1,class:"flex items-center gap-2"},pa={class:"py-2"},va={class:"grid grid-cols-3 gap-2 mt-2"},xa={class:"text-center p-2 bg-white dark:bg-white/5 rounded-lg"},ba={key:0,class:"font-mono text-sm text-content-primary dark:text-content-primary"},ka={class:"text-center p-2 bg-white dark:bg-white/5 rounded-lg"},ga={key:0,class:"font-mono text-sm text-content-primary dark:text-content-primary"},ya={class:"text-center p-2 bg-white dark:bg-white/5 rounded-lg"},fa={key:0,class:"font-mono text-sm text-content-primary dark:text-content-primary"},ha={class:"p-6 space-y-4"},wa={class:"flex justify-between items-start"},_a={class:"flex justify-end pt-4 border-t border-stroke-subtle dark:border-stroke/20"},Ca=G({__name:"AdvertSettings",setup(I){const _=re(),d=B(()=>_.stats?.config?.repeater||{}),v=B(()=>d.value.advert_rate_limit||{}),k=B(()=>d.value.advert_penalty_box||{}),i=B(()=>d.value.advert_adaptive||{}),u=B(()=>i.value.thresholds||{}),s=m(!1),p=m(!1),y=m(""),h=m(""),N=m(!1),M=m(!1),$=m(null),n=m(!0),t=m(2),a=m(1),A=m(10),b=m(60),x=m(!0),g=m(2),z=m(12),W=m(6),C=m(2),j=m(24),P=m(!0),F=m(.1),E=m(5),w=m(.05),f=m(.2),H=m(.5),R=async()=>{M.value=!0;try{const X=await de.get("/api/advert_rate_limit_stats");X.data?.success&&($.value=X.data.data)}catch(X){console.error("Failed to fetch rate limit stats:",X)}finally{M.value=!1}};te([v,k,i],()=>{console.log("[AdvertSettings] Watch triggered, isEditing:",s.value),s.value?console.log("[AdvertSettings] Watch skipped (editing mode)"):(console.log("[AdvertSettings] Watch loading values from store"),console.log("[AdvertSettings] rateLimitConfig:",v.value),console.log("[AdvertSettings] penaltyConfig:",k.value),console.log("[AdvertSettings] adaptiveConfig:",i.value),n.value=v.value.enabled??!1,t.value=v.value.bucket_capacity??2,a.value=v.value.refill_tokens??1,A.value=Math.round((v.value.refill_interval_seconds??36e3)/3600),b.value=Math.round((v.value.min_interval_seconds??0)/60),x.value=k.value.enabled??!1,g.value=k.value.violation_threshold??2,z.value=Math.round((k.value.violation_decay_seconds??43200)/3600),W.value=Math.round((k.value.base_penalty_seconds??21600)/3600),C.value=k.value.penalty_multiplier??2,j.value=Math.round((k.value.max_penalty_seconds??86400)/3600),P.value=i.value.enabled??!1,F.value=i.value.ewma_alpha??.1,E.value=Math.round((i.value.hysteresis_seconds??300)/60),w.value=u.value.quiet_max??.05,f.value=u.value.normal_max??.2,H.value=u.value.busy_max??.5,console.log("[AdvertSettings] Watch loaded values:"),console.log(" rateLimitEnabled:",n.value),console.log(" minIntervalMinutes:",b.value))},{immediate:!0}),ae(()=>{R()});const se=()=>{console.log("[AdvertSettings] reloadFormValues called"),console.log("[AdvertSettings] rateLimitConfig:",v.value),console.log("[AdvertSettings] penaltyConfig:",k.value),console.log("[AdvertSettings] adaptiveConfig:",i.value),n.value=v.value.enabled??!1,t.value=v.value.bucket_capacity??2,a.value=v.value.refill_tokens??1,A.value=Math.round((v.value.refill_interval_seconds??36e3)/3600),b.value=Math.round((v.value.min_interval_seconds??0)/60),x.value=k.value.enabled??!1,g.value=k.value.violation_threshold??2,z.value=Math.round((k.value.violation_decay_seconds??43200)/3600),W.value=Math.round((k.value.base_penalty_seconds??21600)/3600),C.value=k.value.penalty_multiplier??2,j.value=Math.round((k.value.max_penalty_seconds??86400)/3600),P.value=i.value.enabled??!1,F.value=i.value.ewma_alpha??.1,E.value=Math.round((i.value.hysteresis_seconds??300)/60),w.value=u.value.quiet_max??.05,f.value=u.value.normal_max??.2,H.value=u.value.busy_max??.5,console.log("[AdvertSettings] Form values after reload:"),console.log(" rateLimitEnabled:",n.value),console.log(" minIntervalMinutes:",b.value),console.log(" penaltyEnabled:",x.value),console.log(" adaptiveEnabled:",P.value)},ge=()=>{s.value=!0,y.value="",h.value=""},ye=()=>{s.value=!1,y.value="",h.value="",se()},fe=async()=>{p.value=!0,h.value="",y.value="";try{const X={rate_limit_enabled:n.value,bucket_capacity:t.value,refill_tokens:a.value,refill_interval_seconds:A.value*3600,min_interval_seconds:b.value*60,penalty_enabled:x.value,violation_threshold:g.value,violation_decay_seconds:z.value*3600,base_penalty_seconds:W.value*3600,penalty_multiplier:C.value,max_penalty_seconds:j.value*3600,adaptive_enabled:P.value,ewma_alpha:F.value,hysteresis_seconds:E.value*60,quiet_max:w.value,normal_max:f.value,busy_max:H.value};console.log("[AdvertSettings] Sending save request with payload:",X);const T=(await de.post("/api/update_advert_rate_limit_config",X)).data;console.log("[AdvertSettings] API response:",T),T.success?(y.value=T.data?.message||"Settings saved successfully",console.log("[AdvertSettings] Save successful, fetching updated config..."),await _.fetchStats(),console.log("[AdvertSettings] systemStore.fetchStats() complete"),console.log("[AdvertSettings] rateLimitConfig after fetchStats:",v.value),await R(),console.log("[AdvertSettings] fetchStats() complete"),await ie(),console.log("[AdvertSettings] nextTick() complete, calling reloadFormValues()"),se(),console.log("[AdvertSettings] reloadFormValues() complete, exiting edit mode"),s.value=!1,setTimeout(()=>{y.value=""},3e3)):(h.value=T.error||"Failed to save settings",console.error("[AdvertSettings] Save failed:",T.error))}catch(X){console.error("Failed to save advert settings:",X),h.value=X.response?.data?.error||"Failed to save settings"}finally{p.value=!1}},ue=B(()=>$.value?.adaptive?.current_tier||"unknown"),he=B(()=>{switch(ue.value){case"quiet":return"bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400 border-green-500";case"normal":return"bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-400 border-blue-500";case"busy":return"bg-yellow-100 dark:bg-yellow-500/20 text-yellow-700 dark:text-yellow-400 border-yellow-500";case"congested":return"bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400 border-red-500";default:return"bg-gray-100 dark:bg-gray-500/20 text-gray-700 dark:text-gray-400 border-gray-500"}});return(X,l)=>(o(),r("div",un,[y.value?(o(),r("div",mn,c(y.value),1)):L("",!0),h.value?(o(),r("div",pn,c(h.value),1)):L("",!0),e("div",vn,[e("div",xn,[e("button",{onClick:R,disabled:M.value,class:"px-3 py-1.5 text-xs bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-secondary dark:text-content-muted rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors disabled:opacity-50"},c(M.value?"Loading...":"Refresh Stats"),9,bn),e("button",{onClick:l[0]||(l[0]=T=>N.value=!0),class:"px-3 py-1.5 text-xs bg-blue-100 dark:bg-blue-500/20 hover:bg-blue-200 dark:hover:bg-blue-500/30 text-blue-700 dark:text-blue-400 rounded-lg border border-blue-500/50 transition-colors",title:"How rate limiting works"},l[19]||(l[19]=[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)]))]),e("div",kn,[s.value?(o(),r(K,{key:1},[e("button",{onClick:ye,disabled:p.value,class:"px-3 sm:px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/20 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"}," Cancel ",8,gn),e("button",{onClick:fe,disabled:p.value,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"},c(p.value?"Saving...":"Save Changes"),9,yn)],64)):(o(),r("button",{key:0,onClick:ge,class:"px-3 sm:px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors text-sm"}," Edit Settings "))])]),e("div",fn,[l[28]||(l[28]=e("h3",{class:"text-sm font-medium text-content-primary dark:text-content-primary"},"Current Status",-1)),M.value&&!$.value?(o(),r("div",hn,l[20]||(l[20]=[e("div",{class:"animate-spin w-5 h-5 border-2 border-stroke-subtle dark:border-stroke/20 border-t-cyan-500 dark:border-t-primary rounded-full"},null,-1),e("span",{class:"ml-2 text-sm text-content-muted"},"Loading stats...",-1)]))):$.value?(o(),r(K,{key:2},[e("div",_n,[e("div",Cn,[l[22]||(l[22]=e("div",{class:"text-xs text-content-muted dark:text-content-muted"},"Mesh Tier",-1)),e("div",{class:D(["mt-1 px-2 py-0.5 rounded border text-xs font-medium inline-block",he.value])},c(ue.value.toUpperCase()),3)]),e("div",$n,[l[23]||(l[23]=e("div",{class:"text-xs text-content-muted dark:text-content-muted"},"Adverts/min",-1)),e("div",Mn,c($.value.metrics?.adverts_per_min_ewma?.toFixed(2)||"0.00"),1)]),e("div",An,[l[24]||(l[24]=e("div",{class:"text-xs text-content-muted dark:text-content-muted"},"Allowed",-1)),e("div",Sn,c($.value.stats?.adverts_allowed||0),1)]),e("div",jn,[l[25]||(l[25]=e("div",{class:"text-xs text-content-muted dark:text-content-muted"},"Dropped",-1)),e("div",Tn,c($.value.stats?.adverts_dropped||0),1)])]),Object.keys($.value.active_penalties||{}).length>0?(o(),r("div",Nn,[l[26]||(l[26]=e("div",{class:"text-xs font-medium text-red-700 dark:text-red-400 mb-1"},"Active Penalties",-1)),(o(!0),r(K,null,Z($.value.active_penalties,(T,le)=>(o(),r("div",{key:le,class:"text-xs font-mono text-red-600 dark:text-red-400"},c(le)+"... - "+c(Math.round(T))+"s remaining ",1))),128))])):L("",!0),$.value.recent_drops&&$.value.recent_drops.length>0?(o(),r("div",Bn,[l[27]||(l[27]=e("div",{class:"text-xs font-medium text-orange-700 dark:text-orange-400 mb-1"},"Recently Dropped Adverts",-1)),(o(!0),r(K,null,Z($.value.recent_drops,(T,le)=>(o(),r("div",{key:le,class:"text-xs text-orange-600 dark:text-orange-400 py-0.5"},[e("span",En,c(T.name),1),e("span",Ln,"("+c(T.pubkey)+"...)",1),e("span",Fn," - "+c(T.reason)+" ("+c(T.seconds_ago)+"s ago)",1)]))),128))])):L("",!0)],64)):(o(),r("div",wn,l[21]||(l[21]=[e("p",{class:"text-xs text-content-muted dark:text-content-muted"},' Stats not available. Click "Refresh Stats" to load. ',-1)])))]),e("div",Pn,[l[36]||(l[36]=e("h3",{class:"text-sm font-medium text-content-primary dark:text-content-primary flex items-center gap-2"},[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"})]),U(" Token Bucket Rate Limiting ")],-1)),l[37]||(l[37]=e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Controls how many adverts each pubkey can send in a given time period.",-1)),e("div",zn,[l[30]||(l[30]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Rate Limiting",-1)),s.value?S((o(),r("select",{key:1,"onUpdate:modelValue":l[1]||(l[1]=T=>n.value=T),class:"w-full sm:w-32 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},l[29]||(l[29]=[e("option",{value:!0},"Enabled",-1),e("option",{value:!1},"Disabled",-1)]),512)),[[oe,n.value]]):(o(),r("div",Vn,c(n.value?"Enabled":"Disabled"),1))]),e("div",Dn,[l[31]||(l[31]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Bucket Capacity"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Max burst size (adverts)")],-1)),s.value?S((o(),r("input",{key:1,"onUpdate:modelValue":l[2]||(l[2]=T=>t.value=T),type:"number",min:"1",max:"10",class:"w-full sm:w-24 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512)),[[V,t.value,void 0,{number:!0}]]):(o(),r("div",In,c(t.value),1))]),e("div",Hn,[l[33]||(l[33]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Refill Interval"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Time between token refills")],-1)),s.value?(o(),r("div",Rn,[S(e("input",{"onUpdate:modelValue":l[3]||(l[3]=T=>A.value=T),type:"number",min:"1",max:"48",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,A.value,void 0,{number:!0}]]),l[32]||(l[32]=e("span",{class:"text-content-muted text-sm"},"hours",-1))])):(o(),r("div",Un,c(A.value)+" hours",1))]),e("div",Kn,[l[35]||(l[35]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Minimum Interval"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Hard minimum between adverts")],-1)),s.value?(o(),r("div",qn,[S(e("input",{"onUpdate:modelValue":l[4]||(l[4]=T=>b.value=T),type:"number",min:"0",max:"1440",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,b.value,void 0,{number:!0}]]),l[34]||(l[34]=e("span",{class:"text-content-muted text-sm"},"min",-1))])):(o(),r("div",On,c(b.value)+" min",1))])]),e("div",Wn,[l[47]||(l[47]=e("h3",{class:"text-sm font-medium text-content-primary dark:text-content-primary flex items-center gap-2"},[e("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"})]),U(" Penalty Box (Repeat Offenders) ")],-1)),l[48]||(l[48]=e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Applies escalating cooldowns to pubkeys that repeatedly violate limits.",-1)),e("div",Gn,[l[39]||(l[39]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Penalty Box",-1)),s.value?S((o(),r("select",{key:1,"onUpdate:modelValue":l[5]||(l[5]=T=>x.value=T),class:"w-full sm:w-32 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},l[38]||(l[38]=[e("option",{value:!0},"Enabled",-1),e("option",{value:!1},"Disabled",-1)]),512)),[[oe,x.value]]):(o(),r("div",Qn,c(x.value?"Enabled":"Disabled"),1))]),e("div",Yn,[l[40]||(l[40]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Violation Threshold"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Violations before penalty")],-1)),s.value?S((o(),r("input",{key:1,"onUpdate:modelValue":l[6]||(l[6]=T=>g.value=T),type:"number",min:"1",max:"10",class:"w-full sm:w-24 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512)),[[V,g.value,void 0,{number:!0}]]):(o(),r("div",Xn,c(g.value),1))]),e("div",Jn,[l[42]||(l[42]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Base Penalty Duration"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"First penalty duration")],-1)),s.value?(o(),r("div",ea,[S(e("input",{"onUpdate:modelValue":l[7]||(l[7]=T=>W.value=T),type:"number",min:"1",max:"48",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,W.value,void 0,{number:!0}]]),l[41]||(l[41]=e("span",{class:"text-content-muted text-sm"},"hours",-1))])):(o(),r("div",Zn,c(W.value)+" hours",1))]),e("div",ta,[l[44]||(l[44]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Penalty Multiplier"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Escalation factor")],-1)),s.value?(o(),r("div",ra,[S(e("input",{"onUpdate:modelValue":l[8]||(l[8]=T=>C.value=T),type:"number",min:"1",max:"5",step:"0.5",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,C.value,void 0,{number:!0}]]),l[43]||(l[43]=e("span",{class:"text-content-muted text-sm"},"x",-1))])):(o(),r("div",oa,c(C.value)+"x",1))]),e("div",sa,[l[46]||(l[46]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Max Penalty Duration"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Maximum cooldown cap")],-1)),s.value?(o(),r("div",aa,[S(e("input",{"onUpdate:modelValue":l[9]||(l[9]=T=>j.value=T),type:"number",min:"1",max:"168",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,j.value,void 0,{number:!0}]]),l[45]||(l[45]=e("span",{class:"text-content-muted text-sm"},"hours",-1))])):(o(),r("div",na,c(j.value)+" hours",1))])]),e("div",la,[l[58]||(l[58]=Q('

Adaptive Rate Limiting

How the three systems work together: Each layer can be enabled/disabled independently and the others will still function.

  • Rate Limiting OFF: All limiting disabled — adverts pass through freely
  • Adaptive OFF: Token bucket uses fixed limits (no tier scaling), penalty box still works
  • Penalty Box OFF: Token bucket still applies, but no escalating cooldowns for repeat offenders

Decision flow when all enabled: Adaptive tier check → Penalty box check → Token bucket check → Violation recording (triggers penalty box)

Activity tiers:Quiet (bypass limiting) → Normal (1x limits) → Busy (0.5x capacity) → Congested (0.25x capacity)

',2)),e("div",da,[l[50]||(l[50]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Adaptive Mode",-1)),s.value?S((o(),r("select",{key:1,"onUpdate:modelValue":l[10]||(l[10]=T=>P.value=T),class:"w-full sm:w-32 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},l[49]||(l[49]=[e("option",{value:!0},"Enabled",-1),e("option",{value:!1},"Disabled",-1)]),512)),[[oe,P.value]]):(o(),r("div",ia,c(P.value?"Enabled":"Disabled"),1))]),e("div",ca,[l[52]||(l[52]=e("div",null,[e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Tier Change Delay"),e("p",{class:"text-xs text-content-muted dark:text-content-muted"},"Prevents tier flapping")],-1)),s.value?(o(),r("div",ma,[S(e("input",{"onUpdate:modelValue":l[11]||(l[11]=T=>E.value=T),type:"number",min:"0",max:"60",class:"w-20 px-3 py-1.5 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary"},null,512),[[V,E.value,void 0,{number:!0}]]),l[51]||(l[51]=e("span",{class:"text-content-muted text-sm"},"min",-1))])):(o(),r("div",ua,c(E.value)+" min",1))]),e("div",pa,[l[56]||(l[56]=e("span",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm mb-2 block"},"Activity Tier Thresholds (adverts/min)",-1)),e("div",va,[e("div",xa,[l[53]||(l[53]=e("div",{class:"text-xs text-green-600 dark:text-green-400 mb-1"},"Quiet Max",-1)),s.value?S((o(),r("input",{key:1,"onUpdate:modelValue":l[12]||(l[12]=T=>w.value=T),type:"number",min:"0",max:"1",step:"0.01",class:"w-full px-2 py-1 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded text-content-primary dark:text-content-primary text-sm text-center focus:outline-none focus:border-primary"},null,512)),[[V,w.value,void 0,{number:!0}]]):(o(),r("div",ba,c(w.value),1))]),e("div",ka,[l[54]||(l[54]=e("div",{class:"text-xs text-blue-600 dark:text-blue-400 mb-1"},"Normal Max",-1)),s.value?S((o(),r("input",{key:1,"onUpdate:modelValue":l[13]||(l[13]=T=>f.value=T),type:"number",min:"0",max:"5",step:"0.01",class:"w-full px-2 py-1 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded text-content-primary dark:text-content-primary text-sm text-center focus:outline-none focus:border-primary"},null,512)),[[V,f.value,void 0,{number:!0}]]):(o(),r("div",ga,c(f.value),1))]),e("div",ya,[l[55]||(l[55]=e("div",{class:"text-xs text-yellow-600 dark:text-yellow-400 mb-1"},"Busy Max",-1)),s.value?S((o(),r("input",{key:1,"onUpdate:modelValue":l[14]||(l[14]=T=>H.value=T),type:"number",min:"0",max:"10",step:"0.01",class:"w-full px-2 py-1 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded text-content-primary dark:text-content-primary text-sm text-center focus:outline-none focus:border-primary"},null,512)),[[V,H.value,void 0,{number:!0}]]):(o(),r("div",fa,c(H.value),1))])]),l[57]||(l[57]=e("p",{class:"text-xs text-content-muted dark:text-content-muted mt-2"},"Above Busy Max = Congested tier (strictest limiting)",-1))])]),N.value?(o(),r("div",{key:2,class:"fixed inset-0 bg-black/50 flex items-start justify-center z-50 p-4 overflow-y-auto",onClick:l[18]||(l[18]=Y(T=>N.value=!1,["self"]))},[e("div",{class:"bg-background dark:bg-background-dark rounded-lg shadow-xl max-w-3xl w-full my-8",onClick:l[17]||(l[17]=Y(()=>{},["stop"]))},[e("div",ha,[e("div",wa,[l[60]||(l[60]=e("h2",{class:"text-xl font-semibold text-content-primary dark:text-content-primary"},"How Advert Rate Limiting Works",-1)),e("button",{onClick:l[15]||(l[15]=T=>N.value=!1),class:"text-content-muted hover:text-content-primary dark:text-content-muted dark:hover:text-content-primary"},l[59]||(l[59]=[e("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),l[61]||(l[61]=Q('

Why you may see the same advert more than once

Mesh traffic can reach your repeater through different paths, so duplicate advert packets are expected.

  • First copy arrives and is forwarded
  • Second copy arrives through another repeater path
  • Later copies may be dropped once limits are hit

This is normal behavior and helps prevent repeated rebroadcasts from flooding the mesh.

Token Bucket Rate Limiting

Each sender has a token bucket. Every forwarded advert uses one token.

  • Bucket Capacity: How many adverts can pass in a burst.
  • Refill Rate: How quickly tokens come back over time.
  • Min Interval: Optional gap between adverts from the same sender (usually set to 0).
Example (capacity 2):
- Copy 1 forwarded (2 → 1 tokens)
- Copy 2 forwarded (1 → 0 tokens)
- Copy 3 dropped (no tokens left)

Penalty Box (Repeat Offenders)

If a sender keeps hitting the limit, it is temporarily blocked.

  • Violation Threshold: How many hits before penalty starts.
  • Base Penalty: First block duration.
  • Multiplier: Repeated penalties get longer.
  • Decay Time: Violations age out after stable behavior.

Adaptive Mesh Activity Tiers

Adaptive mode adjusts limits based on recent advert activity.

How Congestion is Measured:
  • What is counted: Advert packets only (not chat/data traffic)
  • Smoothing: 60-second EWMA to avoid reacting to short spikes
  • Score: Tier is based on adverts per minute
  • Hysteresis: Tier changes must hold for 5 minutes
QUIET
Activity < 0.05/min
No rate limiting
NORMAL
Activity 0.05-0.20/min
Light limiting (50%)
BUSY
Activity 0.20-0.50/min
Standard limiting (100%)
CONGESTED
Activity > 0.50/min
Aggressive (200%)
Quick examples:
- 0.02 adverts/min → QUIET (bypass)
- 0.35 adverts/min → BUSY (tighter limits)
- 0.68 adverts/min → CONGESTED (strict limits)

Recommended starting settings

  • Min Interval: 0 (disabled), let adaptive mode do the work
  • Bucket Capacity: 2-3 tokens for normal mesh propagation
  • Adaptive Mode: On
  • Penalty Box: On
',5)),e("div",_a,[e("button",{onClick:l[16]||(l[16]=T=>N.value=!1),class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-content-primary dark:text-content-primary rounded-lg border border-primary/50 transition-colors"}," Got it! ")])])])])):L("",!0)]))}}),$a={class:"p-3 sm:p-6 space-y-4 sm:space-y-6"},Ma={class:"glass-card rounded-[15px] z-10 p-3 sm:p-4 border border-cyan-400 dark:border-primary/30 bg-cyan-500/10 dark:bg-primary/10"},Aa={class:"text-cyan-700 dark:text-primary text-sm sm:text-base"},Sa={class:"mt-1 sm:mt-2 text-cyan-600 dark:text-primary/80"},ja={class:"glass-card rounded-[15px] p-3 sm:p-6"},Ta={class:"flex overflow-x-auto border-b border-stroke-subtle dark:border-stroke/10 mb-4 sm:mb-6 -mx-3 px-3 sm:mx-0 sm:px-0 scrollbar-hide"},Na=["onClick"],Ba={class:"flex items-center gap-1 sm:gap-2"},Ea={key:0,class:"w-3.5 h-3.5 sm:w-4 sm:h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},La={key:1,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Fa={key:2,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Pa={key:3,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},za={key:4,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Va={key:5,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Da={key:6,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Ia={key:7,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Ha={class:"min-h-[400px]"},Ua={key:0,class:"flex items-center justify-center py-12"},Ra={key:1,class:"flex items-center justify-center py-12"},Ka={class:"text-center"},Oa={class:"text-content-secondary dark:text-content-muted text-sm mb-4"},qa={key:2},Xa=G({name:"ConfigurationView",__name:"Configuration",setup(I){const _=re(),d=m(Ae("configuration_activeTab","radio")),v=m(!1);te(d,u=>Se("configuration_activeTab",u));const k=[{id:"radio",label:"Radio Settings",icon:"radio"},{id:"repeater",label:"Repeater Settings",icon:"repeater"},{id:"advert",label:"Advert Limits",icon:"advert"},{id:"duty",label:"Duty Cycle",icon:"duty"},{id:"delays",label:"TX Delays",icon:"delays"},{id:"transport",label:"Regions/Keys",icon:"keys"},{id:"api-tokens",label:"API Tokens",icon:"tokens"},{id:"web",label:"Web Options",icon:"web"}];ae(async()=>{try{await _.fetchStats(),v.value=!0}catch(u){console.error("Failed to load configuration data:",u),v.value=!0}});function i(u){d.value=u}return(u,s)=>{const p=be("router-link");return o(),r("div",$a,[s[14]||(s[14]=e("div",null,[e("h1",{class:"text-xl sm:text-2xl font-bold text-content-primary dark:text-content-primary"},"Configuration"),e("p",{class:"text-content-secondary dark:text-content-muted mt-1 sm:mt-2 text-sm sm:text-base"},"System configuration and settings")],-1)),e("div",Ma,[e("div",Aa,[s[3]||(s[3]=e("strong",null,"CAD Calibration Tool Available",-1)),e("p",Sa,[s[2]||(s[2]=U(" Optimize your Channel Activity Detection settings. ",-1)),O(p,{to:"/cad-calibration",class:"underline hover:text-cyan-800 dark:hover:text-primary transition-colors"},{default:ve(()=>s[1]||(s[1]=[U(" Launch CAD Calibration Tool → ",-1)])),_:1,__:[1]})])])]),e("div",ja,[e("div",Ta,[(o(),r(K,null,Z(k,y=>e("button",{key:y.id,onClick:h=>i(y.id),class:D(["px-3 sm:px-4 py-2 text-xs sm:text-sm font-medium transition-colors duration-200 border-b-2 mr-3 sm:mr-6 whitespace-nowrap flex-shrink-0",d.value===y.id?"text-cyan-500 dark:text-primary border-cyan-500 dark:border-primary":"text-content-secondary dark:text-content-muted border-transparent hover:text-content-primary dark:hover:text-content-primary hover:border-stroke-subtle dark:hover:border-stroke/30"])},[e("div",Ba,[y.icon==="radio"?(o(),r("svg",Ea,s[4]||(s[4]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.822c5.716-5.716 14.976-5.716 20.692 0"},null,-1)]))):y.icon==="repeater"?(o(),r("svg",La,s[5]||(s[5]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 12h14M5 12l4-4m-4 4l4 4"},null,-1)]))):y.icon==="advert"?(o(),r("svg",Fa,s[6]||(s[6]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"},null,-1)]))):y.icon==="duty"?(o(),r("svg",Pa,s[7]||(s[7]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)]))):y.icon==="delays"?(o(),r("svg",za,s[8]||(s[8]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"},null,-1)]))):y.icon==="keys"?(o(),r("svg",Va,s[9]||(s[9]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"},null,-1)]))):y.icon==="tokens"?(o(),r("svg",Da,s[10]||(s[10]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"},null,-1)]))):y.icon==="web"?(o(),r("svg",Ia,s[11]||(s[11]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"},null,-1)]))):L("",!0),U(" "+c(y.label),1)])],10,Na)),64))]),e("div",Ha,[!v.value&&J(_).isLoading?(o(),r("div",Ua,s[12]||(s[12]=[e("div",{class:"text-center"},[e("div",{class:"animate-spin w-8 h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-cyan-500 dark:border-t-primary rounded-full mx-auto mb-4"}),e("div",{class:"text-content-secondary dark:text-content-muted"},"Loading configuration...")],-1)]))):J(_).error&&!v.value?(o(),r("div",Ra,[e("div",Ka,[s[13]||(s[13]=e("div",{class:"text-red-500 dark:text-red-400 mb-2"},"Failed to load configuration",-1)),e("div",Oa,c(J(_).error),1),e("button",{onClick:s[0]||(s[0]=y=>J(_).fetchStats()),class:"px-4 py-2 bg-cyan-500/20 dark:bg-primary/20 hover:bg-cyan-500/30 dark:hover:bg-primary/30 text-cyan-900 dark:text-white rounded-lg border border-cyan-500/50 dark:border-primary/50 transition-colors"}," Retry ")])])):(o(),r("div",qa,[S(e("div",null,[O(st,{key:"radio-settings"})],512),[[ee,d.value==="radio"]]),S(e("div",null,[O(zt,{key:"repeater-settings"})],512),[[ee,d.value==="repeater"]]),S(e("div",null,[O(Ca,{key:"advert-settings"})],512),[[ee,d.value==="advert"]]),S(e("div",null,[O(Qt,{key:"duty-cycle"})],512),[[ee,d.value==="duty"]]),S(e("div",null,[O(co,{key:"transmission-delays"})],512),[[ee,d.value==="delays"]]),S(e("div",null,[O(cs,{key:"transport-keys"})],512),[[ee,d.value==="transport"]]),S(e("div",null,[O(Vs,{key:"api-tokens"})],512),[[ee,d.value==="api-tokens"]]),S(e("div",null,[O(cn,{key:"web-settings"})],512),[[ee,d.value==="web"]])]))])])])}}});export{Xa as default}; diff --git a/repeater/web/html/assets/ConfirmDialog.vue_vue_type_script_setup_true_lang-CVxh_fqf.js b/repeater/web/html/assets/ConfirmDialog.vue_vue_type_script_setup_true_lang-CVxh_fqf.js index b79cc3b..bf32ae0 100644 --- a/repeater/web/html/assets/ConfirmDialog.vue_vue_type_script_setup_true_lang-CVxh_fqf.js +++ b/repeater/web/html/assets/ConfirmDialog.vue_vue_type_script_setup_true_lang-CVxh_fqf.js @@ -1 +1,5 @@ +<<<<<<<< HEAD:repeater/web/html/assets/ConfirmDialog.vue_vue_type_script_setup_true_lang-CVxh_fqf.js import{a as p,b as n,g as m,e as t,s as g,t as s,j as d,p as l}from"./index-DyUIpN7m.js";const f={class:"flex items-center justify-between mb-4"},w={class:"text-xl font-semibold text-content-primary dark:text-content-primary"},v={class:"mb-6"},h={key:0,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},y={key:1,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},C={key:2,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},B={class:"text-content-secondary dark:text-content-primary/80 text-base leading-relaxed"},j={class:"flex gap-3"},_=p({__name:"ConfirmDialog",props:{show:{type:Boolean},title:{default:"Confirm Action"},message:{},confirmText:{default:"Confirm"},cancelText:{default:"Cancel"},variant:{default:"warning"}},emits:["close","confirm"],setup(c,{emit:b}){const o=c,r=b,u=i=>{i.target===i.currentTarget&&r("close")},k={danger:"bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400",warning:"bg-yellow-100 dark:bg-yellow-500/20 border-yellow-500/30 text-yellow-600 dark:text-yellow-400",info:"bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400"},x={danger:"bg-red-500 hover:bg-red-600",warning:"bg-yellow-500 hover:bg-yellow-600",info:"bg-blue-500 hover:bg-blue-600"};return(i,e)=>o.show?(l(),n("div",{key:0,onClick:u,class:"fixed inset-0 bg-black/40 backdrop-blur-lg z-[99999] flex items-center justify-center p-4",style:{"backdrop-filter":"blur(8px) saturate(180%)",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"}},[t("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10",onClick:e[3]||(e[3]=g(()=>{},["stop"]))},[t("div",f,[t("h3",w,s(o.title),1),t("button",{onClick:e[0]||(e[0]=a=>r("close")),class:"text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors"},e[4]||(e[4]=[t("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),t("div",v,[t("div",{class:d(["inline-flex p-3 rounded-xl mb-4",k[o.variant]])},[o.variant==="danger"?(l(),n("svg",h,e[5]||(e[5]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"},null,-1)]))):o.variant==="warning"?(l(),n("svg",y,e[6]||(e[6]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"},null,-1)]))):(l(),n("svg",C,e[7]||(e[7]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)])))],2),t("p",B,s(o.message),1)]),t("div",j,[t("button",{onClick:e[1]||(e[1]=a=>r("close")),class:"flex-1 px-4 py-3 rounded-xl bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary transition-all duration-200 border border-stroke-subtle dark:border-stroke/10"},s(o.cancelText),1),t("button",{onClick:e[2]||(e[2]=a=>r("confirm")),class:d(["flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200",x[o.variant]])},s(o.confirmText),3)])])])):m("",!0)}});export{_}; +======== +import{a as p,b as n,g as m,e as t,s as g,t as s,j as d,p as l}from"./index-BfUIlcDy.js";const f={class:"flex items-center justify-between mb-4"},w={class:"text-xl font-semibold text-content-primary dark:text-content-primary"},v={class:"mb-6"},h={key:0,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},y={key:1,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},C={key:2,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},B={class:"text-content-secondary dark:text-content-primary/80 text-base leading-relaxed"},j={class:"flex gap-3"},_=p({__name:"ConfirmDialog",props:{show:{type:Boolean},title:{default:"Confirm Action"},message:{},confirmText:{default:"Confirm"},cancelText:{default:"Cancel"},variant:{default:"warning"}},emits:["close","confirm"],setup(c,{emit:b}){const o=c,r=b,u=i=>{i.target===i.currentTarget&&r("close")},k={danger:"bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400",warning:"bg-yellow-100 dark:bg-yellow-500/20 border-yellow-500/30 text-yellow-600 dark:text-yellow-400",info:"bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400"},x={danger:"bg-red-500 hover:bg-red-600",warning:"bg-yellow-500 hover:bg-yellow-600",info:"bg-blue-500 hover:bg-blue-600"};return(i,e)=>o.show?(l(),n("div",{key:0,onClick:u,class:"fixed inset-0 bg-black/40 backdrop-blur-lg z-[99999] flex items-center justify-center p-4",style:{"backdrop-filter":"blur(8px) saturate(180%)",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"}},[t("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10",onClick:e[3]||(e[3]=g(()=>{},["stop"]))},[t("div",f,[t("h3",w,s(o.title),1),t("button",{onClick:e[0]||(e[0]=a=>r("close")),class:"text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors"},e[4]||(e[4]=[t("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),t("div",v,[t("div",{class:d(["inline-flex p-3 rounded-xl mb-4",k[o.variant]])},[o.variant==="danger"?(l(),n("svg",h,e[5]||(e[5]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"},null,-1)]))):o.variant==="warning"?(l(),n("svg",y,e[6]||(e[6]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"},null,-1)]))):(l(),n("svg",C,e[7]||(e[7]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)])))],2),t("p",B,s(o.message),1)]),t("div",j,[t("button",{onClick:e[1]||(e[1]=a=>r("close")),class:"flex-1 px-4 py-3 rounded-xl bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary transition-all duration-200 border border-stroke-subtle dark:border-stroke/10"},s(o.cancelText),1),t("button",{onClick:e[2]||(e[2]=a=>r("confirm")),class:d(["flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200",x[o.variant]])},s(o.confirmText),3)])])])):m("",!0)}});export{_}; +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/ConfirmDialog.vue_vue_type_script_setup_true_lang-K0cwbhb6.js diff --git a/repeater/web/html/assets/Dashboard-BBy-PU0D.js b/repeater/web/html/assets/Dashboard-BBy-PU0D.js new file mode 100644 index 0000000..688489a --- /dev/null +++ b/repeater/web/html/assets/Dashboard-BBy-PU0D.js @@ -0,0 +1,2 @@ +import{C as wt,a as Nt,L as Bt,P as Ft,b as Et,c as jt,i as It}from"./chart-B185MtDy.js";import{a as dt,r as B,c as K,D as tt,o as yt,E as it,H as vt,b as l,e as t,t as n,g as k,n as ct,I as Lt,p as r,x as pt,J as gt,K as kt,f as et,F as V,h as O,L as ft,M as Ut,i as St,u as ut,k as rt,N as Vt,T as Ht,l as zt,O as Xt,j as T,s as Gt,w as $t,q as Tt}from"./index-BfUIlcDy.js";import{u as Ot}from"./useSignalQuality-BkE4M-We.js";import{g as Rt,s as Ct}from"./preferences-DtwbSSgO.js";const Wt={class:"sparkline-card"},Qt={class:"card-header"},qt={class:"card-title"},Kt={class:"card-values"},Jt={class:"card-chart"},Yt=dt({name:"ChartSparkline",__name:"ChartSparkline",props:{title:{},value:{},color:{},data:{default:()=>[]},showChart:{type:Boolean,default:!0},secondaryValue:{default:void 0},secondaryLabel:{default:""},secondaryColor:{default:""},secondaryData:{default:()=>[]}},setup(at){wt.register(Nt,Bt,Ft,Et,jt,It);const _=at,W=B(null),m=B(null),R=h=>{if(h.length<3)return h;const F=Math.min(15,Math.max(3,Math.floor(h.length*.2))),X=[];for(let C=0;CM+w,0)/g.length)}const z=Math.min(12,X.length),E=X.length/z,D=[];for(let C=0;C!_.data||_.data.length===0?[]:R(_.data)),P=K(()=>!_.secondaryData||_.secondaryData.length===0?[]:R(_.secondaryData)),H=()=>{if(!W.value)return;const h=W.value.getContext("2d");if(!h)return;m.value&&(m.value.destroy(),m.value=null);const F=$.value;if(F.length<2)return;const X=[{data:F,borderColor:_.color,borderWidth:2.5,fill:!1,tension:.4,pointRadius:0,pointHoverRadius:0}],z=P.value;z.length>=2&&_.secondaryColor&&X.push({data:z,borderColor:_.secondaryColor,borderWidth:2,borderDash:[4,3],fill:!1,tension:.4,pointRadius:0,pointHoverRadius:0}),m.value=Lt(new wt(h,{type:"line",data:{labels:F.map((E,D)=>D.toString()),datasets:X},options:{responsive:!0,maintainAspectRatio:!1,animation:{duration:800,easing:"easeOutQuart"},plugins:{legend:{display:!1},tooltip:{enabled:!1}},scales:{x:{display:!1,grid:{display:!1}},y:{display:!1,grid:{display:!1},grace:"10%"}},elements:{line:{capBezierPoints:!0}}}}))},A=()=>{if(!m.value){H();return}const h=$.value;if(h.length<2)return;m.value.data.labels=h.map((X,z)=>z.toString()),m.value.data.datasets[0].data=h;const F=P.value;F.length>=2&&_.secondaryColor&&(m.value.data.datasets.length<2?m.value.data.datasets.push({data:F,borderColor:_.secondaryColor,borderWidth:2,borderDash:[4,3],fill:!1,tension:.4,pointRadius:0,pointHoverRadius:0}):m.value.data.datasets[1].data=F),m.value.update("default")};return tt(()=>_.data,()=>{it(()=>A())},{deep:!0}),tt(()=>_.color,()=>{m.value&&(m.value.data.datasets[0].borderColor=_.color,m.value.update("none"))}),yt(()=>{it(()=>H())}),vt(()=>{m.value&&(m.value.destroy(),m.value=null)}),(h,F)=>(r(),l("div",Wt,[t("div",Qt,[t("p",qt,n(h.title),1),t("div",Kt,[t("span",{class:"card-value",style:ct({color:h.color})},n(typeof h.value=="number"?h.value.toLocaleString():h.value),5),h.secondaryValue!==void 0?(r(),l("span",{key:0,class:"card-secondary-value",style:ct({color:h.secondaryColor})},n(h.secondaryLabel)+n(typeof h.secondaryValue=="number"?h.secondaryValue.toLocaleString():h.secondaryValue),5)):k("",!0)])]),t("div",Jt,[h.showChart?(r(),l("canvas",{key:0,ref_key:"canvasRef",ref:W},null,512)):k("",!0)])]))}}),mt=pt(Yt,[["__scopeId","data-v-814635af"]]),Zt={class:"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3 lg:gap-4 mb-5 stats-cards-container"},te=dt({name:"StatsCards",__name:"StatsCards",setup(at){const _=gt(),W=kt(),m=B(null),R=B(null),$=B(!1),P=K(()=>{const h=_.packetStats,F=_.systemStats,X=C=>{const x=Math.floor(C/86400),d=Math.floor(C%86400/3600),v=Math.floor(C%3600/60);return x>0?`${x}d ${d}h`:d>0?`${d}h ${v}m`:`${v}m`},z=h?.total_packets||0,E=h?.dropped_packets||0,D=z>0?Math.round(E/z*100):0;return{packetsReceived:z,packetsForwarded:h?.transmitted_packets||0,uptimeFormatted:F?X(F.uptime_seconds||0):"0m",uptimeHours:F?Math.floor((F.uptime_seconds||0)/3600):0,droppedPackets:E,dropPercent:`${D}%`,signalQuality:Math.round((h?.avg_rssi||0)+120),crcErrorCount:_.crcErrorCount}}),H=K(()=>_.sparklineData),A=async()=>{if(!$.value)try{$.value=!0,await Promise.all([_.fetchSystemStats(),_.fetchPacketStats({hours:24})]),await it()}catch(h){console.error("Error fetching stats:",h)}finally{$.value=!1}};return yt(async()=>{await _.initializeSparklineHistory(),A(),W.isConnected||(m.value=window.setInterval(A,3e4)),R.value=window.setInterval(()=>{_.interpolateRates()},6e4)}),tt(()=>W.isConnected,h=>{h?m.value&&(clearInterval(m.value),m.value=null):m.value||(m.value=window.setInterval(A,3e4))}),vt(()=>{m.value&&clearInterval(m.value),R.value&&clearInterval(R.value)}),(h,F)=>(r(),l("div",Zt,[et(mt,{title:"Up Time",value:P.value.uptimeFormatted,color:"#EBA0FC",data:[],showChart:!1,class:"stat-card"},null,8,["value"]),et(mt,{title:"RX Packets",value:P.value.packetsReceived,color:"#AAE8E8",data:H.value.totalPackets,class:"stat-card"},null,8,["value","data"]),et(mt,{title:"Forward",value:P.value.packetsForwarded,color:"#FFC246",data:H.value.transmittedPackets,class:"stat-card"},null,8,["value","data"]),et(mt,{title:"Dropped",value:P.value.droppedPackets,color:"#FB787B",data:H.value.droppedPackets,class:"stat-card"},null,8,["value","data"]),et(mt,{title:"CRC Errors",value:P.value.crcErrorCount,color:"#F59E0B",data:H.value.crcErrors,class:"stat-card"},null,8,["value","data"])]))}}),ee=pt(te,[["__scopeId","data-v-84cee3fb"]]),se={class:"glass-card rounded-[10px] p-4 lg:p-6"},ae={class:"h-48 lg:h-56 relative"},ne={key:0,class:"absolute inset-0 flex items-center justify-center"},oe={key:1,class:"absolute inset-0 flex items-center justify-center"},re={class:"text-red-600 dark:text-red-400 text-sm lg:text-base"},le={key:2,class:"absolute inset-0 flex items-center justify-center"},ie={key:3,class:"h-full flex flex-col"},de={key:0,class:"absolute top-2 left-1/2 -translate-x-1/2 bg-white/95 dark:bg-surface-elevated border border-stroke-subtle dark:border-stroke rounded-lg px-3 py-2 z-10 pointer-events-none min-w-48"},ce={class:"text-content-primary dark:text-content-primary text-sm font-medium mb-1"},ue={class:"text-content-primary dark:text-content-primary"},pe={class:"flex-1 flex items-end justify-evenly gap-4 px-4"},me=["onMouseenter"],xe={class:"text-content-primary dark:text-content-primary text-xs sm:text-sm font-semibold text-center w-full",style:{"padding-bottom":"5px"}},ye={class:"text-content-secondary dark:text-content-muted text-xs mt-2 text-center"},be={key:0,class:"mt-4 flex flex-wrap justify-center gap-3 sm:gap-4 px-2 sm:px-4 text-[10px] sm:text-xs text-content-secondary dark:text-content-muted"},ve={class:"truncate text-left"},ge={key:1,class:"mt-3 text-xs text-content-secondary dark:text-content-muted text-center"},he=dt({name:"PacketTypesChart",__name:"PacketTypesChart",setup(at){const _=B([]),W=gt(),m=kt(),R=B(!0),$=B(null),P=B(null),H=[{name:"Payload",types:["Plain Text Message","Group Text Message","Group Datagram","Multi-part Packet"],subColors:["#3B82F6","#60A5FA","#93C5FD","#BFDBFE"]},{name:"Requests",types:["Request","Response","Anonymous Request"],subColors:["#10B981","#34D399","#6EE7B7"]},{name:"Control",types:["Node Advertisement","Acknowledgment","Returned Path"],subColors:["#F59E0B","#FBBF24","#FCD34D"]},{name:"Routing",types:["Trace"],subColors:["#8B5CF6"]},{name:"Reserved",types:["Reserved Type 11","Reserved Type 12","Reserved Type 13"],subColors:["#6B7280","#9CA3AF","#D1D5DB"]}],A=K(()=>H.map(x=>{const d=_.value.filter(v=>x.types.some(g=>v.name.includes(g)||v.name===g)).sort((v,g)=>g.count-v.count).map((v,g)=>({...v,color:x.subColors[g%x.subColors.length]}));return{name:x.name,color:x.subColors[0],items:d,total:d.reduce((v,g)=>v+g.count,0)}}).filter(x=>x.total>0)),h=K(()=>Math.max(...A.value.map(x=>x.total),1)),F=K(()=>A.value.reduce((x,d)=>x+d.total,0)),X=async()=>{try{$.value=null;const x=await ft.get("/packet_type_graph_data");if(x?.success&&x?.data){const d=x.data;if(d?.series){const v=[];d.series.forEach((g,M)=>{let w=0;g.data&&Array.isArray(g.data)&&(w=g.data.reduce((s,e)=>s+(e[1]||0),0)),w>0&&v.push({name:g.name||`Type ${g.type}`,type:g.type,count:w,color:""})}),_.value=v,R.value=!1}else $.value="No series data in server response",R.value=!1}else $.value="Invalid response from server",R.value=!1}catch(x){$.value=x instanceof Error?x.message:"Failed to load data",R.value=!1}},z={0:"Request",1:"Response",2:"Plain Text Message",3:"Acknowledgment",4:"Node Advertisement",5:"Group Text Message",6:"Group Datagram",7:"Anonymous Request",8:"Returned Path",9:"Trace",10:"Multi-part Packet",15:"Custom Packet"},E=()=>{const x=W.packetTypeBreakdown;!x||x.length===0||(_.value=x.map(d=>({name:z[Number(d.type)]||`Type ${d.type}`,type:d.type,count:d.count,color:""})),R.value=!1,$.value=null)},D=x=>Math.max(x/h.value*90,2),C=(x,d)=>d===0?0:x/d*100;return yt(()=>{X()}),tt(()=>W.packetTypeBreakdown,()=>E(),{deep:!0,immediate:!0}),tt(()=>m.isConnected,x=>{x||X()},{immediate:!0}),(x,d)=>(r(),l("div",se,[d[3]||(d[3]=t("div",{class:"flex items-baseline justify-between mb-3 lg:mb-4"},[t("h3",{class:"text-content-primary dark:text-content-primary text-lg lg:text-xl font-semibold"},"Packet Types"),t("p",{class:"text-content-secondary dark:text-content-muted text-xs lg:text-sm uppercase"},"Distribution by Type")],-1)),t("div",ae,[R.value?(r(),l("div",ne,d[1]||(d[1]=[t("div",{class:"text-content-secondary dark:text-content-primary text-sm lg:text-base"},"Loading packet types...",-1)]))):$.value?(r(),l("div",oe,[t("div",re,n($.value),1)])):A.value.length===0?(r(),l("div",le,d[2]||(d[2]=[t("div",{class:"text-content-secondary dark:text-content-primary text-sm lg:text-base"},"No packet data available",-1)]))):(r(),l("div",ie,[P.value?(r(),l("div",de,[t("div",ce,n(P.value.name)+" · "+n(P.value.total.toLocaleString()),1),(r(!0),l(V,null,O(P.value.items,v=>(r(),l("div",{key:v.type,class:"flex justify-between gap-4 text-xs text-content-secondary dark:text-content-muted"},[t("span",null,n(v.name),1),t("span",ue,n(v.count.toLocaleString()),1)]))),128))])):k("",!0),t("div",pe,[(r(!0),l(V,null,O(A.value,v=>(r(),l("div",{key:v.name,class:"flex flex-col items-center flex-1 max-w-32 h-full justify-end cursor-pointer",onMouseenter:g=>P.value=v,onMouseleave:d[0]||(d[0]=g=>P.value=null)},[t("span",xe,n(v.total.toLocaleString()),1),t("div",{class:"w-full rounded-[5px] transition-all duration-300 ease-out hover:opacity-90 overflow-hidden flex flex-col-reverse",style:ct({height:D(v.total)+"%",minHeight:"8px"})},[(r(!0),l(V,null,O(v.items,g=>(r(),l("div",{key:g.type,style:ct({height:C(g.count,v.total)+"%",backgroundColor:g.color})},null,4))),128))],4),t("span",ye,n(v.name),1)],40,me))),128))])]))]),A.value.length>0?(r(),l("div",be,[(r(!0),l(V,null,O(A.value,v=>(r(),l("div",{key:"legend-"+v.name,class:"flex flex-col gap-0.5 min-w-[100px] max-w-[140px] flex-shrink-0"},[(r(!0),l(V,null,O(v.items,g=>(r(),l("div",{key:g.type,class:"flex items-center gap-1.5"},[t("span",{class:"w-2 h-2 rounded-sm shrink-0",style:ct({backgroundColor:g.color})},null,4),t("span",ve,n(g.name),1)]))),128))]))),128))])):k("",!0),A.value.length>0?(r(),l("div",ge," Total: "+n(F.value.toLocaleString())+" packets ",1)):k("",!0)]))}}),fe=pt(he,[["__scopeId","data-v-0948a4bb"]]),ke={class:"glass-card rounded-[10px] p-4 lg:p-6"},_e={class:"relative h-40 lg:h-48"},we={class:"mt-3 lg:mt-4 grid grid-cols-2 gap-3 lg:gap-4"},$e={class:"text-center"},Te={class:"text-lg lg:text-2xl font-bold text-content-primary dark:text-content-primary"},Re={class:"text-center"},Ce={class:"text-lg lg:text-2xl font-bold text-content-primary dark:text-content-primary"},Se={class:"mt-2 lg:mt-3 grid grid-cols-3 gap-2 lg:gap-3 text-center"},Pe={class:"text-xs lg:text-sm font-semibold text-accent-purple flex items-center justify-center gap-1"},Ae={key:0,class:"inline-block w-1.5 h-1.5 rounded-full bg-secondary opacity-70",title:"Early data - limited uptime"},De={class:"text-xs text-content-secondary dark:text-content-muted"},Me={class:"text-xs lg:text-sm font-semibold text-accent-red flex items-center justify-center gap-1"},Ne={key:0,class:"inline-block w-1.5 h-1.5 rounded-full bg-secondary opacity-70",title:"Early data - limited uptime"},Be={class:"text-xs text-content-secondary dark:text-content-muted"},Fe={class:"text-xs lg:text-sm font-semibold text-white"},Ee=dt({name:"AirtimeUtilizationChart",__name:"AirtimeUtilizationChart",setup(at){const _=gt(),W=Ut(),m=B(null),R=B([]),$=B(!0),P=B(null),H=B(30),A=B({totalReceived:0,totalTransmitted:0,dropped:0,firstPacketTime:0}),h=B({sf:9,bwHz:62500,cr:5,preamble:17}),F=x=>{const{sf:d,bwHz:v,cr:g,preamble:M}=h.value,w=1,s=0,e=d>=11&&v<=125e3?1:0,c=v/1e3,i=Math.pow(2,d)/c,o=(M+4.25)*i,b=Math.max(8*x-4*d+28+16*w-20*s,0),f=4*(d-2*e),j=(8+Math.ceil(b/f)*g)*i;return o+j},X=x=>{if(x.airtime_ms!==void 0&&x.airtime_ms>0)return x.airtime_ms;const d=x.length??x.payload_length??32;return F(d)},z=(x,d=60)=>{if(x.length===0)return[];const v=1-Math.pow(.5,1/d),g=Math.min(x.length,Math.max(10,Math.floor(d/3)));let M=0,w=0;for(let s=0;s(M=v*s.rxUtil+(1-v)*M,w=v*s.txUtil+(1-v)*w,{...s,rxUtil:M,txUtil:w}))},E=K(()=>{const x=_.packetStats?.total_packets||0,d=_.packetStats?.transmitted_packets||0,v=W.stats?.uptime_seconds||0,g=x||A.value.totalReceived,M=d||A.value.totalTransmitted,w=A.value.firstPacketTime>0?Math.floor(Date.now()/1e3)-A.value.firstPacketTime:0,s=v||w,e=Math.max(s/3600,.1);if(e<1){const S=Math.max(s/60,1);return{rxRate:{value:Math.round(g/S*100)/100,label:e<.5?"RX/min (early)":"RX/min"},txRate:{value:Math.round(M/S*100)/100,label:e<.5?"TX/min (early)":"TX/min"},confidence:"low"}}const i=Math.round(g/e*100)/100,o=Math.round(M/e*100)/100;let b,f;return e<6?(b=`RX/hr (${Math.round(e)}h)`,f="medium"):e<24?(b=`RX/hr (${Math.round(e)}h)`,f="high"):(b="RX/hr",f="high"),{rxRate:{value:i,label:b},txRate:{value:o,label:b.replace("RX","TX")},confidence:f}}),D=async()=>{$.value=!0;try{const M=Math.floor(Date.now()/1e3),w=M-24*3600;let s=0;try{const y=await ft.get("/stats");if(y.success&&y.data){const N=y.data,J=N.config;if(J?.radio){const q=J.radio;h.value={sf:q.spreading_factor??9,bwHz:q.bandwidth??62500,cr:q.coding_rate??5,preamble:q.preamble_length??17}}s=N.dropped_count??0}}catch{}const e=await ft.get("/filtered_packets",{start_timestamp:w,end_timestamp:M,limit:5e4});if(!e.success){R.value=[],$.value=!1,it(()=>C());return}const c=e.data||[],i=new Float64Array(8640),o=new Float64Array(8640);let b=0,f=0,S=1/0;for(const y of c){const N=Math.floor((y.timestamp-w)/10);if(N<0||N>=8640)continue;const J=X(y),q=y.packet_origin;y.timestamp[y.rxUtil,y.txUtil]))*1.05;H.value=Math.max(5,Math.ceil(U/5)*5),$.value=!1,it(()=>C())}catch(x){console.error("Failed to fetch airtime data:",x),R.value=[],$.value=!1,it(()=>C())}},C=()=>{if(!m.value)return;const x=m.value,d=x.getContext("2d");if(!d)return;const v=x.parentElement;if(!v)return;const g=v.getBoundingClientRect(),M=g.width,w=g.height;x.width=M*window.devicePixelRatio,x.height=w*window.devicePixelRatio,x.style.width=M+"px",x.style.height=w+"px",d.scale(window.devicePixelRatio,window.devicePixelRatio);const s=20,e=45;if(d.clearRect(0,0,M,w),$.value){d.fillStyle="#666",d.font="16px system-ui",d.textAlign="center",d.fillText("Loading chart data...",M/2,w/2);return}if(R.value.length===0){d.fillStyle="#666",d.font="16px system-ui",d.textAlign="center",d.fillText("No data available",M/2,w/2);return}const c=M-e-s,i=w-s*2,o=H.value,b=H.value;d.strokeStyle="rgba(255, 255, 255, 0.1)",d.lineWidth=1,d.font="10px system-ui",d.textAlign="right";for(let f=0;f<=5;f++){const S=s+i*f/5;d.beginPath(),d.moveTo(e,S),d.lineTo(M-s,S),d.stroke();const j=o-f/5*b;d.fillStyle="rgba(255, 255, 255, 0.5)",d.fillText(`${j.toFixed(0)}%`,e-5,S+3)}for(let f=0;f<=6;f++){const S=e+c*f/6;d.beginPath(),d.moveTo(S,s),d.lineTo(S,w-s),d.stroke()}R.value.length>1&&(d.strokeStyle="#EBA0FC",d.lineWidth=2,d.beginPath(),R.value.forEach((f,S)=>{const j=e+c*S/(R.value.length-1),L=w-s-Math.min(f.rxUtil,H.value)/b*i;S===0?d.moveTo(j,L):d.lineTo(j,L)}),d.stroke()),R.value.length>1&&(d.strokeStyle="#FB787B",d.lineWidth=2,d.beginPath(),R.value.forEach((f,S)=>{const j=e+c*S/(R.value.length-1),L=w-s-Math.min(f.txUtil,H.value)/b*i;S===0?d.moveTo(j,L):d.lineTo(j,L)}),d.stroke())};return yt(()=>{D(),P.value=window.setInterval(D,3e4),it(()=>{C(),setTimeout(()=>C(),100)}),window.addEventListener("resize",C)}),vt(()=>{P.value&&clearInterval(P.value),window.removeEventListener("resize",C)}),(x,d)=>(r(),l("div",ke,[d[3]||(d[3]=St('

Airtime Utilization

Activity (Last 24 Hours)

Rx Util
Tx Util
',3)),t("div",_e,[t("canvas",{ref_key:"chartRef",ref:m,class:"absolute inset-0 w-full h-full"},null,512)]),t("div",we,[t("div",$e,[t("div",Te,n(ut(_).packetStats?.total_packets||A.value.totalReceived),1),d[0]||(d[0]=t("div",{class:"text-xs text-content-secondary dark:text-content-muted uppercase tracking-wide"},"Total Received",-1))]),t("div",Re,[t("div",Ce,n(ut(_).packetStats?.transmitted_packets||A.value.totalTransmitted),1),d[1]||(d[1]=t("div",{class:"text-xs text-content-secondary dark:text-content-muted uppercase tracking-wide"},"Total Transmitted",-1))])]),t("div",Se,[t("div",null,[t("div",Pe,[rt(n(E.value.rxRate.value)+" ",1),E.value.confidence==="low"?(r(),l("span",Ae)):k("",!0)]),t("div",De,n(E.value.rxRate.label),1)]),t("div",null,[t("div",Me,[rt(n(E.value.txRate.value)+" ",1),E.value.confidence==="low"?(r(),l("span",Ne)):k("",!0)]),t("div",Be,n(E.value.txRate.label),1)]),t("div",null,[t("div",Fe,n(ut(_).packetStats?.dropped_packets||A.value.dropped),1),d[2]||(d[2]=t("div",{class:"text-xs text-white/60"},"Dropped",-1))])])]))}}),je=pt(Ee,[["__scopeId","data-v-6bf3fe96"]]),Ie={class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] shadow-2xl border border-stroke-subtle dark:border-white/20 flex flex-col h-full overflow-hidden"},Le={class:"flex items-center justify-between p-8 pb-4 flex-shrink-0"},Ue={class:"text-content-secondary dark:text-content-muted text-sm"},Ve={class:"flex items-center gap-2"},He=["title"],ze={class:"flex-1 overflow-y-auto custom-scrollbar px-8"},Xe={class:"mb-6"},Ge={class:"glass-card bg-white/5 rounded-[15px] p-4"},Oe={class:"grid grid-cols-1 md:grid-cols-2 gap-4"},We={class:"space-y-3"},Qe={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},qe={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Ke={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Je={class:"text-content-primary dark:text-content-primary font-mono text-xs break-all"},Ye={key:0,class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Ze={class:"text-content-primary dark:text-content-primary font-mono text-xs"},ts={class:"space-y-3"},es={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},ss={class:"text-content-primary dark:text-content-primary font-semibold"},as={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},ns={class:"text-content-primary dark:text-content-primary font-semibold"},os={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},rs={class:"mb-6"},ls={class:"bg-gray-50 dark:bg-white/5 rounded-[15px] p-4 border border-stroke-subtle dark:border-stroke/10"},is={class:"space-y-3"},ds={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},cs={class:"text-content-primary dark:text-content-primary"},us={key:0,class:"pt-2"},ps={class:"glass-card bg-background-mute dark:bg-black/30 rounded-[10px] p-4 mb-4"},ms={class:"w-full overflow-x-auto"},xs={class:"text-content-primary dark:text-content-primary/90 text-xs font-mono whitespace-pre leading-relaxed min-w-full"},ys={class:"flex items-center justify-between mb-3"},bs={class:"text-content-secondary dark:text-content-primary/80 text-sm font-semibold"},vs={class:"text-content-muted dark:text-content-muted text-xs"},gs={class:"bg-background-mute dark:bg-black/40 rounded-[8px] p-3 mb-3"},hs={class:"font-mono text-xs text-content-primary dark:text-content-primary break-all whitespace-pre-wrap leading-relaxed"},fs={class:"bg-gray-50 dark:bg-white/5 rounded-[10px] overflow-hidden"},ks={key:0,class:"min-w-0"},_s={class:"text-cyan-500 text-sm font-mono break-words min-w-0"},ws={class:"text-content-primary dark:text-content-primary text-sm break-words min-w-0"},$s={class:"text-content-primary dark:text-content-primary text-sm font-semibold break-all min-w-0 overflow-hidden"},Ts=["title"],Rs={key:0,class:"text-orange-500 text-xs font-mono break-all min-w-0 overflow-hidden"},Cs=["title"],Ss={class:"grid grid-cols-2 gap-2"},Ps={class:"text-cyan-500 text-sm font-mono break-words"},As={class:"text-content-primary dark:text-content-primary text-sm break-words"},Ds=["title"],Ms={key:0},Ns=["title"],Bs={key:0,class:"text-content-muted dark:text-content-muted text-xs italic mt-2 px-1"},Fs={key:1,class:"py-2"},Es={class:"mb-6"},js={class:"bg-gray-50 dark:bg-white/5 rounded-[15px] p-4 border border-stroke-subtle dark:border-stroke/10"},Is={class:"space-y-4"},Ls={class:"grid grid-cols-1 md:grid-cols-2 gap-4"},Us={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Vs={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Hs={key:0,class:"py-2"},zs={class:"bg-background-mute dark:bg-black/20 rounded-[10px] p-4"},Xs={class:"flex items-center flex-wrap gap-2"},Gs={class:"relative group"},Os={class:"relative px-3 py-2 bg-gradient-to-br from-blue-500/20 to-cyan-500/20 border border-cyan-400/40 rounded-lg transform transition-all hover:scale-105"},Ws={class:"font-mono text-xs font-semibold text-content-primary dark:text-content-primary/90"},Qs={class:"absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-content-primary dark:bg-background/90 text-white dark:text-content-primary text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-10"},qs={key:0,class:"mx-2 text-cyan-600 dark:text-cyan-400/60"},Ks={key:1,class:"py-2"},Js={class:"text-content-secondary dark:text-content-muted text-sm mb-2 flex items-center"},Ys={key:0,class:"w-4 h-4 ml-2 text-yellow-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Zs={key:1,class:"text-yellow-500 text-xs ml-1"},ta={class:"bg-background-mute dark:bg-black/20 rounded-[10px] p-4"},ea={class:"flex items-center flex-wrap gap-2"},sa={class:"relative group"},aa={key:0,class:"absolute -top-1 -right-1 w-2 h-2 bg-yellow-400 rounded-full animate-pulse"},na={class:"absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-content-primary dark:bg-background/90 text-white dark:text-content-primary text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-10"},oa={key:0,class:"mx-1 text-orange-600 dark:text-orange-400/60"},ra={class:"mb-6"},la={class:"glass-card bg-gray-50 dark:bg-white/5 rounded-[15px] p-4"},ia={class:"grid grid-cols-1 md:grid-cols-3 gap-4 mb-4"},da={class:"text-center p-3 glass-card bg-background-mute dark:bg-black/20 rounded-[10px]"},ca={class:"text-lg font-bold text-content-primary dark:text-content-primary"},ua={class:"text-center p-3 glass-card bg-background-mute dark:bg-black/20 rounded-[10px]"},pa={class:"text-center p-3 glass-card bg-background-mute dark:bg-black/20 rounded-[10px]"},ma={class:"text-lg font-bold text-content-primary dark:text-content-primary"},xa={key:0,class:"mb-4"},ya={class:"flex items-center gap-3"},ba={class:"flex gap-1"},va={class:"text-content-secondary dark:text-content-primary/80 text-sm capitalize"},ga={key:1,class:"mb-4"},ha={key:2,class:"mb-4"},fa={class:"text-content-secondary dark:text-content-muted text-sm mb-3"},ka={class:"space-y-2"},_a={class:"flex items-center gap-3"},wa={class:"text-content-muted dark:text-content-muted text-sm"},$a={key:3,class:"mt-6 pt-4 border-t border-stroke-subtle dark:border-stroke/10"},Ta={class:"grid grid-cols-1 md:grid-cols-3 gap-3 mb-4"},Ra={class:"text-center p-3 glass-card bg-background-mute dark:bg-black/20 rounded-[10px]"},Ca={class:"text-2xl font-bold text-content-primary dark:text-content-primary"},Sa={class:"text-center p-3 glass-card bg-background-mute dark:bg-black/20 rounded-[10px]"},Pa={class:"text-2xl font-bold text-content-primary dark:text-content-primary"},Aa={class:"text-content-muted dark:text-content-muted text-xs mt-1"},Da={class:"text-center p-3 glass-card bg-background-mute dark:bg-black/20 rounded-[10px]"},Ma={class:"text-content-muted dark:text-content-muted text-xs mt-1"},Na={key:0,class:"glass-card bg-background-mute dark:bg-black/20 rounded-[10px] p-4"},Ba={class:"space-y-3"},Fa={class:"flex-shrink-0 w-16 text-right"},Ea={class:"text-content-secondary dark:text-content-muted text-xs"},ja={class:"flex-1 relative"},Ia={class:"h-8 rounded-lg overflow-hidden bg-background-mute dark:bg-stroke/5 relative"},La={class:"absolute inset-0 flex items-center px-3"},Ua={class:"text-content-primary dark:text-content-primary text-xs font-mono font-semibold"},Va={class:"flex-shrink-0 w-12 text-left"},Ha={class:"text-content-muted dark:text-content-muted text-xs"},za={class:"grid grid-cols-1 md:grid-cols-2 gap-4"},Xa={class:"space-y-2"},Ga={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Oa={class:"text-content-primary dark:text-content-primary"},Wa={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Qa={class:"space-y-2"},qa={class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Ka={key:0,class:"flex justify-between py-2 border-b border-stroke-subtle dark:border-stroke/10"},Ja={class:"text-red-600 dark:text-red-400 text-sm"},Ya={class:"p-8 pt-4 border-t border-stroke-subtle dark:border-stroke/10 flex justify-end flex-shrink-0"},Za=dt({name:"PacketDetailsModal",__name:"PacketDetailsModal",props:{packet:{},isOpen:{type:Boolean},localHash:{}},emits:["close"],setup(at,{emit:_}){const{getSignalQuality:W}=Ot(),m=at,R=_,$=B(!1),P=s=>new Date(s*1e3).toLocaleString(),H=s=>s.transmitted?s.is_duplicate?"text-amber-600 dark:text-amber-400":s.drop_reason?"text-red-600 dark:text-red-400":"text-green-600 dark:text-green-400":"text-red-600 dark:text-red-400",A=s=>s.transmitted?s.is_duplicate?"Duplicate":s.drop_reason?"Dropped":"Forwarded":"Dropped",h=s=>({0:"Request",1:"Response",2:"Plain Text Message",3:"Acknowledgment",4:"Node Advertisement",5:"Group Text Message",6:"Group Datagram",7:"Anonymous Request",8:"Returned Path",9:"Trace",10:"Multi-part Packet",15:"Custom Packet"})[s]||`Unknown Type (${s})`,F=s=>({0:"Transport Flood",1:"Flood",2:"Direct",3:"Transport Direct"})[s]||`Unknown Route (${s})`,X=s=>{if(!s)return"None";const c=s.replace(/\s+/g,"").toUpperCase().match(/.{2}/g)||[],i=[];for(let o=0;o{try{let i=0;const o=e.length/2;if(o>=100){if(e.length>=i+64){const b=e.slice(i,i+64);s.push({name:"Public Key",byteRange:`${(c+i)/2}-${(c+i+63)/2}`,hexData:b.match(/.{8}/g)?.join(" ")||b,description:"Ed25519 public key of the node (32 bytes)",fields:[{bits:"0-255",name:"Ed25519 Public Key",value:`${b.slice(0,16)}...${b.slice(-16)}`,binary:"32 bytes (256 bits)"}]}),i+=64}if(e.length>=i+8){const b=e.slice(i,i+8),f=parseInt(b,16),S=new Date(f*1e3);s.push({name:"Timestamp",byteRange:`${(c+i)/2}-${(c+i+7)/2}`,hexData:b.match(/.{2}/g)?.join(" ")||b,description:"Unix timestamp of advertisement",fields:[{bits:"0-31",name:"Unix Timestamp",value:`${f} (${S.toLocaleString()})`,binary:f.toString(2).padStart(32,"0")}]}),i+=8}if(e.length>=i+128){const b=e.slice(i,i+128);s.push({name:"Signature",byteRange:`${(c+i)/2}-${(c+i+127)/2}`,hexData:b.match(/.{8}/g)?.join(" ")||b,description:"Ed25519 signature of public key, timestamp, and appdata",fields:[{bits:"0-511",name:"Ed25519 Signature",value:`${b.slice(0,16)}...${b.slice(-16)}`,binary:"64 bytes (512 bits)"}]}),i+=128}if(e.length>i){const b=e.slice(i);E(s,b,c+i)}}else s.push({name:"ADVERT AppData (Partial)",byteRange:`${c/2}-${c/2+o-1}`,hexData:e.match(/.{2}/g)?.join(" ")||e,description:`Partial ADVERT data - appears to be just AppData portion (${o} bytes)`,fields:[{bits:`0-${o*8-1}`,name:"Partial Data",value:`${o} bytes - attempting to decode as AppData`,binary:`${o} bytes (${o*8} bits)`}]}),E(s,e,c)}catch(i){s.push({name:"ADVERT Parse Error",byteRange:"N/A",hexData:e.slice(0,32)+"...",description:"Failed to parse ADVERT payload structure",fields:[{bits:"N/A",name:"Error",value:`Parse error: ${i instanceof Error?i.message:"Unknown error"}`,binary:"Invalid"}]})}},E=(s,e,c)=>{try{const i=e.length/2;s.push({name:"AppData",byteRange:`${c/2}-${c/2+i-1}`,hexData:e.match(/.{2}/g)?.join(" ")||e,description:`Node advertisement application data (${i} bytes)`,fields:[{bits:`0-${i*8-1}`,name:"Application Data",value:`${i} bytes (contains flags, location, name, etc.)`,binary:`${i} bytes (${i*8} bits)`}]});let o=0;if(e.length>=2){const b=parseInt(e.slice(o,o+2),16),f=[],S=!!(b&16),j=!!(b&32),L=!!(b&64),st=!!(b&128);if(b&1&&f.push("is chat node"),b&2&&f.push("is repeater"),b&4&&f.push("is room server"),b&8&&f.push("is sensor"),S&&f.push("has location"),j&&f.push("has feature 1"),L&&f.push("has feature 2"),st&&f.push("has name"),s.push({name:"AppData Flags",byteRange:`${(c+o)/2}`,hexData:`0x${e.slice(o,o+2)}`,description:"Flags indicating which optional fields are present",fields:[{bits:"0-7",name:"Flags",value:f.join(", ")||"none",binary:b.toString(2).padStart(8,"0")}]}),o+=2,S&&e.length>=o+16){const I=e.slice(o,o+8),Q=[];for(let p=6;p>=0;p-=2)Q.push(I.slice(p,p+2));const U=parseInt(Q.join(""),16),y=U>2147483647?U-4294967296:U,N=y/1e6,J=e.slice(o+8,o+16),q=[];for(let p=6;p>=0;p-=2)q.push(J.slice(p,p+2));const nt=parseInt(q.join(""),16),Y=nt>2147483647?nt-4294967296:nt,bt=Y/1e6;s.push({name:"Location Data",byteRange:`${(c+o)/2}-${(c+o+15)/2}`,hexData:`${I.match(/.{2}/g)?.join(" ")||I} ${J.match(/.{2}/g)?.join(" ")||J}`,description:"GPS coordinates (latitude and longitude)",fields:[{bits:"0-31",name:"Latitude",value:`${N.toFixed(6)}° (raw: ${y})`,binary:y.toString(2).padStart(32,"0")},{bits:"32-63",name:"Longitude",value:`${bt.toFixed(6)}° (raw: ${Y})`,binary:Y.toString(2).padStart(32,"0")}]}),o+=16}if(j&&e.length>=o+4){const I=e.slice(o,o+4),Q=parseInt(I,16);s.push({name:"Feature 1",byteRange:`${(c+o)/2}-${(c+o+3)/2}`,hexData:I.match(/.{2}/g)?.join(" ")||I,description:"Reserved feature 1 (2 bytes)",fields:[{bits:"0-15",name:"Feature 1 Value",value:`${Q}`,binary:Q.toString(2).padStart(16,"0")}]}),o+=4}if(L&&e.length>=o+4){const I=e.slice(o,o+4),Q=parseInt(I,16);s.push({name:"Feature 2",byteRange:`${(c+o)/2}-${(c+o+3)/2}`,hexData:I.match(/.{2}/g)?.join(" ")||I,description:"Reserved feature 2 (2 bytes)",fields:[{bits:"0-15",name:"Feature 2 Value",value:`${Q}`,binary:Q.toString(2).padStart(16,"0")}]}),o+=4}if(st&&e.length>o){const I=e.slice(o),Q=I.match(/.{2}/g)||[],U=Q.map(y=>{const N=parseInt(y,16);return N>=32&&N<=126?String.fromCharCode(N):"."}).join("").replace(/\.+$/,"");s.push({name:"Node Name",byteRange:`${(c+o)/2}-${(c+e.length-1)/2}`,hexData:I.match(/.{2}/g)?.join(" ")||I,description:`Node name string (${Q.length} bytes)`,fields:[{bits:`0-${Q.length*8-1}`,name:"Node Name",value:`"${U}"`,binary:`ASCII text (${Q.length} bytes)`}]})}}}catch(i){s.push({name:"AppData Parse Error",byteRange:"N/A",hexData:e.slice(0,Math.min(32,e.length)),description:"Failed to parse AppData structure",fields:[{bits:"N/A",name:"Error",value:`Parse error: ${i instanceof Error?i.message:"Unknown error"}`,binary:"Invalid"}]})}},D=s=>{if(!s)return[];if(Array.isArray(s))return s;if(typeof s=="string")try{return JSON.parse(s)}catch{return[]}return[]},C=s=>{const e=[];if(!s)return e;try{const c=s.raw_packet;if(c){const i=c.replace(/\s+/g,"").toUpperCase();let o=0;if(i.length>=2){const b=i.slice(o,o+2),f=parseInt(b,16),S=f&3,j=(f&60)>>2,L=(f&192)>>6,st={0:"Transport Flood",1:"Flood",2:"Direct",3:"Transport Direct"},I={0:"REQ",1:"RESPONSE",2:"TXT_MSG",3:"ACK",4:"ADVERT",5:"GRP_TXT",6:"GRP_DATA",7:"ANON_REQ",8:"PATH",9:"TRACE",10:"MULTIPART",15:"RAW_CUSTOM"};if(e.push({name:"Header",byteRange:"0",hexData:`0x${b}`,description:"Contains routing type, payload type, and payload version",fields:[{bits:"0-1",name:"Route Type",value:st[S]||"Unknown",binary:S.toString(2).padStart(2,"0")},{bits:"2-5",name:"Payload Type",value:I[j]||"Unknown",binary:j.toString(2).padStart(4,"0")},{bits:"6-7",name:"Version",value:L.toString(),binary:L.toString(2).padStart(2,"0")}]}),o+=2,(S===0||S===3)&&i.length>=o+8){const U=i.slice(o,o+8),y=parseInt(U.slice(0,4),16),N=parseInt(U.slice(4,8),16);e.push({name:"Transport Codes",byteRange:"1-4",hexData:`${U.slice(0,4)} ${U.slice(4,8)}`,description:"2x 16-bit transport codes for routing optimization",fields:[{bits:"0-15",name:"Code 1",value:y.toString(),binary:y.toString(2).padStart(16,"0")},{bits:"16-31",name:"Code 2",value:N.toString(),binary:N.toString(2).padStart(16,"0")}]}),o+=8}if(i.length>=o+2){const U=i.slice(o,o+2),y=parseInt(U,16);if(e.push({name:"Path Length",byteRange:`${o/2}`,hexData:`0x${U}`,description:`${y} bytes of path data`,fields:[{bits:"0-7",name:"Path Length",value:`${y} bytes`,binary:y.toString(2).padStart(8,"0")}]}),o+=2,y>0&&i.length>=o+y*2){const N=i.slice(o,o+y*2);e.push({name:"Path Data",byteRange:`${o/2}-${(o+y*2-2)/2}`,hexData:N.match(/.{2}/g)?.join(" ")||N,description:"Routing path information",fields:[{bits:`0-${y*8-1}`,name:"Route Path",value:`${y} bytes of routing data`,binary:`${y} bytes (${y*8} bits)`}]}),o+=y*2}}if(i.length>o){const U=i.slice(o),y=U.length/2;j===4?z(e,U,o):e.push({name:"Payload Data",byteRange:`${o/2}-${o/2+y-1}`,hexData:U.match(/.{2}/g)?.join(" ")||U,description:"Application data content",fields:[{bits:`0-${y*8-1}`,name:"Application Data",value:`${y} bytes`,binary:`${y} bytes (${y*8} bits)`}]})}}}else{if(s.header){const i=s.header.replace(/0x/gi,"").replace(/\s+/g,"").toUpperCase(),o=parseInt(i,16),b=o&3,f=(o&60)>>2,S=(o&192)>>6,j={0:"Transport Flood",1:"Flood",2:"Direct",3:"Transport Direct"},L={0:"REQ",1:"RESPONSE",2:"TXT_MSG",3:"ACK",4:"ADVERT",5:"GRP_TXT",6:"GRP_DATA",7:"ANON_REQ",8:"PATH",9:"TRACE",10:"MULTIPART",15:"RAW_CUSTOM"};e.push({name:"Header",byteRange:"0",hexData:`0x${i}`,description:"Contains routing type, payload type, and payload version",fields:[{bits:"0-1",name:"Route Type",value:j[b]||"Unknown",binary:b.toString(2).padStart(2,"0")},{bits:"2-5",name:"Payload Type",value:L[f]||"Unknown",binary:f.toString(2).padStart(4,"0")},{bits:"6-7",name:"Version",value:S.toString(),binary:S.toString(2).padStart(2,"0")}]}),s.transport_codes&&e.push({name:"Transport Codes",byteRange:"1-4",hexData:s.transport_codes,description:"2x 16-bit transport codes for routing optimization",fields:[{bits:"0-31",name:"Transport Codes",value:s.transport_codes,binary:"Available in separate field"}]}),s.original_path&&s.original_path.length>0&&e.push({name:"Original Path",byteRange:"?",hexData:s.original_path.join(" "),description:`Original routing path (${s.original_path.length} nodes)`,fields:[{bits:"0-?",name:"Path Nodes",value:`${s.original_path.length} nodes`,binary:"Available as node list"}]}),s.forwarded_path&&s.forwarded_path.length>0&&e.push({name:"Forwarded Path",byteRange:"?",hexData:s.forwarded_path.join(" "),description:`Forwarded routing path (${s.forwarded_path.length} nodes)`,fields:[{bits:"0-?",name:"Path Nodes",value:`${s.forwarded_path.length} nodes`,binary:"Available as node list"}]})}if(s.payload){const i=s.payload.replace(/\s+/g,"").toUpperCase(),o=i.length/2;s.type===4?z(e,i,0):e.push({name:"Payload Data",byteRange:`0-${o-1}`,hexData:i.match(/.{2}/g)?.join(" ")||i,description:`Application data content (${o} bytes)`,fields:[{bits:`0-${o*8-1}`,name:"Application Data",value:`${o} bytes`,binary:`${o} bytes (${o*8} bits)`}]})}}}catch{e.push({name:"Parse Error",byteRange:"N/A",hexData:"Error",description:"Unable to parse packet structure",fields:[{bits:"N/A",name:"Error",value:"Parse failed",binary:"Invalid"}]})}return e},x=(s,e)=>s==null||e==null?"text-content-muted dark:text-content-muted":W(e).color,d=s=>{if(s==null)return{level:0,className:"signal-none"};const e=W(s);let c,i;return e.bars>=5?(c=4,i="signal-excellent"):e.bars>=4?(c=3,i="signal-good"):e.bars>=2?(c=2,i="signal-fair"):e.bars>=1?(c=1,i="signal-poor"):(c=0,i="signal-none"),{level:c,className:i}},v=s=>{if(!s)return[];try{const e=JSON.parse(s);return Array.isArray(e)?e:[]}catch{return[]}},g=s=>s>=1e3?`${(s/1e3).toFixed(2)}s`:`${Math.round(s)}ms`,M=s=>{s.key==="Escape"&&R("close")},w=s=>{s.target===s.currentTarget&&R("close")};return tt(()=>m.isOpen,s=>{s?document.body.style.overflow="hidden":document.body.style.overflow=""},{immediate:!0}),(s,e)=>(r(),Vt(Xt,{to:"body"},[et(Ht,{name:"modal",appear:""},{default:zt(()=>[s.isOpen&&s.packet?(r(),l("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 overflow-hidden",onClick:w,onKeydown:M,tabindex:"0"},[e[51]||(e[51]=t("div",{class:"absolute inset-0 bg-black/60 backdrop-blur-md pointer-events-none"},null,-1)),t("div",{class:"relative w-full max-w-4xl max-h-[90vh] flex flex-col",onClick:e[3]||(e[3]=Gt(()=>{},["stop"]))},[t("div",Ie,[t("div",Le,[t("div",null,[e[4]||(e[4]=t("h2",{class:"text-2xl font-bold text-content-primary dark:text-content-primary mb-1"},"Packet Details",-1)),t("p",Ue,n(h(s.packet.type))+" - "+n(F(s.packet.route)),1)]),t("div",Ve,[t("button",{onClick:e[0]||(e[0]=c=>$.value=!$.value),class:T(["flex items-center gap-2 px-3 py-1.5 rounded-lg transition-all duration-200",$.value?"bg-cyan-500/20 border border-cyan-400/30 text-cyan-600 dark:text-cyan-400":"bg-background-mute dark:bg-white/10 border border-stroke-subtle dark:border-stroke/20 text-content-secondary dark:text-content-muted"]),title:$.value?"Hide binary values":"Show binary values"},e[5]||(e[5]=[t("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"})],-1),t("span",{class:"text-xs font-medium"},"Binary",-1)]),10,He),t("button",{onClick:e[1]||(e[1]=c=>R("close")),class:"w-8 h-8 flex items-center justify-center rounded-full bg-background-mute dark:bg-white/10 hover:bg-stroke-subtle dark:hover:bg-white/20 transition-colors duration-200 text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary"},e[6]||(e[6]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))])]),t("div",ze,[t("div",Xe,[e[13]||(e[13]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4 flex items-center"},[t("div",{class:"w-2 h-2 rounded-full bg-cyan-400 mr-3"}),rt(" Basic Information ")],-1)),t("div",Ge,[t("div",Oe,[t("div",We,[t("div",Qe,[e[7]||(e[7]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Timestamp",-1)),t("span",qe,n(P(s.packet.timestamp)),1)]),t("div",Ke,[e[8]||(e[8]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Packet Hash",-1)),t("span",Je,n(s.packet.packet_hash),1)]),s.packet.header?(r(),l("div",Ye,[e[9]||(e[9]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Header",-1)),t("span",Ze,n(s.packet.header),1)])):k("",!0)]),t("div",ts,[t("div",es,[e[10]||(e[10]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Type",-1)),t("span",ss,n(s.packet.type)+" ("+n(h(s.packet.type))+")",1)]),t("div",as,[e[11]||(e[11]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Route",-1)),t("span",ns,n(s.packet.route)+" ("+n(F(s.packet.route))+")",1)]),t("div",os,[e[12]||(e[12]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Status",-1)),t("span",{class:T(["font-semibold",H(s.packet)])},n(A(s.packet)),3)])])])])]),t("div",rs,[e[25]||(e[25]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4 flex items-center"},[t("div",{class:"w-2 h-2 rounded-full bg-orange-400 mr-3"}),rt(" Payload Data ")],-1)),t("div",ls,[t("div",is,[t("div",ds,[e[14]||(e[14]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Payload Length",-1)),t("span",cs,n(s.packet.payload_length||s.packet.length)+" bytes",1)]),s.packet.payload?(r(),l("div",us,[e[23]||(e[23]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-3"},"Payload Analysis",-1)),t("div",ps,[e[15]||(e[15]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-2 font-semibold"},"Raw Hex Data",-1)),t("div",ms,[t("pre",xs,n(X(s.packet.payload)),1)])]),(r(!0),l(V,null,O(C(s.packet).filter(c=>!c.name.includes("Parse Error")),(c,i)=>(r(),l("div",{key:i,class:"mb-4"},[t("div",ys,[t("h4",bs,n(c.name),1),t("span",vs,"Bytes "+n(c.byteRange),1)]),t("div",gs,[t("div",hs,n(c.hexData),1)]),t("div",fs,[t("div",{class:T(["hidden md:grid gap-3 p-3 bg-background-mute dark:bg-white/10 text-content-secondary dark:text-content-muted text-xs font-semibold uppercase tracking-wide",$.value?"grid-cols-4":"grid-cols-3"])},[e[16]||(e[16]=t("div",{class:"min-w-0"},"Bits",-1)),e[17]||(e[17]=t("div",{class:"min-w-0"},"Field",-1)),e[18]||(e[18]=t("div",{class:"min-w-0"},"Value",-1)),$.value?(r(),l("div",ks,"Binary")):k("",!0)],2),(r(!0),l(V,null,O(c.fields,(o,b)=>(r(),l("div",{key:b,class:T(["hidden md:grid gap-3 p-3 border-b border-stroke-subtle dark:border-stroke/5 last:border-b-0 hover:bg-background-mute dark:hover:bg-stroke/5 transition-colors",$.value?"grid-cols-4":"grid-cols-3"])},[t("div",_s,n(o.bits),1),t("div",ws,n(o.name),1),t("div",$s,[t("span",{class:"block",title:o.value},n(o.value),9,Ts)]),$.value?(r(),l("div",Rs,[t("span",{class:"block",title:o.binary},n(o.binary),9,Cs)])):k("",!0)],2))),128)),(r(!0),l(V,null,O(c.fields,(o,b)=>(r(),l("div",{key:`mobile-${b}`,class:"md:hidden p-3 border-b border-stroke-subtle dark:border-stroke/5 last:border-b-0 space-y-2"},[t("div",Ss,[t("div",null,[e[19]||(e[19]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs uppercase tracking-wide"},"Bits:",-1)),t("div",Ps,n(o.bits),1)]),t("div",null,[e[20]||(e[20]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs uppercase tracking-wide"},"Field:",-1)),t("div",As,n(o.name),1)])]),t("div",null,[e[21]||(e[21]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs uppercase tracking-wide"},"Value:",-1)),t("div",{class:"text-content-primary dark:text-content-primary text-sm font-semibold break-all",title:o.value},n(o.value),9,Ds)]),$.value?(r(),l("div",Ms,[e[22]||(e[22]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs uppercase tracking-wide"},"Binary:",-1)),t("div",{class:"text-orange-500 text-xs font-mono break-all",title:o.binary},n(o.binary),9,Ns)])):k("",!0)]))),128))]),c.description?(r(),l("div",Bs,n(c.description),1)):k("",!0)]))),128))])):(r(),l("div",Fs,e[24]||(e[24]=[t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Payload:",-1),t("span",{class:"text-content-muted dark:text-content-muted ml-2"},"None",-1)])))])])]),t("div",Es,[e[33]||(e[33]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4 flex items-center"},[t("div",{class:"w-2 h-2 rounded-full bg-purple-400 mr-3"}),rt(" Path Information ")],-1)),t("div",js,[t("div",Is,[t("div",Ls,[t("div",Us,[e[26]||(e[26]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Source Hash",-1)),t("span",{class:T(["text-content-primary dark:text-content-primary font-mono text-xs",m.localHash&&s.packet.src_hash===m.localHash?"bg-cyan-400/20 text-cyan-600 dark:text-cyan-300 px-1 rounded":""])},n(s.packet.src_hash||"Unknown"),3)]),t("div",Vs,[e[27]||(e[27]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Destination Hash",-1)),t("span",{class:T(["text-content-primary dark:text-content-primary font-mono text-xs",m.localHash&&s.packet.dst_hash===m.localHash?"bg-cyan-400/20 text-cyan-600 dark:text-cyan-300 px-1 rounded":""])},n(s.packet.dst_hash||"Broadcast"),3)])]),D(s.packet.original_path).length>0?(r(),l("div",Hs,[e[29]||(e[29]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-2"},"Original Path",-1)),t("div",zs,[t("div",Xs,[(r(!0),l(V,null,O(D(s.packet.original_path),(c,i)=>(r(),l("div",{key:i,class:"flex items-center"},[t("div",Gs,[t("div",Os,[t("div",Ws,n(c.length<=2?c.toUpperCase():c.slice(0,2).toUpperCase()),1)]),t("div",Qs," Node: "+n(c),1)]),i0?(r(),l("div",Ks,[t("div",Js,[e[31]||(e[31]=rt(" Forwarded Path ",-1)),JSON.stringify(D(s.packet.original_path))!==JSON.stringify(D(s.packet.forwarded_path))?(r(),l("svg",Ys,e[30]||(e[30]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)]))):k("",!0),JSON.stringify(D(s.packet.original_path))!==JSON.stringify(D(s.packet.forwarded_path))?(r(),l("span",Zs,"(Modified)")):k("",!0)]),t("div",ta,[t("div",ea,[(r(!0),l(V,null,O(D(s.packet.forwarded_path),(c,i)=>(r(),l("div",{key:i,class:"flex items-center"},[t("div",sa,[t("div",{class:T(["relative px-3 py-2 bg-gradient-to-br from-orange-500/20 to-yellow-500/20 border border-orange-500 dark:border-orange-400/40 rounded-lg transform transition-all hover:scale-105",m.localHash&&c===m.localHash?"bg-gradient-to-br from-yellow-400/30 to-orange-400/30 border-yellow-300 shadow-yellow-400/20 shadow-lg":"hover:border-orange-500 dark:border-orange-400/60"])},[t("div",{class:T(["font-mono text-xs font-semibold",m.localHash&&c===m.localHash?"text-yellow-200":"text-white/90"])},n(c.slice(0,2).toUpperCase()),3),m.localHash&&c===m.localHash?(r(),l("div",aa)):k("",!0)],2),t("div",na,n(c),1)]),it("div",{key:c,class:T(["w-2 h-6 rounded-sm transition-all duration-300",c<=d(s.packet.rssi).level?{"signal-excellent":"bg-green-400","signal-good":"bg-cyan-400","signal-fair":"bg-yellow-400","signal-poor":"bg-red-400"}[d(s.packet.rssi).className]:"bg-stroke-subtle dark:bg-stroke/10"])},null,2)),64))]),t("span",va,n(d(s.packet.rssi).className.replace("signal-","")),1)])])):(r(),l("div",ga,e[40]||(e[40]=[t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-2"},"Signal Quality",-1),t("div",{class:"text-content-muted dark:text-content-muted text-sm italic"},"N/A (TX Packet)",-1)]))),s.packet.is_trace&&s.packet.path_snr_details&&s.packet.path_snr_details.length>0?(r(),l("div",ha,[t("div",fa,"Path SNR Details ("+n(s.packet.path_snr_details.length)+" hops)",1),t("div",ka,[(r(!0),l(V,null,O(s.packet.path_snr_details,(c,i)=>(r(),l("div",{key:i,class:"flex items-center justify-between p-2 glass-card bg-background-mute dark:bg-black/20 rounded-[8px]"},[t("div",_a,[t("span",wa,n(i+1)+".",1),t("span",{class:T(["font-mono text-xs text-content-primary dark:text-content-primary",m.localHash&&c.hash===m.localHash?"bg-cyan-400/20 text-cyan-600 dark:text-cyan-300 px-1 rounded":""])},n(c.hash),3)]),t("span",{class:T(["text-sm font-bold",x(c.snr_db,null)])},n(c.snr_db.toFixed(1))+"dB ",3)]))),128))])])):k("",!0),s.packet.transmitted&&s.packet.lbt_attempts!==void 0?(r(),l("div",$a,[e[45]||(e[45]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-3 flex items-center"},[t("svg",{class:"w-4 h-4 mr-2",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"})]),rt(" Listen Before Talk (LBT) Metrics ")],-1)),t("div",Ta,[t("div",Ra,[e[41]||(e[41]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"CAD Attempts",-1)),t("div",Ca,n(s.packet.lbt_attempts),1)]),t("div",Sa,[e[42]||(e[42]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Total LBT Delay",-1)),t("div",Pa,n(g(v(s.packet.lbt_backoff_delays_ms).reduce((c,i)=>c+i,0))),1),t("div",Aa,n(v(s.packet.lbt_backoff_delays_ms).length)+" backoffs ",1)]),t("div",Da,[e[43]||(e[43]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Channel Status",-1)),t("div",{class:T(["text-lg font-bold",s.packet.lbt_channel_busy?"text-yellow-600 dark:text-yellow-400":"text-green-600 dark:text-green-400"])},n(s.packet.lbt_channel_busy?"BUSY":"CLEAR"),3),t("div",Ma,n(s.packet.lbt_channel_busy?"Waited for clear":"Immediate TX"),1)])]),v(s.packet.lbt_backoff_delays_ms).length>0?(r(),l("div",Na,[e[44]||(e[44]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-3 font-semibold"},"Backoff Pattern (Exponential with Jitter)",-1)),t("div",Ba,[(r(!0),l(V,null,O(v(s.packet.lbt_backoff_delays_ms),(c,i)=>(r(),l("div",{key:i,class:"flex items-center gap-3"},[t("div",Fa,[t("span",Ea,"Attempt "+n(i+1),1)]),t("div",ja,[t("div",Ia,[t("div",{class:T(["h-full rounded-lg transition-all duration-300",[i===0?"bg-gradient-to-r from-cyan-500/50 to-cyan-600/50":i===1?"bg-gradient-to-r from-yellow-500/50 to-yellow-600/50":i===2?"bg-gradient-to-r from-orange-500/50 to-orange-600/50":"bg-gradient-to-r from-red-500/50 to-red-600/50"]]),style:ct({width:`${Math.min(100,c/Math.max(...v(s.packet.lbt_backoff_delays_ms))*100)}%`})},[t("div",La,[t("span",Ua,n(g(c)),1)])],6)])]),t("div",Va,[t("span",Ha,n(Math.round(c/v(s.packet.lbt_backoff_delays_ms).reduce((o,b)=>o+b,0)*100))+"% ",1)])]))),128))])])):k("",!0)])):k("",!0),t("div",za,[t("div",Xa,[t("div",Ga,[e[46]||(e[46]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"TX Delay",-1)),t("span",Oa,n(Number(s.packet.tx_delay_ms)>0?Number(s.packet.tx_delay_ms).toFixed(1)+"ms":"-"),1)]),t("div",Wa,[e[47]||(e[47]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Transmitted",-1)),t("span",{class:T(s.packet.transmitted?"text-green-600 dark:text-green-400":"text-red-600 dark:text-red-400")},n(s.packet.transmitted?"Yes":"No"),3)])]),t("div",Qa,[t("div",qa,[e[48]||(e[48]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Is Duplicate",-1)),t("span",{class:T(s.packet.is_duplicate?"text-amber-600 dark:text-amber-400":"text-content-muted dark:text-content-muted")},n(s.packet.is_duplicate?"Yes":"No"),3)]),s.packet.drop_reason?(r(),l("div",Ka,[e[49]||(e[49]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Drop Reason",-1)),t("span",Ja,n(s.packet.drop_reason),1)])):k("",!0)])])])])]),t("div",Ya,[t("button",{onClick:e[2]||(e[2]=c=>R("close")),class:"px-6 py-2 bg-gradient-to-r from-cyan-500/20 to-cyan-400/20 hover:from-cyan-500/30 hover:to-cyan-400/30 border border-cyan-400/30 rounded-[10px] text-content-primary dark:text-content-primary transition-all duration-200 backdrop-blur-sm"}," Close ")])])])],32)):k("",!0)]),_:1})]))}}),tn=pt(Za,[["__scopeId","data-v-7f139e4b"]]),en={class:"glass-card rounded-[20px] p-6"},sn={class:"flex flex-col lg:flex-row lg:justify-between lg:items-center mb-6 gap-4 filter-container"},an={class:"flex items-center gap-2 header-info relative"},nn={class:"text-content-secondary dark:text-content-muted text-sm packet-count"},on=["title"],rn={class:"hidden sm:inline"},ln={key:1,class:"text-accent-red text-sm error-indicator"},dn={class:"flex items-center gap-3 lg:flex filter-controls"},cn={class:"flex flex-col"},un=["value"],pn={class:"flex flex-col"},mn=["value"],xn={class:"flex flex-col"},yn={class:"flex flex-col reset-container"},bn=["disabled"],vn={class:"space-y-4 overflow-hidden"},gn={class:"space-y-4"},hn=["onClick"],fn={class:"hidden lg:grid grid-cols-12 gap-2 items-center"},kn={class:"col-span-1 text-content-primary dark:text-content-primary text-sm"},_n={class:"col-span-1 flex items-center gap-2"},wn={class:"flex flex-col"},$n={class:"text-content-primary dark:text-content-primary text-xs"},Tn=["title"],Rn={class:"col-span-2"},Cn={class:"col-span-1 text-content-primary dark:text-content-primary text-xs"},Sn={class:"col-span-2"},Pn={class:"space-y-1"},An={key:0,class:"flex items-center gap-0.5 flex-wrap"},Dn={key:0,class:"w-2.5 h-2.5 text-content-muted dark:text-content-muted/60",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Mn={key:0,class:"text-[9px] text-content-muted dark:text-content-muted ml-1"},Nn={key:1,class:"flex items-center gap-1"},Bn={class:"inline-block px-2 py-0.5 rounded bg-badge-cyan-bg text-badge-cyan-text text-xs font-mono"},Fn={class:"col-span-1 text-content-primary dark:text-content-primary text-xs"},En={class:"col-span-1 text-content-primary dark:text-content-primary text-xs"},jn={class:"col-span-1 text-content-primary dark:text-content-primary text-xs"},In={class:"col-span-1 text-content-primary dark:text-content-primary text-xs"},Ln={key:0,class:"flex items-center gap-1"},Un={class:"col-span-1"},Vn={key:0,class:"text-accent-red text-[8px] italic truncate"},Hn={class:"lg:hidden space-y-2"},zn={class:"flex items-center justify-between"},Xn={class:"flex items-center gap-2"},Gn={class:"flex flex-col"},On={class:"text-content-primary dark:text-content-primary text-sm font-medium"},Wn=["title"],Qn={class:"flex items-center gap-2 text-right"},qn={class:"text-content-secondary dark:text-content-muted text-xs"},Kn={class:"flex items-center justify-between"},Jn={class:"flex items-center gap-1.5"},Yn={key:0,class:"flex items-center gap-0.5"},Zn={key:0,class:"w-2.5 h-2.5 text-content-muted dark:text-content-muted/60",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},to={key:0,class:"text-[9px] text-content-muted dark:text-content-muted ml-1"},eo={class:"flex items-center gap-1"},so={class:"inline-block px-2 py-0.5 rounded bg-badge-cyan-bg text-badge-cyan-text text-xs font-mono font-semibold"},ao={class:"flex items-center gap-0.5 text-content-muted dark:text-content-muted/60"},no={key:0,class:"text-[9px] font-medium",title:"Multi-hop path"},oo={class:"flex items-center gap-1"},ro={class:"flex items-center gap-2"},lo={class:"flex items-center gap-1"},io={key:0,class:"flex gap-0.5"},co={class:"text-content-primary dark:text-content-primary text-xs"},uo={class:"flex items-center justify-between text-content-secondary dark:text-content-muted text-xs"},po={class:"flex items-center gap-3"},mo={class:"flex items-center gap-2"},xo={key:0,class:"flex items-center gap-1"},yo={key:0,class:"text-accent-red text-xs italic"},bo={key:0,class:"flex justify-between items-center mt-6 pt-4 border-t border-stroke-subtle dark:border-stroke pagination-container"},vo={class:"flex items-center gap-4 pagination-info"},go={class:"text-content-secondary dark:text-content-muted text-sm"},ho={key:0,class:"flex items-center gap-2 load-more-section"},fo=["disabled"],ko={class:"text-content-secondary dark:text-content-muted text-xs load-more-count"},_o={class:"flex items-center gap-2 pagination-controls"},wo=["disabled"],$o={class:"flex items-center gap-1 page-numbers"},To={key:1,class:"text-content-secondary dark:text-content-muted text-sm px-2 ellipsis"},Ro=["onClick"],Co={key:2,class:"text-content-secondary dark:text-content-muted text-sm px-2 ellipsis"},So=["disabled"],Po={key:1,class:"flex justify-center mt-6 pt-4 border-t border-stroke-subtle dark:border-stroke"},Ao={class:"flex items-center gap-4"},Do={class:"text-content-secondary dark:text-content-muted text-sm"},Mo={class:"text-content-secondary dark:text-content-muted text-xs"},No={key:2,class:"flex justify-center mt-6 pt-4 border-t border-stroke-subtle dark:border-stroke"},xt=10,lt=1e3,Bo=dt({name:"PacketTable",__name:"PacketTable",setup(at){const _=gt(),W=kt(),m=B(1),R=B(null),$=B(100),P=B(!1),H=B(!1);let A=null;tt(()=>_.isLoading,p=>{p?(A&&(clearTimeout(A),A=null),H.value=!0):A=window.setTimeout(()=>{H.value=!1,A=null},600)});const h=B(null),F=B(!1),X=p=>{h.value=p,F.value=!0},z=()=>{F.value=!1,h.value=null},E=B(Rt("packetTable_selectedType","all")),D=B(Rt("packetTable_selectedRoute","all")),C=B(!1),x=B(null),d=["all","0","1","2","3","4","5","6","7","8","9","10","11"],v=["all","1","2"];tt(E,p=>{Ct("packetTable_selectedType",p),m.value=1}),tt(D,p=>{Ct("packetTable_selectedRoute",p),m.value=1}),tt(C,()=>{m.value=1});const g=K(()=>{let p=_.recentPackets;if(E.value!=="all"){const u=parseInt(E.value);p=p.filter(a=>a.type===u)}if(D.value!=="all"){const u=parseInt(D.value);p=p.filter(a=>a.route===u)}return C.value&&x.value!==null&&(p=p.filter(u=>u.timestamp>=x.value)),p}),M=K(()=>{const p=(m.value-1)*xt,u=p+xt;return g.value.slice(p,u)}),w=K(()=>Math.ceil(g.value.length/xt)),s=K(()=>m.value===w.value),e=K(()=>_.recentPackets.length>=$.value&&$.values.value&&e.value&&!P.value),i=p=>new Date(p*1e3).toLocaleTimeString(void 0,{hour12:!0}),o=p=>({0:"REQ",1:"RESPONSE",2:"TXT_MSG",3:"ACK",4:"ADVERT",5:"GRP_TXT",6:"GRP_DATA",7:"ANON_REQ",8:"PATH",9:"TRACE",10:"MULTI_PART",11:"CONTROL"})[p]||`TYPE_${p}`,b=p=>({0:"T-Flood",1:"Flood",2:"Direct",3:"T-Direct"})[p]||`Route ${p}`,f=p=>p.transmitted?"text-accent-green":"text-primary",S=p=>p.drop_reason?"Dropped":p.transmitted?"Forward":"Received",j=p=>p===1?"bg-badge-cyan-bg text-badge-cyan-text":"bg-badge-neutral-bg text-badge-neutral-text",L=p=>({0:"bg-primary",1:"bg-accent-green",2:"bg-secondary",3:"bg-accent-purple",4:"bg-accent-red",5:"bg-accent-cyan",6:"bg-primary",7:"bg-accent-purple",8:"bg-accent-green",9:"bg-secondary"})[p]||"bg-gray-500",st=p=>({0:"border-l-primary",1:"border-l-accent-green",2:"border-l-secondary",3:"border-l-accent-purple",4:"border-l-accent-red",5:"border-l-accent-cyan",6:"border-l-primary",7:"border-l-accent-purple",8:"border-l-accent-green",9:"border-l-secondary"})[p]||"border-l-gray-500",I=p=>!p.transmitted||!p.lbt_attempts||p.lbt_attempts===0?"bg-green-400":p.lbt_attempts===1?"bg-cyan-400":p.lbt_attempts===2?"bg-yellow-400":"bg-orange-400",Q=p=>p>=1e3?(p/1e3).toFixed(2)+"s":p.toFixed(1)+"ms",U=p=>{if(!p)return[];if(Array.isArray(p))return p;if(typeof p=="string")try{const u=JSON.parse(p);return typeof u=="string"?JSON.parse(u):Array.isArray(u)?u:[]}catch{return[]}return[]},y=p=>{const u=U(p.original_path),a=U(p.forwarded_path),G=u.length>0?u:a;return G.length===0?null:{hops:G.length-1,nodes:G.map(ot=>ot.slice(-4).toUpperCase())}},N=p=>{if(p.type!==4||!p.payload)return null;try{const u=p.payload.replace(/\s+/g,"").toUpperCase();let a=u,G=0;if(u.length/2>=100)if(u.length>200)a=u.slice(200),G=0;else return null;if(a.length>=2){const Z=parseInt(a.slice(0,2),16);G+=2;const Pt=!!(Z&16),At=!!(Z&32),Dt=!!(Z&64);if(!!!(Z&128))return null;if(Pt&&a.length>=G+16&&(G+=16),At&&a.length>=G+4&&(G+=4),Dt&&a.length>=G+4&&(G+=4),a.length>G){const _t=(a.slice(G).match(/.{2}/g)||[]).map(Mt=>{const ht=parseInt(Mt,16);return ht>=32&&ht<=126?String.fromCharCode(ht):"."}).join("").replace(/\.*$/,"");return _t.length>0?_t:null}}}catch(u){console.error("Error parsing ADVERT node name:",u)}return null},J=()=>{E.value="all",D.value="all",C.value=!1,x.value=null,m.value=1},q=()=>{C.value?(C.value=!1,x.value=null):(C.value=!0,x.value=Date.now()/1e3),m.value=1},nt=K(()=>x.value?new Date(x.value*1e3).toLocaleTimeString(void 0,{hour12:!0}):""),Y=async p=>{try{const u=p||$.value;await _.fetchRecentPackets({limit:u})}catch(u){console.error("Error fetching packet data:",u)}},bt=async()=>{if(!(P.value||$.value>=lt)){P.value=!0;try{const p=Math.min($.value+200,lt);$.value=p,await Y(p)}catch(p){console.error("Error loading more records:",p)}finally{P.value=!1}}};return yt(async()=>{await Y(),W.isConnected||(R.value=window.setInterval(Y,1e4))}),tt(()=>W.isConnected,p=>{p?R.value&&(clearInterval(R.value),R.value=null):R.value||(R.value=window.setInterval(Y,1e4))}),vt(()=>{R.value&&clearInterval(R.value),A&&clearTimeout(A)}),(p,u)=>(r(),l(V,null,[t("div",en,[t("div",sn,[t("div",an,[u[7]||(u[7]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold"},"Recent Packets",-1)),t("span",nn," ("+n(g.value.length)+" of "+n(ut(_).recentPackets.length)+") ",1),C.value?(r(),l("span",{key:0,class:"text-primary text-xs sm:text-sm bg-primary/10 px-2 py-1 rounded-md border border-primary/20 live-mode-badge whitespace-nowrap",title:`Filter activated at ${nt.value}`},[t("span",rn,"Live Mode (since "+n(nt.value)+")",1),u[6]||(u[6]=t("span",{class:"sm:hidden"},"Live",-1))],8,on)):k("",!0),ut(_).error?(r(),l("span",ln,n(ut(_).error),1)):k("",!0)]),t("div",dn,[t("div",cn,[u[8]||(u[8]=t("label",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Type",-1)),$t(t("select",{"onUpdate:modelValue":u[0]||(u[0]=a=>E.value=a),class:"glass-card border border-stroke-subtle dark:border-stroke rounded-[10px] px-3 py-2 text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20 transition-all duration-200 min-w-[120px] cursor-pointer hover:border-primary/50"},[(r(),l(V,null,O(d,a=>t("option",{key:a,value:a,class:"bg-surface dark:bg-surface-elevated text-content-primary dark:text-content-primary"},n(a==="all"?"All Types":`Type ${a} (${o(parseInt(a))})`),9,un)),64))],512),[[Tt,E.value]])]),t("div",pn,[u[9]||(u[9]=t("label",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Route",-1)),$t(t("select",{"onUpdate:modelValue":u[1]||(u[1]=a=>D.value=a),class:"glass-card border border-stroke-subtle dark:border-stroke rounded-[10px] px-3 py-2 text-content-primary dark:text-content-primary text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20 transition-all duration-200 min-w-[120px] cursor-pointer hover:border-primary/50"},[(r(),l(V,null,O(v,a=>t("option",{key:a,value:a,class:"bg-surface dark:bg-surface-elevated text-content-primary dark:text-content-primary"},n(a==="all"?"All Routes":`Route ${a} (${b(parseInt(a))})`),9,mn)),64))],512),[[Tt,D.value]])]),t("div",xn,[u[10]||(u[10]=t("label",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Filter",-1)),t("button",{onClick:q,class:T(["glass-card border rounded-[10px] px-4 py-2 text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-primary/20 min-w-[120px]",{"border-primary bg-primary/10 text-primary":C.value,"border-stroke-subtle dark:border-stroke text-content-secondary dark:text-content-muted hover:border-primary hover:text-content-primary dark:hover:text-content-primary hover:bg-primary/5":!C.value}])},n(C.value?"New Only":"Show New"),3)]),t("div",yn,[u[11]||(u[11]=t("label",{class:"text-transparent text-xs mb-1"},".",-1)),t("button",{onClick:J,class:T(["glass-card border border-stroke-subtle dark:border-stroke hover:border-primary rounded-[10px] px-4 py-2 text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary text-sm transition-all duration-200 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20",{"opacity-50 cursor-not-allowed hover:border-stroke-subtle dark:hover:border-stroke hover:text-content-secondary dark:hover:text-content-muted":E.value==="all"&&D.value==="all"&&!C.value,"hover:bg-primary/10":E.value!=="all"||D.value!=="all"||C.value}]),disabled:E.value==="all"&&D.value==="all"&&!C.value}," Reset ",10,bn)])])]),u[25]||(u[25]=St('',1)),t("div",vn,[t("div",gn,[(r(!0),l(V,null,O(M.value,(a,G)=>(r(),l("div",{key:`${a.packet_hash}_${a.timestamp}_${G}`,class:T(["packet-row border-b border-stroke-subtle dark:border-dark-border/50 pb-4 hover:bg-background-mute dark:hover:bg-stroke/5 transition-colors duration-150 cursor-pointer rounded-[10px] p-2 border-l-4",st(a.type)]),onClick:ot=>X(a)},[t("div",fn,[t("div",kn,n(i(a.timestamp)),1),t("div",_n,[t("div",{class:T(["w-2 h-2 rounded-full",L(a.type)])},null,2),t("div",wn,[t("span",$n,n(o(a.type)),1),a.type===4&&N(a)?(r(),l("span",{key:0,class:"text-accent-red/70 text-[10px] font-medium max-w-[80px] truncate",title:N(a)||void 0},n(N(a)),9,Tn)):k("",!0)])]),t("div",Rn,[t("span",{class:T(["inline-block px-2 py-1 rounded text-xs font-medium",j(a.route)])},n(b(a.route)),3)]),t("div",Cn,n(a.length)+"B",1),t("div",Sn,[t("div",Pn,[y(a)?(r(),l("div",An,[(r(!0),l(V,null,O(y(a).nodes,(ot,Z)=>(r(),l(V,{key:Z},[t("span",{class:T(["inline-block px-1.5 py-0.5 rounded text-[10px] font-mono font-semibold",Z===0?"bg-badge-cyan-bg text-badge-cyan-text":"bg-gray-500/20 text-content-muted dark:text-content-muted"])},n(ot),3),Z0?(r(),l("span",Mn," ("+n(y(a).hops)+" hop"+n(y(a).hops>1?"s":"")+") ",1)):k("",!0)])):(r(),l("div",Nn,[t("span",Bn,n(a.src_hash?.slice(-4).toUpperCase()||"????"),1),u[13]||(u[13]=t("svg",{class:"w-3 h-3 text-content-muted dark:text-content-muted/60",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2.5",d:"M9 5l7 7-7 7"})],-1)),t("span",{class:T(["inline-block px-2 py-0.5 rounded text-xs font-mono",a.dst_hash?"bg-badge-cyan-bg text-badge-cyan-text":"bg-yellow-500/20 text-yellow-700 dark:text-yellow-300"])},n(a.dst_hash?a.dst_hash.slice(-4).toUpperCase():"BCAST"),3)]))])]),t("div",Fn,n(a.rssi!=null?a.rssi.toFixed(0):"N/A"),1),t("div",En,n(a.snr!=null?a.snr.toFixed(1)+"dB":"N/A"),1),t("div",jn,n(a.score!=null?a.score.toFixed(2):"N/A"),1),t("div",In,[Number(a.tx_delay_ms)>0?(r(),l("div",Ln,[a.transmitted?(r(),l("div",{key:0,class:T(["w-1.5 h-1.5 rounded-full flex-shrink-0",I(a)])},null,2)):k("",!0),t("span",null,n(Q(Number(a.tx_delay_ms))),1)])):k("",!0)]),t("div",Un,[t("div",null,[t("span",{class:T(["text-xs font-medium",f(a)])},n(S(a)),3),a.drop_reason?(r(),l("p",Vn,n(a.drop_reason),1)):k("",!0)])])]),t("div",Hn,[t("div",zn,[t("div",Xn,[t("div",{class:T(["w-2 h-2 rounded-full flex-shrink-0",L(a.type)])},null,2),t("div",Gn,[t("span",On,n(o(a.type)),1),a.type===4&&N(a)?(r(),l("span",{key:0,class:"text-accent-red/70 text-[10px] font-medium leading-tight",title:N(a)||void 0},n(N(a)),9,Wn)):k("",!0)]),t("span",{class:T(["inline-block px-2 py-1 rounded text-xs font-medium ml-2",j(a.route)])},n(b(a.route)),3)]),t("div",Qn,[t("span",qn,n(i(a.timestamp)),1),t("span",{class:T(["text-xs font-medium",f(a)])},n(S(a)),3)])]),t("div",Kn,[t("div",Jn,[y(a)?(r(),l("div",Yn,[u[15]||(u[15]=t("span",{class:"text-content-muted dark:text-content-muted text-[10px] font-medium"},"PATH",-1)),(r(!0),l(V,null,O(y(a).nodes,(ot,Z)=>(r(),l(V,{key:Z},[t("span",{class:T(["inline-block px-1.5 py-0.5 rounded text-[10px] font-mono font-semibold",Z===0?"bg-badge-cyan-bg text-badge-cyan-text":"bg-gray-500/20 text-content-muted dark:text-content-muted"])},n(ot),3),Z0?(r(),l("span",to," ("+n(y(a).hops)+" hop"+n(y(a).hops>1?"s":"")+") ",1)):k("",!0)])):(r(),l(V,{key:1},[t("div",eo,[u[16]||(u[16]=t("span",{class:"text-content-muted dark:text-content-muted text-[10px] font-medium"},"SRC",-1)),t("span",so,n(a.src_hash?.slice(-4)||"????"),1)]),t("div",ao,[u[18]||(u[18]=t("svg",{class:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2.5",d:"M9 5l7 7-7 7"})],-1)),a.route===1?(r(),l("span",no,u[17]||(u[17]=[t("svg",{class:"w-2.5 h-2.5 inline",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13 5l7 7-7 7M5 5l7 7-7 7"})],-1)]))):k("",!0)]),t("div",oo,[t("span",{class:T(["inline-block px-2 py-0.5 rounded text-xs font-mono font-semibold",a.dst_hash?"bg-badge-cyan-bg text-badge-cyan-text":"bg-yellow-500/20 text-yellow-700 dark:text-yellow-300"])},n(a.dst_hash?a.dst_hash.slice(-4).toUpperCase():"BCAST"),3),u[19]||(u[19]=t("span",{class:"text-content-muted dark:text-content-muted text-[10px] font-medium"},"DST",-1))])],64))]),t("div",ro,[t("div",lo,[a.snr!=null?(r(),l("div",io,[t("div",{class:T(["w-1 h-3 rounded-sm",a.snr>=-10?"bg-green-400":"bg-white/20"])},null,2),t("div",{class:T(["w-1 h-4 rounded-sm",a.snr>=-5?"bg-green-400":"bg-white/20"])},null,2),t("div",{class:T(["w-1 h-5 rounded-sm",a.snr>=0?"bg-green-400":"bg-white/20"])},null,2),t("div",{class:T(["w-1 h-6 rounded-sm",a.snr>=10?"bg-green-400":"bg-white/20"])},null,2)])):k("",!0),t("span",co,n(a.rssi!=null?a.rssi.toFixed(0)+"dBm":"TX"),1)])])]),t("div",uo,[t("div",po,[t("span",null,n(a.length)+"B",1),t("span",null,"SNR: "+n(a.snr!=null?a.snr.toFixed(1)+"dB":"N/A"),1),t("span",null,"Score: "+n(a.score!=null?a.score.toFixed(2):"N/A"),1)]),t("div",mo,[Number(a.tx_delay_ms)>0?(r(),l("span",xo,[a.transmitted?(r(),l("div",{key:0,class:T(["w-1.5 h-1.5 rounded-full flex-shrink-0",I(a)])},null,2)):k("",!0),t("span",null,n(Q(Number(a.tx_delay_ms))),1)])):k("",!0)])]),a.drop_reason?(r(),l("div",yo,n(a.drop_reason),1)):k("",!0)])],10,hn))),128))])]),w.value>1?(r(),l("div",bo,[t("div",vo,[t("span",go," Showing "+n((m.value-1)*xt+1)+" - "+n(Math.min(m.value*xt,g.value.length))+" of "+n(g.value.length)+" packets ",1),c.value?(r(),l("div",ho,[u[20]||(u[20]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs"},"•",-1)),t("button",{onClick:bt,disabled:P.value,class:T(["glass-card border border-primary rounded-[8px] px-3 py-1.5 text-xs transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-primary/20 hover:bg-primary/5",{"text-primary border-primary cursor-pointer":!P.value,"text-content-secondary dark:text-content-muted border-stroke-subtle dark:border-stroke cursor-not-allowed opacity-50":P.value}])},n(P.value?"Loading...":`Load ${Math.min(200,lt-$.value)} more`),11,fo),t("span",ko,"("+n($.value)+"/"+n(lt)+" max)",1)])):k("",!0)]),t("div",_o,[t("button",{onClick:u[2]||(u[2]=a=>m.value=m.value-1),disabled:m.value<=1,class:T(["glass-card border rounded-[10px] px-3 py-2 text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-primary/20 prev-next-btn",{"border-stroke-subtle dark:border-stroke text-content-muted dark:text-content-muted cursor-not-allowed opacity-50":m.value<=1,"border-stroke-subtle dark:border-stroke text-content-primary dark:text-content-primary hover:border-primary hover:text-primary hover:bg-primary/5":m.value>1}])},u[21]||(u[21]=[t("span",{class:"hidden sm:inline"},"Previous",-1),t("span",{class:"sm:hidden"},"‹",-1)]),10,wo),t("div",$o,[m.value>3?(r(),l("button",{key:0,onClick:u[3]||(u[3]=a=>m.value=1),class:"glass-card border border-stroke-subtle dark:border-stroke hover:border-primary rounded-[8px] px-3 py-2 text-sm text-content-primary dark:text-content-primary hover:text-primary hover:bg-primary/5 transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-primary/20"}," 1 ")):k("",!0),m.value>4?(r(),l("span",To,"...")):k("",!0),(r(!0),l(V,null,O(Array.from({length:Math.min(5,w.value)},(a,G)=>Math.max(1,Math.min(m.value-2,w.value-4))+G).filter(a=>a<=w.value),a=>(r(),l("button",{key:a,onClick:G=>m.value=a,class:T(["glass-card border rounded-[8px] px-3 py-2 text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-primary/20 page-number",{"border-primary bg-primary/10 text-primary":m.value===a,"border-stroke-subtle dark:border-stroke text-content-primary dark:text-content-primary hover:border-primary hover:text-primary hover:bg-primary/5":m.value!==a}])},n(a),11,Ro))),128)),m.valuem.value=w.value),class:"glass-card border border-stroke-subtle dark:border-stroke hover:border-primary rounded-[8px] px-3 py-2 text-sm text-content-primary dark:text-content-primary hover:text-primary hover:bg-primary/5 transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-primary/20"},n(w.value),1)):k("",!0)]),t("button",{onClick:u[5]||(u[5]=a=>m.value=m.value+1),disabled:m.value>=w.value,class:T(["glass-card border rounded-[10px] px-3 py-2 text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-primary/20 prev-next-btn",{"border-stroke-subtle dark:border-stroke text-content-muted dark:text-content-muted cursor-not-allowed opacity-50":m.value>=w.value,"border-stroke-subtle dark:border-stroke text-content-primary dark:text-content-primary hover:border-primary hover:text-primary hover:bg-primary/5":m.value(r(),l("div",null,[et(ee),t("div",Eo,[et(je),et(fe)]),et(Fo)]))}});export{Xo as default}; diff --git a/repeater/web/html/assets/Dashboard-DyVMkajK.css b/repeater/web/html/assets/Dashboard-DyVMkajK.css new file mode 100644 index 0000000..66de429 --- /dev/null +++ b/repeater/web/html/assets/Dashboard-DyVMkajK.css @@ -0,0 +1 @@ +.sparkline-card[data-v-814635af]{background:#ffffffbf;border:1px solid rgba(0,0,0,.06);border-radius:12px;padding:12px 14px;-webkit-backdrop-filter:blur(50px);backdrop-filter:blur(50px);overflow:hidden;transition:background .3s ease,border-color .3s ease,box-shadow .3s ease;box-shadow:0 4px 16px #0000000a,0 1px 3px #00000005}.dark .sparkline-card[data-v-814635af]{background:#0006;border:1px solid rgba(255,255,255,.05);box-shadow:0 4px 16px #0003}.card-header[data-v-814635af]{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px}.card-title[data-v-814635af]{color:#4b5563b3;font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.05em;transition:color .3s ease}.dark .card-title[data-v-814635af]{color:#fff9}.card-value[data-v-814635af]{font-size:22px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums}.card-values[data-v-814635af]{display:flex;align-items:baseline;gap:6px}.card-secondary-value[data-v-814635af]{font-size:13px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums;opacity:.85}.card-chart[data-v-814635af]{width:100%;height:28px;overflow:hidden}.card-chart canvas[data-v-814635af]{width:100%!important;height:100%!important}@media (min-width: 1024px){.sparkline-card[data-v-814635af]{padding:14px 16px}.card-header[data-v-814635af]{margin-bottom:10px}.card-title[data-v-814635af]{font-size:12px}.card-value[data-v-814635af]{font-size:26px}.card-chart[data-v-814635af]{height:32px}}.stats-cards-container[data-v-84cee3fb]{will-change:auto;contain:layout}.stat-card[data-v-84cee3fb]{transition:opacity .3s ease-out}.stat-card[data-v-84cee3fb] .text-lg,.stat-card[data-v-84cee3fb] .text-\[30px\]{transition:color .2s ease-out}canvas[data-v-6bf3fe96]{width:100%;height:100%}.modal-enter-active[data-v-7f139e4b]{transition:all .3s cubic-bezier(.4,0,.2,1)}.modal-leave-active[data-v-7f139e4b]{transition:all .2s ease-in}.modal-enter-from[data-v-7f139e4b]{opacity:0;transform:scale(.95) translateY(-10px)}.modal-leave-to[data-v-7f139e4b]{opacity:0;transform:scale(1.05)}.custom-scrollbar[data-v-7f139e4b]{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}.custom-scrollbar[data-v-7f139e4b]::-webkit-scrollbar{width:6px}.custom-scrollbar[data-v-7f139e4b]::-webkit-scrollbar-track{background:#ffffff1a;border-radius:3px}.custom-scrollbar[data-v-7f139e4b]::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.custom-scrollbar[data-v-7f139e4b]::-webkit-scrollbar-thumb:hover{background:#fff6}.glass-card[data-v-7f139e4b]{-webkit-backdrop-filter:blur(50px);backdrop-filter:blur(50px)}.fade-enter-active[data-v-ec32abd6],.fade-leave-active[data-v-ec32abd6]{transition:opacity .3s ease-out,transform .3s ease-out}.fade-enter-from[data-v-ec32abd6],.fade-leave-to[data-v-ec32abd6]{opacity:0;transform:translateY(-10px)}@keyframes spin-ec32abd6{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.animate-spin[data-v-ec32abd6]{animation:spin-ec32abd6 .8s linear infinite}.packet-list-enter-active[data-v-ec32abd6],.packet-list-leave-active[data-v-ec32abd6],.packet-list-move[data-v-ec32abd6]{transition:all .4s ease-out}.packet-list-enter-from[data-v-ec32abd6]{opacity:0;transform:translateY(-30px) scale(.98)}.packet-list-enter-to[data-v-ec32abd6],.packet-list-leave-from[data-v-ec32abd6]{opacity:1;transform:translateY(0) scale(1)}.packet-list-leave-to[data-v-ec32abd6]{opacity:0;transform:translateY(-20px) scale(.95)}.packet-row[data-v-ec32abd6]{position:relative;transition:all .3s ease}.packet-list-enter-active .packet-row[data-v-ec32abd6]{background:linear-gradient(90deg,rgba(78,201,176,.1) 0%,rgba(78,201,176,.05) 50%,transparent 100%);box-shadow:0 0 20px #4ec9b033;border-left:3px solid rgba(78,201,176,.6);border-radius:8px;padding-left:12px}.packet-row[data-v-ec32abd6]:hover{background:#ffffff05;border-radius:8px;transition:background .2s ease}@media (max-width: 1023px){.filter-container[data-v-ec32abd6]{flex-direction:column;gap:1rem;align-items:stretch}.header-info[data-v-ec32abd6]{flex-direction:column;align-items:flex-start;gap:.5rem}.packet-count[data-v-ec32abd6]{order:1}.live-mode-badge[data-v-ec32abd6]{order:2;align-self:flex-start}.loading-indicator[data-v-ec32abd6],.error-indicator[data-v-ec32abd6]{order:3;align-self:flex-start}.filter-controls[data-v-ec32abd6]{display:grid!important;grid-template-columns:1fr 1fr;gap:.75rem;flex-direction:column}.filter-controls .flex.flex-col[data-v-ec32abd6]{flex-direction:column;align-items:stretch;gap:.25rem}.filter-controls .flex.flex-col label[data-v-ec32abd6]{margin-bottom:0;font-size:.75rem}.reset-container[data-v-ec32abd6]{grid-column:span 2!important;display:flex;justify-content:center;margin-top:.5rem}.pagination-container[data-v-ec32abd6]{flex-direction:column;gap:1rem;align-items:stretch}.pagination-info[data-v-ec32abd6]{justify-content:center;text-align:center;flex-direction:column;gap:.5rem}.load-more-section[data-v-ec32abd6]{justify-content:center}.load-more-count[data-v-ec32abd6]{display:none}.pagination-controls[data-v-ec32abd6]{justify-content:center}.page-numbers[data-v-ec32abd6]{max-width:200px;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.page-numbers[data-v-ec32abd6]::-webkit-scrollbar{display:none}.ellipsis[data-v-ec32abd6]{display:none}.page-number[data-v-ec32abd6]{min-width:40px;flex-shrink:0}}@media (max-width: 640px){.filter-controls[data-v-ec32abd6]{grid-template-columns:1fr!important;gap:.75rem}.reset-container[data-v-ec32abd6]{grid-column:span 1!important}.header-info h3[data-v-ec32abd6]{font-size:1.125rem}.packet-count[data-v-ec32abd6]{font-size:.75rem}.live-mode-badge[data-v-ec32abd6]{font-size:.75rem;padding:.25rem .5rem}.pagination-info span[data-v-ec32abd6]{font-size:.75rem}.prev-next-btn[data-v-ec32abd6]{min-width:40px;padding:.5rem}.page-numbers[data-v-ec32abd6]{max-width:150px;gap:.25rem}.page-number[data-v-ec32abd6]{min-width:36px;padding:.5rem .25rem;font-size:.75rem}.load-more-section button[data-v-ec32abd6]{font-size:.6rem;padding:.375rem .75rem}} diff --git a/repeater/web/html/assets/Login-BwBtx78C.js b/repeater/web/html/assets/Login-BwBtx78C.js index 49bcddb..ac5b4c0 100644 --- a/repeater/web/html/assets/Login-BwBtx78C.js +++ b/repeater/web/html/assets/Login-BwBtx78C.js @@ -1 +1,5 @@ +<<<<<<<< HEAD:repeater/web/html/assets/Login-BwBtx78C.js import{a as P,r as a,b as i,g,s as _,e,w as v,v as h,t as y,k as M,y as S,p as u,f as w,_ as $,i as N,G as j,C as B,z as D,m as I,A as L,B as C,x as U}from"./index-DyUIpN7m.js";const q={class:"glass-card backdrop-blur-xl border border-stroke-subtle dark:border-white/20 rounded-[15px] p-6 max-w-md w-full shadow-2xl"},E={key:0,class:"bg-red-500/10 border border-red-500/30 rounded-lg p-3"},z={class:"text-red-600 dark:text-red-400 text-sm"},T={key:1,class:"bg-green-500/10 border border-green-600/40 dark:border-green-500/30 rounded-lg p-3"},G={class:"text-green-600 dark:text-green-400 text-sm"},H={class:"flex justify-end gap-3 mt-6"},F=["disabled"],O=["disabled"],R={key:0,class:"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"},A=P({name:"ChangePasswordModal",__name:"ChangePasswordModal",props:{isOpen:{type:Boolean},canSkip:{type:Boolean,default:!0}},emits:["close","success"],setup(V,{emit:x}){const p=x,l=a(""),s=a(""),d=a(""),o=a(!1),n=a(""),m=a(""),f=()=>{o.value||p("close")},k=()=>{p("close")},c=async()=>{if(n.value="",m.value="",s.value.length<8){n.value="New password must be at least 8 characters long";return}if(s.value!==d.value){n.value="Passwords do not match";return}if(s.value===l.value){n.value="New password must be different from current password";return}o.value=!0;try{const r=(await S.post("/auth/change_password",{current_password:l.value,new_password:s.value})).data;r&&r.success?(m.value=r.message||"Password changed successfully!",setTimeout(()=>{p("success"),p("close")},1500)):n.value=r?.error||"Failed to change password"}catch(t){console.error("Password change error:",t),n.value=t.response?.data?.error||"Failed to change password. Please try again."}finally{o.value=!1}};return(t,r)=>t.isOpen?(u(),i("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm",onClick:_(f,["self"])},[e("div",q,[r[6]||(r[6]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary mb-2"},"Change Default Password",-1)),r[7]||(r[7]=e("p",{class:"text-content-secondary dark:text-content-muted text-sm mb-6"}," You're using the default password. Please change it to secure your account. ",-1)),e("form",{onSubmit:_(c,["prevent"]),class:"space-y-4"},[e("div",null,[r[3]||(r[3]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-primary/70 mb-2"},"Current Password",-1)),v(e("input",{"onUpdate:modelValue":r[0]||(r[0]=b=>l.value=b),type:"password",required:"",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:border-primary transition-colors",placeholder:"Enter current password"},null,512),[[h,l.value]])]),e("div",null,[r[4]||(r[4]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-primary/70 mb-2"},"New Password",-1)),v(e("input",{"onUpdate:modelValue":r[1]||(r[1]=b=>s.value=b),type:"password",required:"",minlength:"8",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:border-primary transition-colors",placeholder:"Enter new password (min 8 characters)"},null,512),[[h,s.value]])]),e("div",null,[r[5]||(r[5]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-primary/70 mb-2"},"Confirm New Password",-1)),v(e("input",{"onUpdate:modelValue":r[2]||(r[2]=b=>d.value=b),type:"password",required:"",minlength:"8",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:border-primary transition-colors",placeholder:"Confirm new password"},null,512),[[h,d.value]])]),n.value?(u(),i("div",E,[e("p",z,y(n.value),1)])):g("",!0),m.value?(u(),i("div",T,[e("p",G,y(m.value),1)])):g("",!0),e("div",H,[t.canSkip?(u(),i("button",{key:0,type:"button",onClick:k,disabled:o.value,class:"px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/10 transition-colors disabled:opacity-50"}," Skip for Now ",8,F)):g("",!0),e("button",{type:"submit",disabled:o.value,class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-white rounded-lg border border-primary/50 transition-colors disabled:opacity-50 flex items-center gap-2"},[o.value?(u(),i("div",R)):g("",!0),M(" "+y(o.value?"Changing...":"Change Password"),1)],8,O)])],32)])])):g("",!0)}}),Y={class:"min-h-screen bg-background dark:bg-background overflow-hidden relative flex items-start sm:items-center justify-center p-2 sm:p-4 pt-8 sm:pt-4"},J={class:"absolute top-4 right-4 z-20"},K={class:"login-card relative z-10 w-full max-w-md p-6 sm:p-10 rounded-[16px] sm:rounded-[24px] border-0 sm:border sm:border-stroke-subtle dark:sm:border-stroke/20 shadow-[0_8px_32px_0_rgba(0,0,0,0.1)] dark:shadow-[0_8px_32px_0_rgba(0,0,0,0.37)] backdrop-blur-xl"},Q={class:"relative login-content"},W={class:"form-group"},X={class:"relative"},Z=["disabled"],ee={class:"form-group"},te={class:"relative"},re=["disabled"],se={key:0,class:"bg-red-500/10 border border-red-500/30 rounded-[12px] p-2.5 sm:p-3.5 backdrop-blur-sm animate-shake"},oe={class:"text-red-600 dark:text-red-400 text-xs sm:text-sm font-medium"},ae=["disabled"],ne={key:0,class:"w-4 h-4 sm:w-5 sm:h-5 border-2 border-white border-t-transparent rounded-full animate-spin"},le={key:1,class:"w-4 h-4 sm:w-5 sm:h-5 group-hover:translate-x-1 transition-transform duration-300",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},de={class:"relative"},ie={class:"mt-6 sm:mt-8 pt-4 sm:pt-6 border-t border-stroke-subtle dark:border-stroke/10"},ue={class:"flex items-center justify-center gap-3"},pe={href:"https://github.com/rightup",target:"_blank",class:"inline-flex items-center justify-center w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-content-primary dark:bg-white/10 border border-stroke-subtle dark:border-stroke/20 hover:bg-primary/20 dark:hover:bg-primary/30 hover:border-primary/50 transition-all duration-300 hover:scale-110 group backdrop-blur-sm",title:"GitHub"},ce={href:"https://buymeacoffee.com/rightup",target:"_blank",class:"inline-flex items-center justify-center w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-content-primary dark:bg-white/10 border border-stroke-subtle dark:border-stroke/20 hover:bg-yellow-50 dark:hover:bg-yellow-500/20 hover:border-yellow-500/50 transition-all duration-300 hover:scale-110 group backdrop-blur-sm",title:"Buy Me a Coffee"},me=P({name:"LoginView",__name:"Login",setup(V){const x=I(),p=a("admin"),l=a(""),s=a(!1),d=a(""),o=a(!1),n=a(!1),m=async()=>{d.value="",s.value=!0;try{const c=L(),r=(await S.post("/auth/login",{username:p.value,password:l.value,client_id:c})).data;r.success&&r.token?l.value==="admin123"?(C(r.token),n.value=!0,o.value=!0):(C(r.token),x.push("/")):d.value=r.error||"Login failed"}catch(c){console.error("Login error:",c);const t=c;d.value=t.response?.data?.error||"Connection error. Please try again."}finally{s.value=!1}},f=()=>{o.value=!1,x.push("/")},k=()=>{o.value=!1,n.value&&x.push("/")};return(c,t)=>(u(),i("div",Y,[e("div",J,[w($)]),t[9]||(t[9]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slow -top-[79px] left-[575px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[10]||(t[10]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-75 animate-pulse-slower -top-[94px] -left-[92px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[11]||(t[11]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slowest top-[373px] left-[246px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),e("div",K,[t[8]||(t[8]=e("div",{class:"absolute inset-0 rounded-[24px] bg-gradient-to-br from-primary/3 dark:from-primary/5 to-transparent pointer-events-none"},null,-1)),e("div",Q,[t[7]||(t[7]=N('
MeshCore

pyMC Repeater

Sign in to access your dashboard

',1)),e("form",{onSubmit:_(m,["prevent"]),class:"space-y-4 sm:space-y-5"},[e("div",W,[t[3]||(t[3]=e("label",{for:"username",class:"block text-content-secondary dark:text-content-primary/90 text-xs sm:text-sm font-medium mb-2"}," Username ",-1)),e("div",X,[v(e("input",{id:"username","onUpdate:modelValue":t[0]||(t[0]=r=>p.value=r),type:"text",autocomplete:"username",required:"",class:"input-glass w-full px-3 sm:px-4 py-2.5 sm:py-3.5 rounded-[12px] text-content-primary dark:text-content-primary text-sm placeholder-gray-400 dark:placeholder-white/30 focus:outline-none focus:border-primary/50 transition-all duration-300",placeholder:"Enter username",disabled:s.value},null,8,Z),[[h,p.value]]),t[2]||(t[2]=e("div",{class:"absolute inset-0 rounded-[12px] pointer-events-none input-glow"},null,-1))])]),e("div",ee,[t[5]||(t[5]=e("label",{for:"password",class:"block text-content-secondary dark:text-content-primary/90 text-xs sm:text-sm font-medium mb-2"}," Password ",-1)),e("div",te,[v(e("input",{id:"password","onUpdate:modelValue":t[1]||(t[1]=r=>l.value=r),type:"password",autocomplete:"current-password",required:"",class:"input-glass w-full px-3 sm:px-4 py-2.5 sm:py-3.5 rounded-[12px] text-content-primary dark:text-content-primary text-sm placeholder-gray-400 dark:placeholder-white/30 focus:outline-none focus:border-primary/50 transition-all duration-300",placeholder:"Enter password",disabled:s.value},null,8,re),[[h,l.value]]),t[4]||(t[4]=e("div",{class:"absolute inset-0 rounded-[12px] pointer-events-none input-glow"},null,-1))])]),d.value?(u(),i("div",se,[e("p",oe,y(d.value),1)])):g("",!0),e("button",{type:"submit",disabled:s.value,class:"button-glass w-full relative overflow-hidden bg-primary/20 hover:bg-primary/30 active:scale-[0.98] text-primary dark:text-white font-semibold py-3 sm:py-4 px-4 rounded-[12px] border border-primary/50 hover:border-primary/60 transition-all duration-300 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 sm:gap-2.5 group mt-6 sm:mt-8 text-sm sm:text-base backdrop-blur-sm"},[s.value?(u(),i("div",ne)):(u(),i("svg",le,t[6]||(t[6]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"},null,-1)]))),e("span",de,y(s.value?"Signing in...":"Sign In"),1)],8,ae)],32),e("div",ie,[e("div",ue,[e("a",pe,[w(j,{class:"w-5 h-5 sm:w-6 sm:h-6 text-white group-hover:text-primary transition-colors"})]),e("a",ce,[w(B,{class:"w-5 h-5 sm:w-6 sm:h-6 text-white group-hover:text-yellow-500 transition-colors"})])])])])]),w(A,{"is-open":o.value,"can-skip":!0,onClose:k,onSuccess:f},null,8,["is-open"])]))}}),ge=U(me,[["__scopeId","data-v-7d3a3377"]]);export{ge as default}; +======== +import{a as P,r as a,b as i,g,s as _,e,w as v,v as h,t as y,k as M,y as S,p as u,f as w,_ as $,i as N,G as j,C as B,z as D,m as I,A as L,B as C,x as U}from"./index-BfUIlcDy.js";const q={class:"glass-card backdrop-blur-xl border border-stroke-subtle dark:border-white/20 rounded-[15px] p-6 max-w-md w-full shadow-2xl"},E={key:0,class:"bg-red-500/10 border border-red-500/30 rounded-lg p-3"},z={class:"text-red-600 dark:text-red-400 text-sm"},T={key:1,class:"bg-green-500/10 border border-green-600/40 dark:border-green-500/30 rounded-lg p-3"},G={class:"text-green-600 dark:text-green-400 text-sm"},H={class:"flex justify-end gap-3 mt-6"},F=["disabled"],O=["disabled"],R={key:0,class:"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"},A=P({name:"ChangePasswordModal",__name:"ChangePasswordModal",props:{isOpen:{type:Boolean},canSkip:{type:Boolean,default:!0}},emits:["close","success"],setup(V,{emit:x}){const p=x,l=a(""),s=a(""),d=a(""),o=a(!1),n=a(""),m=a(""),f=()=>{o.value||p("close")},k=()=>{p("close")},c=async()=>{if(n.value="",m.value="",s.value.length<8){n.value="New password must be at least 8 characters long";return}if(s.value!==d.value){n.value="Passwords do not match";return}if(s.value===l.value){n.value="New password must be different from current password";return}o.value=!0;try{const r=(await S.post("/auth/change_password",{current_password:l.value,new_password:s.value})).data;r&&r.success?(m.value=r.message||"Password changed successfully!",setTimeout(()=>{p("success"),p("close")},1500)):n.value=r?.error||"Failed to change password"}catch(t){console.error("Password change error:",t),n.value=t.response?.data?.error||"Failed to change password. Please try again."}finally{o.value=!1}};return(t,r)=>t.isOpen?(u(),i("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm",onClick:_(f,["self"])},[e("div",q,[r[6]||(r[6]=e("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary mb-2"},"Change Default Password",-1)),r[7]||(r[7]=e("p",{class:"text-content-secondary dark:text-content-muted text-sm mb-6"}," You're using the default password. Please change it to secure your account. ",-1)),e("form",{onSubmit:_(c,["prevent"]),class:"space-y-4"},[e("div",null,[r[3]||(r[3]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-primary/70 mb-2"},"Current Password",-1)),v(e("input",{"onUpdate:modelValue":r[0]||(r[0]=b=>l.value=b),type:"password",required:"",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:border-primary transition-colors",placeholder:"Enter current password"},null,512),[[h,l.value]])]),e("div",null,[r[4]||(r[4]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-primary/70 mb-2"},"New Password",-1)),v(e("input",{"onUpdate:modelValue":r[1]||(r[1]=b=>s.value=b),type:"password",required:"",minlength:"8",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:border-primary transition-colors",placeholder:"Enter new password (min 8 characters)"},null,512),[[h,s.value]])]),e("div",null,[r[5]||(r[5]=e("label",{class:"block text-sm font-medium text-content-secondary dark:text-content-primary/70 mb-2"},"Confirm New Password",-1)),v(e("input",{"onUpdate:modelValue":r[2]||(r[2]=b=>d.value=b),type:"password",required:"",minlength:"8",class:"w-full px-4 py-2 bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:border-primary transition-colors",placeholder:"Confirm new password"},null,512),[[h,d.value]])]),n.value?(u(),i("div",E,[e("p",z,y(n.value),1)])):g("",!0),m.value?(u(),i("div",T,[e("p",G,y(m.value),1)])):g("",!0),e("div",H,[t.canSkip?(u(),i("button",{key:0,type:"button",onClick:k,disabled:o.value,class:"px-4 py-2 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary rounded-lg border border-stroke-subtle dark:border-stroke/10 transition-colors disabled:opacity-50"}," Skip for Now ",8,F)):g("",!0),e("button",{type:"submit",disabled:o.value,class:"px-4 py-2 bg-primary/20 hover:bg-primary/30 text-white rounded-lg border border-primary/50 transition-colors disabled:opacity-50 flex items-center gap-2"},[o.value?(u(),i("div",R)):g("",!0),M(" "+y(o.value?"Changing...":"Change Password"),1)],8,O)])],32)])])):g("",!0)}}),Y={class:"min-h-screen bg-background dark:bg-background overflow-hidden relative flex items-start sm:items-center justify-center p-2 sm:p-4 pt-8 sm:pt-4"},J={class:"absolute top-4 right-4 z-20"},K={class:"login-card relative z-10 w-full max-w-md p-6 sm:p-10 rounded-[16px] sm:rounded-[24px] border-0 sm:border sm:border-stroke-subtle dark:sm:border-stroke/20 shadow-[0_8px_32px_0_rgba(0,0,0,0.1)] dark:shadow-[0_8px_32px_0_rgba(0,0,0,0.37)] backdrop-blur-xl"},Q={class:"relative login-content"},W={class:"form-group"},X={class:"relative"},Z=["disabled"],ee={class:"form-group"},te={class:"relative"},re=["disabled"],se={key:0,class:"bg-red-500/10 border border-red-500/30 rounded-[12px] p-2.5 sm:p-3.5 backdrop-blur-sm animate-shake"},oe={class:"text-red-600 dark:text-red-400 text-xs sm:text-sm font-medium"},ae=["disabled"],ne={key:0,class:"w-4 h-4 sm:w-5 sm:h-5 border-2 border-white border-t-transparent rounded-full animate-spin"},le={key:1,class:"w-4 h-4 sm:w-5 sm:h-5 group-hover:translate-x-1 transition-transform duration-300",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},de={class:"relative"},ie={class:"mt-6 sm:mt-8 pt-4 sm:pt-6 border-t border-stroke-subtle dark:border-stroke/10"},ue={class:"flex items-center justify-center gap-3"},pe={href:"https://github.com/rightup",target:"_blank",class:"inline-flex items-center justify-center w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-content-primary dark:bg-white/10 border border-stroke-subtle dark:border-stroke/20 hover:bg-primary/20 dark:hover:bg-primary/30 hover:border-primary/50 transition-all duration-300 hover:scale-110 group backdrop-blur-sm",title:"GitHub"},ce={href:"https://buymeacoffee.com/rightup",target:"_blank",class:"inline-flex items-center justify-center w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-content-primary dark:bg-white/10 border border-stroke-subtle dark:border-stroke/20 hover:bg-yellow-50 dark:hover:bg-yellow-500/20 hover:border-yellow-500/50 transition-all duration-300 hover:scale-110 group backdrop-blur-sm",title:"Buy Me a Coffee"},me=P({name:"LoginView",__name:"Login",setup(V){const x=I(),p=a("admin"),l=a(""),s=a(!1),d=a(""),o=a(!1),n=a(!1),m=async()=>{d.value="",s.value=!0;try{const c=L(),r=(await S.post("/auth/login",{username:p.value,password:l.value,client_id:c})).data;r.success&&r.token?l.value==="admin123"?(C(r.token),n.value=!0,o.value=!0):(C(r.token),x.push("/")):d.value=r.error||"Login failed"}catch(c){console.error("Login error:",c);const t=c;d.value=t.response?.data?.error||"Connection error. Please try again."}finally{s.value=!1}},f=()=>{o.value=!1,x.push("/")},k=()=>{o.value=!1,n.value&&x.push("/")};return(c,t)=>(u(),i("div",Y,[e("div",J,[w($)]),t[9]||(t[9]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slow -top-[79px] left-[575px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[10]||(t[10]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-75 animate-pulse-slower -top-[94px] -left-[92px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[11]||(t[11]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slowest top-[373px] left-[246px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),e("div",K,[t[8]||(t[8]=e("div",{class:"absolute inset-0 rounded-[24px] bg-gradient-to-br from-primary/3 dark:from-primary/5 to-transparent pointer-events-none"},null,-1)),e("div",Q,[t[7]||(t[7]=N('
MeshCore

pyMC Repeater

Sign in to access your dashboard

',1)),e("form",{onSubmit:_(m,["prevent"]),class:"space-y-4 sm:space-y-5"},[e("div",W,[t[3]||(t[3]=e("label",{for:"username",class:"block text-content-secondary dark:text-content-primary/90 text-xs sm:text-sm font-medium mb-2"}," Username ",-1)),e("div",X,[v(e("input",{id:"username","onUpdate:modelValue":t[0]||(t[0]=r=>p.value=r),type:"text",autocomplete:"username",required:"",class:"input-glass w-full px-3 sm:px-4 py-2.5 sm:py-3.5 rounded-[12px] text-content-primary dark:text-content-primary text-sm placeholder-gray-400 dark:placeholder-white/30 focus:outline-none focus:border-primary/50 transition-all duration-300",placeholder:"Enter username",disabled:s.value},null,8,Z),[[h,p.value]]),t[2]||(t[2]=e("div",{class:"absolute inset-0 rounded-[12px] pointer-events-none input-glow"},null,-1))])]),e("div",ee,[t[5]||(t[5]=e("label",{for:"password",class:"block text-content-secondary dark:text-content-primary/90 text-xs sm:text-sm font-medium mb-2"}," Password ",-1)),e("div",te,[v(e("input",{id:"password","onUpdate:modelValue":t[1]||(t[1]=r=>l.value=r),type:"password",autocomplete:"current-password",required:"",class:"input-glass w-full px-3 sm:px-4 py-2.5 sm:py-3.5 rounded-[12px] text-content-primary dark:text-content-primary text-sm placeholder-gray-400 dark:placeholder-white/30 focus:outline-none focus:border-primary/50 transition-all duration-300",placeholder:"Enter password",disabled:s.value},null,8,re),[[h,l.value]]),t[4]||(t[4]=e("div",{class:"absolute inset-0 rounded-[12px] pointer-events-none input-glow"},null,-1))])]),d.value?(u(),i("div",se,[e("p",oe,y(d.value),1)])):g("",!0),e("button",{type:"submit",disabled:s.value,class:"button-glass w-full relative overflow-hidden bg-primary/20 hover:bg-primary/30 active:scale-[0.98] text-primary dark:text-white font-semibold py-3 sm:py-4 px-4 rounded-[12px] border border-primary/50 hover:border-primary/60 transition-all duration-300 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 sm:gap-2.5 group mt-6 sm:mt-8 text-sm sm:text-base backdrop-blur-sm"},[s.value?(u(),i("div",ne)):(u(),i("svg",le,t[6]||(t[6]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"},null,-1)]))),e("span",de,y(s.value?"Signing in...":"Sign In"),1)],8,ae)],32),e("div",ie,[e("div",ue,[e("a",pe,[w(j,{class:"w-5 h-5 sm:w-6 sm:h-6 text-white group-hover:text-primary transition-colors"})]),e("a",ce,[w(B,{class:"w-5 h-5 sm:w-6 sm:h-6 text-white group-hover:text-yellow-500 transition-colors"})])])])])]),w(A,{"is-open":o.value,"can-skip":!0,onClose:k,onSuccess:f},null,8,["is-open"])]))}}),ge=U(me,[["__scopeId","data-v-7d3a3377"]]);export{ge as default}; +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/Login-bVry26ft.js diff --git a/repeater/web/html/assets/Logs-DXaq6_-G.js b/repeater/web/html/assets/Logs-DXaq6_-G.js index b682be2..66f1aad 100644 --- a/repeater/web/html/assets/Logs-DXaq6_-G.js +++ b/repeater/web/html/assets/Logs-DXaq6_-G.js @@ -1 +1,5 @@ +<<<<<<<< HEAD:repeater/web/html/assets/Logs-DXaq6_-G.js import{a as j,r as i,c as w,o as H,H as T,b as s,e as o,k as $,j as h,t as c,g as q,F as L,h as N,i as J,L as K,p as n}from"./index-DyUIpN7m.js";const P={class:"space-y-6"},Q={class:"glass-card backdrop-blur border border-stroke-subtle dark:border-white/10 rounded-[15px] p-6"},X={class:"flex items-center justify-between mb-4"},Y=["disabled"],Z={class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4"},ee={class:"flex flex-wrap gap-2"},te=["onClick"],re={key:0,class:"w-px h-6 bg-stroke-subtle dark:bg-stroke/20 mx-2 self-center"},oe=["onClick"],se={class:"glass-card backdrop-blur border border-stroke-subtle dark:border-white/10 rounded-[15px] overflow-hidden"},ne={key:0,class:"p-8 text-center"},ae={key:1,class:"p-8 text-center"},le={class:"text-content-secondary dark:text-content-muted mb-4"},de={key:2,class:"max-h-[600px] overflow-y-auto"},ce={key:0,class:"p-8 text-center"},ie={key:1,class:"divide-y divide-gray-200 dark:divide-white/5"},ue={class:"flex-shrink-0 text-content-secondary dark:text-content-muted"},ge={class:"flex-shrink-0 px-2 py-1 text-xs font-medium rounded bg-blue-500/20 text-blue-600 dark:text-blue-400"},be={class:"text-content-primary dark:text-content-primary flex-1 break-all"},ve=j({name:"LogsView",__name:"Logs",setup(xe){const x=i([]),a=i(new Set),d=i(new Set(["DEBUG","INFO","WARNING","ERROR"])),v=i(new Set),p=i(new Set),m=i(!0),k=i(null);let u=null;const f=t=>{const e=t.match(/- ([^-]+) - (?:DEBUG|INFO|WARNING|ERROR) -/);return e?e[1].trim():"Unknown"},S=t=>{const e=t.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} - [^-]+ - (?:DEBUG|INFO|WARNING|ERROR) - (.+)$/);return e?e[1]:t},R=(t,e)=>{if(t.size!==e.size)return!1;for(const r of t)if(!e.has(r))return!1;return!0},y=async()=>{try{const t=await K.getLogs();if(t.logs&&t.logs.length>0){x.value=t.logs;const e=new Set;x.value.forEach(b=>{const z=f(b.message);e.add(z)});const r=new Set;x.value.forEach(b=>{r.add(b.level)}),a.value.size===0&&(a.value=new Set(e));const l=!R(v.value,e),g=!R(p.value,r);l&&(v.value=e),g&&(p.value=r),k.value=null}}catch(t){console.error("Error loading logs:",t),k.value=t instanceof Error?t.message:"Failed to load logs"}finally{m.value=!1}},_=w(()=>x.value.filter(e=>{const r=f(e.message),l=a.value.has(r),g=d.value.has(e.level);return l&&g})),C=w(()=>Array.from(v.value).sort()),A=w(()=>{const t=["ERROR","WARNING","WARN","INFO","DEBUG"];return Array.from(p.value).sort((r,l)=>{const g=t.indexOf(r),b=t.indexOf(l);return g!==-1&&b!==-1?g-b:r.localeCompare(l)})}),I=t=>{d.value.has(t)?d.value.delete(t):d.value.add(t),d.value=new Set(d.value)},O=t=>new Date(t).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"}),B=t=>({ERROR:"text-red-600 dark:text-red-400 bg-red-900/20",WARNING:"text-yellow-600 dark:text-yellow-400 bg-yellow-900/20",WARN:"text-yellow-600 dark:text-yellow-400 bg-yellow-900/20",INFO:"text-blue-600 dark:text-blue-400 bg-blue-900/20",DEBUG:"text-gray-400 bg-gray-900/20"})[t]||"text-gray-400 bg-gray-900/20",E=(t,e)=>e?{ERROR:"bg-red-100 dark:bg-red-500/20 text-red-600 dark:text-red-400 border-red-500/50",WARNING:"bg-yellow-100 dark:bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/50",WARN:"bg-yellow-100 dark:bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/50",INFO:"bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/50",DEBUG:"bg-gray-500/20 text-gray-400 border-gray-500/50"}[t]||"bg-primary/20 text-primary border-primary/50":"bg-background-mute dark:bg-white/5 text-content-muted dark:text-white/60 border-stroke-subtle dark:border-white/20 hover:bg-stroke-subtle dark:hover:bg-white/10",G=t=>{a.value.has(t)?a.value.delete(t):a.value.add(t),a.value=new Set(a.value)},F=()=>{a.value=new Set(v.value)},M=()=>{a.value=new Set},D=()=>{d.value=new Set(p.value)},U=()=>{d.value=new Set},W=()=>{u&&clearInterval(u),u=setInterval(y,5e3)},V=()=>{u&&(clearInterval(u),u=null)};return H(()=>{y(),W()}),T(()=>{V()}),(t,e)=>(n(),s("div",P,[o("div",Q,[o("div",X,[e[1]||(e[1]=o("div",null,[o("h1",{class:"text-content-primary dark:text-content-primary text-2xl font-semibold mb-2"},"System Logs"),o("p",{class:"text-content-secondary dark:text-content-muted"},"Real-time system events and diagnostics")],-1)),o("button",{onClick:y,disabled:m.value,class:"flex items-center gap-2 px-4 py-2 bg-primary/20 hover:bg-primary/30 text-primary border border-primary/50 rounded-lg transition-colors disabled:opacity-50"},[(n(),s("svg",{class:h(["w-4 h-4",{"animate-spin":m.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},e[0]||(e[0]=[o("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]),2)),$(" "+c(m.value?"Loading...":"Refresh"),1)],8,Y)]),o("div",Z,[o("div",{class:"flex flex-wrap items-center gap-3 mb-4"},[e[2]||(e[2]=o("span",{class:"text-content-primary dark:text-content-primary font-medium"},"Filters:",-1)),o("button",{onClick:F,class:"px-3 py-1 text-xs bg-accent-green/20 hover:bg-accent-green/30 text-accent-green border border-accent-green/50 rounded transition-colors"}," All Loggers "),o("button",{onClick:M,class:"px-3 py-1 text-xs bg-accent-red/20 hover:bg-accent-red/30 text-accent-red border border-accent-red/50 rounded transition-colors"}," Clear Loggers "),e[3]||(e[3]=o("div",{class:"w-px h-4 bg-white/20 mx-1"},null,-1)),o("button",{onClick:D,class:"px-3 py-1 text-xs bg-accent-green/20 hover:bg-accent-green/30 text-accent-green border border-accent-green/50 rounded transition-colors"}," All Levels "),o("button",{onClick:U,class:"px-3 py-1 text-xs bg-accent-red/20 hover:bg-accent-red/30 text-accent-red border border-accent-red/50 rounded transition-colors"}," Clear Levels ")]),o("div",ee,[(n(!0),s(L,null,N(C.value,r=>(n(),s("button",{key:"logger-"+r,onClick:l=>G(r),class:h(["px-3 py-1 text-xs border rounded-full transition-colors",a.value.has(r)?"bg-primary/20 text-primary border-primary/50":"bg-background-mute dark:bg-white/5 text-content-secondary dark:text-content-muted border-stroke-subtle dark:border-stroke/20 hover:bg-stroke-subtle dark:hover:bg-white/10"])},c(r),11,te))),128)),C.value.length>0&&A.value.length>0?(n(),s("div",re)):q("",!0),(n(!0),s(L,null,N(A.value,r=>(n(),s("button",{key:"level-"+r,onClick:l=>I(r),class:h(["px-3 py-1 text-xs border rounded-full transition-colors font-medium",d.value.has(r)?E(r,!0):E(r,!1)])},c(r),11,oe))),128))])])]),o("div",se,[m.value&&x.value.length===0?(n(),s("div",ne,e[4]||(e[4]=[o("div",{class:"animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"},null,-1),o("p",{class:"text-content-secondary dark:text-content-muted"},"Loading system logs...",-1)]))):k.value?(n(),s("div",ae,[e[5]||(e[5]=o("div",{class:"text-red-600 dark:text-red-400 mb-4"},[o("svg",{class:"w-12 h-12 mx-auto mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[o("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})])],-1)),e[6]||(e[6]=o("h3",{class:"text-content-primary dark:text-content-primary text-lg font-medium mb-2"},"Error Loading Logs",-1)),o("p",le,c(k.value),1),o("button",{onClick:y,class:"px-4 py-2 bg-red-100 dark:bg-red-500/20 hover:bg-red-500/30 text-red-600 dark:text-red-400 border border-red-500/50 rounded-lg transition-colors"}," Try Again ")])):(n(),s("div",de,[_.value.length===0?(n(),s("div",ce,e[7]||(e[7]=[J('

No Logs to Display

No logs match the current filter criteria.

',3)]))):(n(),s("div",ie,[(n(!0),s(L,null,N(_.value,(r,l)=>(n(),s("div",{key:l,class:"flex items-start gap-4 p-4 hover:bg-background-mute dark:hover:bg-stroke/5 transition-colors font-mono text-sm"},[o("span",ue," ["+c(O(r.timestamp))+"] ",1),o("span",ge,c(f(r.message)),1),o("span",{class:h(["flex-shrink-0 px-2 py-1 text-xs font-medium rounded",B(r.level)])},c(r.level),3),o("span",be,c(S(r.message)),1)]))),128))]))]))])]))}});export{ve as default}; +======== +import{a as j,r as i,c as w,o as H,H as T,b as s,e as o,k as $,j as h,t as c,g as q,F as L,h as N,i as J,L as K,p as n}from"./index-BfUIlcDy.js";const P={class:"space-y-6"},Q={class:"glass-card backdrop-blur border border-stroke-subtle dark:border-white/10 rounded-[15px] p-6"},X={class:"flex items-center justify-between mb-4"},Y=["disabled"],Z={class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4"},ee={class:"flex flex-wrap gap-2"},te=["onClick"],re={key:0,class:"w-px h-6 bg-stroke-subtle dark:bg-stroke/20 mx-2 self-center"},oe=["onClick"],se={class:"glass-card backdrop-blur border border-stroke-subtle dark:border-white/10 rounded-[15px] overflow-hidden"},ne={key:0,class:"p-8 text-center"},ae={key:1,class:"p-8 text-center"},le={class:"text-content-secondary dark:text-content-muted mb-4"},de={key:2,class:"max-h-[600px] overflow-y-auto"},ce={key:0,class:"p-8 text-center"},ie={key:1,class:"divide-y divide-gray-200 dark:divide-white/5"},ue={class:"flex-shrink-0 text-content-secondary dark:text-content-muted"},ge={class:"flex-shrink-0 px-2 py-1 text-xs font-medium rounded bg-blue-500/20 text-blue-600 dark:text-blue-400"},be={class:"text-content-primary dark:text-content-primary flex-1 break-all"},ve=j({name:"LogsView",__name:"Logs",setup(xe){const x=i([]),a=i(new Set),d=i(new Set(["DEBUG","INFO","WARNING","ERROR"])),v=i(new Set),p=i(new Set),m=i(!0),k=i(null);let u=null;const f=t=>{const e=t.match(/- ([^-]+) - (?:DEBUG|INFO|WARNING|ERROR) -/);return e?e[1].trim():"Unknown"},S=t=>{const e=t.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} - [^-]+ - (?:DEBUG|INFO|WARNING|ERROR) - (.+)$/);return e?e[1]:t},R=(t,e)=>{if(t.size!==e.size)return!1;for(const r of t)if(!e.has(r))return!1;return!0},y=async()=>{try{const t=await K.getLogs();if(t.logs&&t.logs.length>0){x.value=t.logs;const e=new Set;x.value.forEach(b=>{const z=f(b.message);e.add(z)});const r=new Set;x.value.forEach(b=>{r.add(b.level)}),a.value.size===0&&(a.value=new Set(e));const l=!R(v.value,e),g=!R(p.value,r);l&&(v.value=e),g&&(p.value=r),k.value=null}}catch(t){console.error("Error loading logs:",t),k.value=t instanceof Error?t.message:"Failed to load logs"}finally{m.value=!1}},_=w(()=>x.value.filter(e=>{const r=f(e.message),l=a.value.has(r),g=d.value.has(e.level);return l&&g})),C=w(()=>Array.from(v.value).sort()),A=w(()=>{const t=["ERROR","WARNING","WARN","INFO","DEBUG"];return Array.from(p.value).sort((r,l)=>{const g=t.indexOf(r),b=t.indexOf(l);return g!==-1&&b!==-1?g-b:r.localeCompare(l)})}),I=t=>{d.value.has(t)?d.value.delete(t):d.value.add(t),d.value=new Set(d.value)},O=t=>new Date(t).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"}),B=t=>({ERROR:"text-red-600 dark:text-red-400 bg-red-900/20",WARNING:"text-yellow-600 dark:text-yellow-400 bg-yellow-900/20",WARN:"text-yellow-600 dark:text-yellow-400 bg-yellow-900/20",INFO:"text-blue-600 dark:text-blue-400 bg-blue-900/20",DEBUG:"text-gray-400 bg-gray-900/20"})[t]||"text-gray-400 bg-gray-900/20",E=(t,e)=>e?{ERROR:"bg-red-100 dark:bg-red-500/20 text-red-600 dark:text-red-400 border-red-500/50",WARNING:"bg-yellow-100 dark:bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/50",WARN:"bg-yellow-100 dark:bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/50",INFO:"bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/50",DEBUG:"bg-gray-500/20 text-gray-400 border-gray-500/50"}[t]||"bg-primary/20 text-primary border-primary/50":"bg-background-mute dark:bg-white/5 text-content-muted dark:text-white/60 border-stroke-subtle dark:border-white/20 hover:bg-stroke-subtle dark:hover:bg-white/10",G=t=>{a.value.has(t)?a.value.delete(t):a.value.add(t),a.value=new Set(a.value)},F=()=>{a.value=new Set(v.value)},M=()=>{a.value=new Set},D=()=>{d.value=new Set(p.value)},U=()=>{d.value=new Set},W=()=>{u&&clearInterval(u),u=setInterval(y,5e3)},V=()=>{u&&(clearInterval(u),u=null)};return H(()=>{y(),W()}),T(()=>{V()}),(t,e)=>(n(),s("div",P,[o("div",Q,[o("div",X,[e[1]||(e[1]=o("div",null,[o("h1",{class:"text-content-primary dark:text-content-primary text-2xl font-semibold mb-2"},"System Logs"),o("p",{class:"text-content-secondary dark:text-content-muted"},"Real-time system events and diagnostics")],-1)),o("button",{onClick:y,disabled:m.value,class:"flex items-center gap-2 px-4 py-2 bg-primary/20 hover:bg-primary/30 text-primary border border-primary/50 rounded-lg transition-colors disabled:opacity-50"},[(n(),s("svg",{class:h(["w-4 h-4",{"animate-spin":m.value}]),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},e[0]||(e[0]=[o("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]),2)),$(" "+c(m.value?"Loading...":"Refresh"),1)],8,Y)]),o("div",Z,[o("div",{class:"flex flex-wrap items-center gap-3 mb-4"},[e[2]||(e[2]=o("span",{class:"text-content-primary dark:text-content-primary font-medium"},"Filters:",-1)),o("button",{onClick:F,class:"px-3 py-1 text-xs bg-accent-green/20 hover:bg-accent-green/30 text-accent-green border border-accent-green/50 rounded transition-colors"}," All Loggers "),o("button",{onClick:M,class:"px-3 py-1 text-xs bg-accent-red/20 hover:bg-accent-red/30 text-accent-red border border-accent-red/50 rounded transition-colors"}," Clear Loggers "),e[3]||(e[3]=o("div",{class:"w-px h-4 bg-white/20 mx-1"},null,-1)),o("button",{onClick:D,class:"px-3 py-1 text-xs bg-accent-green/20 hover:bg-accent-green/30 text-accent-green border border-accent-green/50 rounded transition-colors"}," All Levels "),o("button",{onClick:U,class:"px-3 py-1 text-xs bg-accent-red/20 hover:bg-accent-red/30 text-accent-red border border-accent-red/50 rounded transition-colors"}," Clear Levels ")]),o("div",ee,[(n(!0),s(L,null,N(C.value,r=>(n(),s("button",{key:"logger-"+r,onClick:l=>G(r),class:h(["px-3 py-1 text-xs border rounded-full transition-colors",a.value.has(r)?"bg-primary/20 text-primary border-primary/50":"bg-background-mute dark:bg-white/5 text-content-secondary dark:text-content-muted border-stroke-subtle dark:border-stroke/20 hover:bg-stroke-subtle dark:hover:bg-white/10"])},c(r),11,te))),128)),C.value.length>0&&A.value.length>0?(n(),s("div",re)):q("",!0),(n(!0),s(L,null,N(A.value,r=>(n(),s("button",{key:"level-"+r,onClick:l=>I(r),class:h(["px-3 py-1 text-xs border rounded-full transition-colors font-medium",d.value.has(r)?E(r,!0):E(r,!1)])},c(r),11,oe))),128))])])]),o("div",se,[m.value&&x.value.length===0?(n(),s("div",ne,e[4]||(e[4]=[o("div",{class:"animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"},null,-1),o("p",{class:"text-content-secondary dark:text-content-muted"},"Loading system logs...",-1)]))):k.value?(n(),s("div",ae,[e[5]||(e[5]=o("div",{class:"text-red-600 dark:text-red-400 mb-4"},[o("svg",{class:"w-12 h-12 mx-auto mb-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[o("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})])],-1)),e[6]||(e[6]=o("h3",{class:"text-content-primary dark:text-content-primary text-lg font-medium mb-2"},"Error Loading Logs",-1)),o("p",le,c(k.value),1),o("button",{onClick:y,class:"px-4 py-2 bg-red-100 dark:bg-red-500/20 hover:bg-red-500/30 text-red-600 dark:text-red-400 border border-red-500/50 rounded-lg transition-colors"}," Try Again ")])):(n(),s("div",de,[_.value.length===0?(n(),s("div",ce,e[7]||(e[7]=[J('

No Logs to Display

No logs match the current filter criteria.

',3)]))):(n(),s("div",ie,[(n(!0),s(L,null,N(_.value,(r,l)=>(n(),s("div",{key:l,class:"flex items-start gap-4 p-4 hover:bg-background-mute dark:hover:bg-stroke/5 transition-colors font-mono text-sm"},[o("span",ue," ["+c(O(r.timestamp))+"] ",1),o("span",ge,c(f(r.message)),1),o("span",{class:h(["flex-shrink-0 px-2 py-1 text-xs font-medium rounded",B(r.level)])},c(r.level),3),o("span",be,c(S(r.message)),1)]))),128))]))]))])]))}});export{ve as default}; +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/Logs-CwF9l8Kj.js diff --git a/repeater/web/html/assets/Neighbors-CXfm_tfh.js b/repeater/web/html/assets/Neighbors-CXfm_tfh.js index fccb664..cb86bf8 100644 --- a/repeater/web/html/assets/Neighbors-CXfm_tfh.js +++ b/repeater/web/html/assets/Neighbors-CXfm_tfh.js @@ -1,4 +1,8 @@ +<<<<<<<< HEAD:repeater/web/html/assets/Neighbors-CXfm_tfh.js import{a as bt,b as $,g as D,e as t,t as C,s as Lt,p as f,M as Yt,r as F,c as J,D as ht,N as Rt,f as it,T as Ft,l as Dt,O as jt,j as M,F as ct,h as gt,x as It,k as tt,o as Xt,P as te,i as ft,E as Pt,n as At,w as wt,Q as ie,q as Wt,v as le,L as Et}from"./index-DyUIpN7m.js";import{u as Ut}from"./useSignalQuality-DR_wpBbb.js";import{L as W}from"./leaflet-src-BtisrQHC.js";/* empty css */import{g as _t,s as Ct}from"./preferences-DtwbSSgO.js";import"./_commonjsHelpers-CqkleIqs.js";const de={class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4 mb-6"},ce={class:"flex items-center gap-3"},ue={class:"flex-1 min-w-0"},pe={class:"text-content-primary dark:text-content-primary font-medium truncate"},ge={class:"text-content-secondary dark:text-content-muted text-sm font-mono"},me={key:0,class:"text-white/50 text-xs"},he={key:1,class:"text-white/50 text-xs"},be=bt({__name:"DeleteNeighborModal",props:{show:{type:Boolean},neighbor:{}},emits:["close","delete"],setup(A,{emit:o}){const r=A,i=o,e=()=>{r.neighbor&&(i("delete",r.neighbor.id),d())},d=()=>{i("close")},g=s=>{s.target===s.currentTarget&&d()};return(s,a)=>s.show&&s.neighbor?(f(),$("div",{key:0,onClick:g,class:"fixed inset-0 bg-black/80 backdrop-blur-lg z-[99999] flex items-center justify-center p-4",style:{"backdrop-filter":"blur(8px) saturate(180%)",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"}},[t("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10",onClick:a[0]||(a[0]=Lt(()=>{},["stop"]))},[t("div",{class:"flex items-center gap-3 mb-6"},[a[2]||(a[2]=t("svg",{class:"w-6 h-6 text-accent-red",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"})],-1)),a[3]||(a[3]=t("div",null,[t("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary"},"Delete Neighbor"),t("p",{class:"text-content-secondary dark:text-content-muted text-sm mt-1"}," Are you sure you want to delete this neighbor? ")],-1)),t("button",{onClick:d,class:"ml-auto text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors"},a[1]||(a[1]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),t("div",de,[t("div",ce,[t("div",ue,[t("div",pe,C(s.neighbor?.node_name||s.neighbor?.long_name||s.neighbor?.short_name||"Unknown"),1),t("div",ge," ID: "+C(s.neighbor?.node_num_hex||s.neighbor?.node_num||s.neighbor?.id||"N/A"),1),s.neighbor?.contact_type?(f(),$("div",me,C(s.neighbor.contact_type),1)):D("",!0),s.neighbor?.hw_model?(f(),$("div",he,C(s.neighbor.hw_model),1)):D("",!0)])])]),a[4]||(a[4]=t("div",{class:"bg-accent-red/10 border border-accent-red/30 rounded-lg p-4 mb-6"},[t("div",{class:"flex items-center gap-2 text-accent-red text-sm"},[t("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})]),t("span",null,"This action cannot be undone")])],-1)),t("div",{class:"flex gap-3"},[t("button",{onClick:d,class:"flex-1 px-4 py-3 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 border border-stroke-subtle dark:border-stroke/20 text-content-primary dark:text-content-primary rounded-lg transition-colors"}," Cancel "),t("button",{onClick:e,class:"flex-1 px-4 py-3 bg-accent-red/20 hover:bg-accent-red/30 border border-accent-red/50 text-accent-red rounded-lg transition-colors font-medium"}," Delete ")])])])):D("",!0)}}),xe={class:"bg-gradient-to-r from-primary/20 to-accent-cyan/20 border-b border-stroke-subtle dark:border-stroke/10 px-6 py-4"},ye={class:"flex items-center justify-between"},ve={class:"flex items-center gap-3"},ke={key:0,class:"text-sm text-content-secondary dark:text-content-muted"},fe={class:"p-6"},we={key:0,class:"text-center py-8"},_e={key:1,class:"text-center py-8"},Ce={class:"text-content-secondary dark:text-content-muted text-sm"},$e={key:2,class:"space-y-4"},Me={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},Ae={class:"flex items-center justify-between mb-2"},Le={class:"flex items-baseline gap-2"},Te={class:"text-3xl font-bold text-content-primary dark:text-content-primary"},Ee={class:"grid grid-cols-2 gap-3"},Se={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},Be={class:"flex items-center gap-2 mb-2"},Ne={class:"flex gap-0.5"},Fe={class:"flex items-baseline gap-1"},De={class:"text-xl font-bold text-content-primary dark:text-content-primary"},Pe={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},ze={class:"flex items-baseline gap-1"},Re={class:"text-xl font-bold text-content-primary dark:text-content-primary"},je={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},Ie={class:"relative"},Ue={class:"flex items-center gap-2 overflow-x-auto pb-2"},Oe={key:0,class:"relative flex items-center"},Ve={key:0,class:"absolute left-1/2 -translate-x-1/2 animate-pulse"},He={class:"text-content-muted dark:text-content-muted text-xs mt-2 flex items-center justify-between"},Ze={key:0,class:"text-cyan-500 dark:text-primary animate-pulse"},We={class:"flex items-center justify-between text-xs text-content-muted dark:text-content-muted pt-2"},Qe=bt({__name:"PingResultModal",props:{show:{type:Boolean},nodeName:{default:null},result:{default:null},error:{default:null},loading:{type:Boolean,default:!1}},emits:["close"],setup(A,{emit:o}){const r=A,i=o,e=Yt(),{getSignalQuality:d}=Ut(),g=F(0),s=F(!1),a=J(()=>{const x=e.stats?.config?.radio?.spreading_factor??7,b=e.stats?.config?.radio?.bandwidth??125,L=e.stats?.config?.radio?.coding_rate??5,_=Math.pow(2,x)/b,k=8+4.25*(L-4)+20;return _*k}),w=J(()=>{if(!r.result)return{color:"text-gray-400",label:"Unknown"};const x=r.result.rtt_ms,b=a.value,L=r.result.path.length,k=2*b*L+500*L;return x{if(!r.result)return{bars:0,color:"text-gray-400"};const x=d(r.result.rssi);return{bars:x.bars,color:x.color}});ht(()=>r.result,x=>{if(x&&!s.value){s.value=!0,g.value=0;const b=x.path.length,_=1500/(b*2);let k=0;const P=b*2-2,I=()=>{k<=P?(g.value=k/P,k++,setTimeout(I,_)):(s.value=!1,g.value=1)};setTimeout(I,100)}},{immediate:!0});const v=J(()=>{if(!r.result||!s.value)return-1;const x=r.result.path.length;if(x<=1)return-1;const b=g.value,L=.5;if(b<=L)return b/L*(x-1);{const _=(b-L)/L;return(x-1)*(1-_)}}),S=()=>{i("close")};return(x,b)=>(f(),Rt(jt,{to:"body"},[it(Ft,{name:"modal"},{default:Dt(()=>[x.show?(f(),$("div",{key:0,class:"fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-[99999] p-4",onClick:Lt(S,["self"])},[t("div",{class:"glass-card backdrop-blur-xl border border-stroke-subtle dark:border-white/20 rounded-[20px] shadow-2xl w-full max-w-md overflow-hidden",onClick:b[0]||(b[0]=Lt(()=>{},["stop"]))},[t("div",xe,[t("div",ye,[t("div",ve,[b[2]||(b[2]=t("div",{class:"p-2 bg-cyan-400/20 dark:bg-primary/20 rounded-lg"},[t("svg",{class:"w-5 h-5 text-cyan-500 dark:text-primary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"})])],-1)),t("div",null,[b[1]||(b[1]=t("h2",{class:"text-xl font-bold text-content-primary dark:text-content-primary"},"Ping Result",-1)),x.nodeName?(f(),$("p",ke,C(x.nodeName),1)):D("",!0)])]),t("button",{onClick:S,class:"p-2 hover:bg-stroke-subtle dark:hover:bg-white/10 rounded-lg transition-colors text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary"},b[3]||(b[3]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))])]),t("div",fe,[x.loading?(f(),$("div",we,b[4]||(b[4]=[t("div",{class:"animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"},null,-1),t("p",{class:"text-content-secondary dark:text-content-muted"},"Sending ping...",-1),t("p",{class:"text-content-muted dark:text-content-muted text-sm mt-1"},"Waiting for response...",-1)]))):x.error?(f(),$("div",_e,[b[5]||(b[5]=t("div",{class:"p-3 bg-accent-red/10 rounded-full w-16 h-16 mx-auto mb-4 flex items-center justify-center"},[t("svg",{class:"w-8 h-8 text-accent-red",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-1.964-1.333-2.732 0L3.268 16c-.77 1.333.192 3 1.732 3z"})])],-1)),b[6]||(b[6]=t("h3",{class:"text-accent-red font-semibold mb-2"},"Ping Failed",-1)),t("p",Ce,C(x.error),1)])):x.result?(f(),$("div",$e,[t("div",Me,[t("div",Ae,[b[7]||(b[7]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Round-Trip Time",-1)),t("span",{class:M(["text-xs font-medium px-2 py-1 rounded-full",w.value.color,"bg-current/10"])},C(w.value.label),3)]),t("div",Le,[t("span",Te,C(x.result.rtt_ms.toFixed(2)),1),b[8]||(b[8]=t("span",{class:"text-content-secondary dark:text-content-muted"},"ms",-1))])]),t("div",Ee,[t("div",Se,[t("div",Be,[b[9]||(b[9]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"RSSI",-1)),t("div",Ne,[(f(),$(ct,null,gt(5,L=>t("div",{key:L,class:M(["w-1 h-3 rounded-sm",L<=m.value.bars?m.value.color:"bg-stroke-subtle dark:bg-stroke/10"])},null,2)),64))])]),t("div",Fe,[t("span",De,C(x.result.rssi),1),b[10]||(b[10]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs"},"dBm",-1))])]),t("div",Pe,[b[12]||(b[12]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-2"},"SNR",-1)),t("div",ze,[t("span",Re,C(x.result.snr_db),1),b[11]||(b[11]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs"},"dB",-1))])])]),t("div",je,[b[15]||(b[15]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-3"},"Network Path",-1)),t("div",Ie,[t("div",Ue,[(f(!0),$(ct,null,gt(x.result.path,(L,_)=>(f(),$("div",{key:_,class:"flex items-center gap-2 flex-shrink-0 relative"},[t("div",{class:M(["bg-cyan-400/20 dark:bg-primary/20 text-cyan-600 dark:text-primary border border-cyan-400/40 dark:border-primary/30 px-3 py-1.5 rounded-lg text-sm font-mono transition-all duration-300",s.value&&Math.floor(v.value)===_?"ring-2 ring-cyan-400/50 dark:ring-primary/50 scale-105":""])},C(L),3),_[s.value&&v.value>=_&&v.value<_+1?(f(),$("div",Ve,b[13]||(b[13]=[t("svg",{class:"w-3 h-3 text-cyan-500 dark:text-primary drop-shadow-[0_0_6px_rgba(6,182,212,0.8)] dark:drop-shadow-[0_0_6px_rgba(59,130,246,0.8)]",fill:"currentColor",viewBox:"0 0 24 24"},[t("circle",{cx:"12",cy:"12",r:"8"})],-1)]))):D("",!0)]),_:2},1024)])):D("",!0)]))),128))])]),t("div",He,[t("span",null,C(x.result.path.length)+" hop"+C(x.result.path.length!==1?"s":""),1),s.value?(f(),$("span",Ze,"● Tracing route...")):D("",!0)])]),t("div",We,[t("span",null,"Target: "+C(x.result.target_id),1),t("span",null,"Tag: #"+C(x.result.tag),1)])])):D("",!0)]),t("div",{class:"border-t border-stroke-subtle dark:border-stroke/10 px-6 py-4"},[t("button",{onClick:S,class:"w-full py-2.5 bg-gradient-to-r from-cyan-400 to-cyan-500 text-white hover:from-cyan-500 hover:to-cyan-600 dark:bg-primary/20 dark:text-primary dark:border dark:border-primary/30 dark:hover:bg-primary/30 dark:from-transparent dark:to-transparent rounded-lg font-medium transition-all shadow-[0_2px_12px_rgba(6,182,212,0.3)] dark:shadow-none"}," Close ")])])])):D("",!0)]),_:1})]))}}),qe=It(Qe,[["__scopeId","data-v-b206e10a"]]),Ke={class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] shadow-2xl border border-stroke-subtle dark:border-white/20 flex flex-col h-full overflow-hidden"},Ge={class:"flex items-center justify-between p-8 pb-4 flex-shrink-0"},Je={class:"flex-1 min-w-0"},Ye={class:"text-2xl font-bold text-content-primary dark:text-content-primary mb-1"},Xe={class:"text-content-secondary dark:text-content-muted text-sm font-mono break-all"},to={class:"flex items-center gap-2"},eo={class:"flex-1 overflow-y-auto custom-scrollbar px-8"},oo={class:"mb-6"},ro={class:"grid grid-cols-1 md:grid-cols-2 gap-4"},no={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},so={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},ao={class:"text-content-primary dark:text-content-primary font-medium"},io={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},lo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},co={class:"text-content-primary dark:text-content-primary font-medium"},uo={class:"mb-6"},po={class:"grid grid-cols-1 md:grid-cols-3 gap-4"},go={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},mo={class:"text-content-primary dark:text-content-primary font-medium"},ho={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},bo={class:"text-content-primary dark:text-content-primary font-medium"},xo={key:0,class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},yo={class:"flex items-center gap-2"},vo={class:"flex gap-0.5"},ko={class:"mb-6"},fo={class:"grid grid-cols-1 md:grid-cols-2 gap-4"},wo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},_o={class:"text-content-primary dark:text-content-primary text-sm"},Co={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},$o={class:"text-content-primary dark:text-content-primary text-sm"},Mo={key:0,class:"mb-6"},Ao={class:"grid grid-cols-1 md:grid-cols-3 gap-4 mb-4"},Lo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},To={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Eo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},So={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Bo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},No={class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},Fo={key:0,class:"text-content-primary dark:text-content-primary font-medium"},Do={class:"p-8 pt-4 border-t border-stroke-subtle dark:border-white/10 flex-shrink-0"},Po=bt({name:"NeighborDetailsModal",__name:"NeighborDetailsModal",props:{neighbor:{},isOpen:{type:Boolean},baseLatitude:{default:null},baseLongitude:{default:null}},emits:["close"],setup(A,{emit:o}){const{getSignalQuality:r}=Ut(),i=F("Copy"),e=A,d=o,g=F();let s=null;const a=y=>new Date(y*1e3).toLocaleString(),w=y=>y?`${y} dBm`:"N/A",m=y=>y?`${y.toFixed(1)} dB`:"N/A",v=y=>({0:"Transport Flood",1:"Flood",2:"Direct",3:"Transport Direct"})[y||0]||"Unknown",S=y=>({Unknown:"Unknown","Chat Node":"Chat Node",Repeater:"Repeater","Room Server":"Room Server","Hybrid Node":"Hybrid Node"})[y]||y,x=y=>({Unknown:"text-gray-600 dark:text-gray-400","Chat Node":"text-blue-600 dark:text-blue-400",Repeater:"text-emerald-600 dark:text-emerald-400","Room Server":"text-purple-600 dark:text-purple-400","Hybrid Node":"text-amber-600 dark:text-amber-400"})[y]||"text-gray-600 dark:text-gray-400",b=async()=>{if(!e.neighbor?.latitude||!e.neighbor?.longitude)return;const y=e.neighbor.latitude.toFixed(6),u=e.neighbor.longitude.toFixed(6),j=`${y}, ${u}`;try{await navigator.clipboard.writeText(j),i.value="Copied!",setTimeout(()=>{i.value="Copy"},2e3)}catch(K){console.error("Failed to copy coordinates:",K),i.value="Failed",setTimeout(()=>{i.value="Copy"},2e3)}},L=J(()=>{if(!e.neighbor?.latitude||!e.neighbor?.longitude||!e.baseLatitude||!e.baseLongitude)return null;const y=6371,u=(e.neighbor.latitude-e.baseLatitude)*Math.PI/180,j=(e.neighbor.longitude-e.baseLongitude)*Math.PI/180,K=Math.sin(u/2)*Math.sin(u/2)+Math.cos(e.baseLatitude*Math.PI/180)*Math.cos(e.neighbor.latitude*Math.PI/180)*Math.sin(j/2)*Math.sin(j/2),et=2*Math.atan2(Math.sqrt(K),Math.sqrt(1-K));return y*et}),_=J(()=>e.neighbor?.latitude!==null&&e.neighbor?.longitude!==null&&e.neighbor?.latitude!==0&&e.neighbor?.longitude!==0&&Math.abs(e.neighbor?.latitude??0)<=90&&Math.abs(e.neighbor?.longitude??0)<=180),k=()=>{if(!g.value||!e.neighbor||!_.value)return;s&&(s.remove(),s=null);const y=document.documentElement.classList.contains("dark");s=W.map(g.value,{center:[e.neighbor.latitude,e.neighbor.longitude],zoom:13,zoomControl:!0,attributionControl:!1});const u=y?"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png":"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";W.tileLayer(u,{maxZoom:19,attribution:"© OpenStreetMap © CARTO"}).addTo(s);const j=W.divIcon({className:"custom-marker",html:`
${e.neighbor.node_name?.charAt(0)||"?"}
`,iconSize:[32,32],iconAnchor:[16,16]});if(W.marker([e.neighbor.latitude,e.neighbor.longitude],{icon:j}).addTo(s).bindPopup(`${e.neighbor.node_name||"Unknown"}
${e.neighbor.pubkey.slice(0,8)}...`),e.baseLatitude!==null&&e.baseLongitude!==null&&e.baseLatitude!==0&&e.baseLongitude!==0&&Math.abs(e.baseLatitude)<=90&&Math.abs(e.baseLongitude)<=180){const et=W.divIcon({className:"custom-marker",html:'
B
',iconSize:[32,32],iconAnchor:[16,16]});W.marker([e.baseLatitude,e.baseLongitude],{icon:et}).addTo(s).bindPopup("Base Station"),W.polyline([[e.baseLatitude,e.baseLongitude],[e.neighbor.latitude,e.neighbor.longitude]],{color:"#3b82f6",weight:2,opacity:.6,dashArray:"5, 10"}).addTo(s);const lt=W.latLngBounds([e.baseLatitude,e.baseLongitude],[e.neighbor.latitude,e.neighbor.longitude]);s.fitBounds(lt,{padding:[50,50]})}},P=y=>{y.key==="Escape"&&d("close")},I=y=>{y.target===y.currentTarget&&d("close")};ht(()=>e.isOpen,y=>{y?(document.body.style.overflow="hidden",setTimeout(()=>{_.value&&k()},100)):(document.body.style.overflow="",s&&(s.remove(),s=null))},{immediate:!0});const Z=J(()=>e.neighbor?.rssi?r(e.neighbor.rssi):null);return(y,u)=>(f(),Rt(jt,{to:"body"},[it(Ft,{name:"modal",appear:""},{default:Dt(()=>[y.isOpen&&y.neighbor?(f(),$("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 overflow-hidden",onClick:I,onKeydown:P,tabindex:"0"},[u[20]||(u[20]=t("div",{class:"absolute inset-0 bg-black/60 backdrop-blur-md pointer-events-none"},null,-1)),t("div",{class:"relative w-full max-w-4xl max-h-[90vh] flex flex-col",onClick:u[2]||(u[2]=Lt(()=>{},["stop"]))},[t("div",Ke,[t("div",Ge,[t("div",Je,[t("h2",Ye,C(y.neighbor.node_name||"Unknown Node"),1),t("p",Xe,C(y.neighbor.pubkey),1)]),t("div",to,[t("button",{onClick:u[0]||(u[0]=j=>d("close")),class:"w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors duration-200 text-gray-700 dark:text-white hover:text-gray-900 dark:hover:text-white"},u[3]||(u[3]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))])]),t("div",eo,[t("div",oo,[u[8]||(u[8]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Basic Information",-1)),t("div",ro,[t("div",no,[u[4]||(u[4]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Contact Type",-1)),t("div",{class:M(["font-medium",x(y.neighbor.contact_type)])},C(S(y.neighbor.contact_type)),3)]),t("div",so,[u[5]||(u[5]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Route Type",-1)),t("div",ao,C(v(y.neighbor.route_type)),1)]),t("div",io,[u[6]||(u[6]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Zero Hop",-1)),t("div",{class:M(["font-medium",y.neighbor.zero_hop?"text-green-600 dark:text-green-400":"text-gray-600 dark:text-gray-400"])},C(y.neighbor.zero_hop?"Yes":"No"),3)]),t("div",lo,[u[7]||(u[7]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Advert Count",-1)),t("div",co,C(y.neighbor.advert_count.toLocaleString()),1)])])]),t("div",uo,[u[12]||(u[12]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Signal Quality",-1)),t("div",po,[t("div",go,[u[9]||(u[9]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"RSSI",-1)),t("div",mo,C(w(y.neighbor.rssi)),1)]),t("div",ho,[u[10]||(u[10]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"SNR",-1)),t("div",bo,C(m(y.neighbor.snr)),1)]),Z.value?(f(),$("div",xo,[u[11]||(u[11]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Signal Strength",-1)),t("div",yo,[t("div",vo,[(f(),$(ct,null,gt(4,j=>t("div",{key:j,class:M(["w-1 h-3 rounded-sm",j<=Z.value.bars?Z.value.color:"bg-gray-300 dark:bg-gray-700"])},null,2)),64))]),t("span",{class:M(["text-sm font-medium",Z.value.color])},C(Z.value.quality),3)])])):D("",!0)])]),t("div",ko,[u[15]||(u[15]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Timeline",-1)),t("div",fo,[t("div",wo,[u[13]||(u[13]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"First Seen",-1)),t("div",_o,C(a(y.neighbor.first_seen)),1)]),t("div",Co,[u[14]||(u[14]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Last Seen",-1)),t("div",$o,C(a(y.neighbor.last_seen)),1)])])]),_.value?(f(),$("div",Mo,[u[19]||(u[19]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Location",-1)),t("div",Ao,[t("div",Lo,[u[16]||(u[16]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Latitude",-1)),t("div",To,C(y.neighbor.latitude?.toFixed(6)),1)]),t("div",Eo,[u[17]||(u[17]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Longitude",-1)),t("div",So,C(y.neighbor.longitude?.toFixed(6)),1)]),t("div",Bo,[t("div",No,C(L.value!==null?"Distance":"Coordinates"),1),L.value!==null?(f(),$("div",Fo,C(L.value.toFixed(2))+" km ",1)):(f(),$("button",{key:1,onClick:b,class:"w-full px-3 py-1.5 bg-primary hover:bg-primary/90 dark:bg-gray-700 dark:hover:bg-gray-600 text-white text-sm font-medium rounded-lg transition-colors flex items-center justify-center gap-1.5"},[u[18]||(u[18]=t("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})],-1)),tt(" "+C(i.value),1)]))])]),t("div",{ref_key:"mapContainer",ref:g,class:"w-full h-96 rounded-[12px] overflow-hidden border border-stroke-subtle dark:border-white/10"},null,512)])):D("",!0)]),t("div",Do,[t("button",{onClick:u[1]||(u[1]=j=>d("close")),class:"w-full px-4 py-2.5 bg-primary hover:bg-primary/90 dark:bg-gray-700 dark:hover:bg-gray-600 text-white font-medium rounded-lg transition-colors"}," Close ")])])])],32)):D("",!0)]),_:1})]))}}),zo=It(Po,[["__scopeId","data-v-5669a05a"]]),Qt=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array],St=1,vt=8;class Ot{static from(o){if(!(o instanceof ArrayBuffer))throw new Error("Data must be an instance of ArrayBuffer.");const[r,i]=new Uint8Array(o,0,2);if(r!==219)throw new Error("Data does not appear to be in a KDBush format.");const e=i>>4;if(e!==St)throw new Error(`Got v${e} data when expected v${St}.`);const d=Qt[i&15];if(!d)throw new Error("Unrecognized array type.");const[g]=new Uint16Array(o,2,1),[s]=new Uint32Array(o,4,1);return new Ot(s,g,d,o)}constructor(o,r=64,i=Float64Array,e){if(isNaN(o)||o<0)throw new Error(`Unpexpected numItems value: ${o}.`);this.numItems=+o,this.nodeSize=Math.min(Math.max(+r,2),65535),this.ArrayType=i,this.IndexArrayType=o<65536?Uint16Array:Uint32Array;const d=Qt.indexOf(this.ArrayType),g=o*2*this.ArrayType.BYTES_PER_ELEMENT,s=o*this.IndexArrayType.BYTES_PER_ELEMENT,a=(8-s%8)%8;if(d<0)throw new Error(`Unexpected typed array class: ${i}.`);e&&e instanceof ArrayBuffer?(this.data=e,this.ids=new this.IndexArrayType(this.data,vt,o),this.coords=new this.ArrayType(this.data,vt+s+a,o*2),this._pos=o*2,this._finished=!0):(this.data=new ArrayBuffer(vt+g+s+a),this.ids=new this.IndexArrayType(this.data,vt,o),this.coords=new this.ArrayType(this.data,vt+s+a,o*2),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,(St<<4)+d]),new Uint16Array(this.data,2,1)[0]=r,new Uint32Array(this.data,4,1)[0]=o)}add(o,r){const i=this._pos>>1;return this.ids[i]=i,this.coords[this._pos++]=o,this.coords[this._pos++]=r,i}finish(){const o=this._pos>>1;if(o!==this.numItems)throw new Error(`Added ${o} items when expected ${this.numItems}.`);return zt(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this}range(o,r,i,e){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:d,coords:g,nodeSize:s}=this,a=[0,d.length-1,0],w=[];for(;a.length;){const m=a.pop()||0,v=a.pop()||0,S=a.pop()||0;if(v-S<=s){for(let _=S;_<=v;_++){const k=g[2*_],P=g[2*_+1];k>=o&&k<=i&&P>=r&&P<=e&&w.push(d[_])}continue}const x=S+v>>1,b=g[2*x],L=g[2*x+1];b>=o&&b<=i&&L>=r&&L<=e&&w.push(d[x]),(m===0?o<=b:r<=L)&&(a.push(S),a.push(x-1),a.push(1-m)),(m===0?i>=b:e>=L)&&(a.push(x+1),a.push(v),a.push(1-m))}return w}within(o,r,i){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:e,coords:d,nodeSize:g}=this,s=[0,e.length-1,0],a=[],w=i*i;for(;s.length;){const m=s.pop()||0,v=s.pop()||0,S=s.pop()||0;if(v-S<=g){for(let _=S;_<=v;_++)qt(d[2*_],d[2*_+1],o,r)<=w&&a.push(e[_]);continue}const x=S+v>>1,b=d[2*x],L=d[2*x+1];qt(b,L,o,r)<=w&&a.push(e[x]),(m===0?o-i<=b:r-i<=L)&&(s.push(S),s.push(x-1),s.push(1-m)),(m===0?o+i>=b:r+i>=L)&&(s.push(x+1),s.push(v),s.push(1-m))}return a}}function zt(A,o,r,i,e,d){if(e-i<=r)return;const g=i+e>>1;ee(A,o,g,i,e,d),zt(A,o,r,i,g-1,1-d),zt(A,o,r,g+1,e,1-d)}function ee(A,o,r,i,e,d){for(;e>i;){if(e-i>600){const w=e-i+1,m=r-i+1,v=Math.log(w),S=.5*Math.exp(2*v/3),x=.5*Math.sqrt(v*S*(w-S)/w)*(m-w/2<0?-1:1),b=Math.max(i,Math.floor(r-m*S/w+x)),L=Math.min(e,Math.floor(r+(w-m)*S/w+x));ee(A,o,r,b,L,d)}const g=o[2*r+d];let s=i,a=e;for(kt(A,o,i,r),o[2*e+d]>g&&kt(A,o,i,e);sg;)a--}o[2*i+d]===g?kt(A,o,i,a):(a++,kt(A,o,a,e)),a<=r&&(i=a+1),r<=a&&(e=a-1)}}function kt(A,o,r,i){Bt(A,r,i),Bt(o,2*r,2*i),Bt(o,2*r+1,2*i+1)}function Bt(A,o,r){const i=A[o];A[o]=A[r],A[r]=i}function qt(A,o,r,i){const e=A-r,d=o-i;return e*e+d*d}const Ro={minZoom:0,maxZoom:16,minPoints:2,radius:40,extent:512,nodeSize:64,log:!1,generateId:!1,reduce:null,map:A=>A},Kt=Math.fround||(A=>o=>(A[0]=+o,A[0]))(new Float32Array(1)),mt=2,pt=3,Nt=4,ut=5,oe=6;class jo{constructor(o){this.options=Object.assign(Object.create(Ro),o),this.trees=new Array(this.options.maxZoom+1),this.stride=this.options.reduce?7:6,this.clusterProps=[]}load(o){const{log:r,minZoom:i,maxZoom:e}=this.options;r&&console.time("total time");const d=`prepare ${o.length} points`;r&&console.time(d),this.points=o;const g=[];for(let a=0;a=i;a--){const w=+Date.now();s=this.trees[a]=this._createTree(this._cluster(s,a)),r&&console.log("z%d: %d clusters in %dms",a,s.numItems,+Date.now()-w)}return r&&console.timeEnd("total time"),this}getClusters(o,r){let i=((o[0]+180)%360+360)%360-180;const e=Math.max(-90,Math.min(90,o[1]));let d=o[2]===180?180:((o[2]+180)%360+360)%360-180;const g=Math.max(-90,Math.min(90,o[3]));if(o[2]-o[0]>=360)i=-180,d=180;else if(i>d){const v=this.getClusters([i,e,180,g],r),S=this.getClusters([-180,e,d,g],r);return v.concat(S)}const s=this.trees[this._limitZoom(r)],a=s.range($t(i),Mt(g),$t(d),Mt(e)),w=s.data,m=[];for(const v of a){const S=this.stride*v;m.push(w[S+ut]>1?Gt(w,S,this.clusterProps):this.points[w[S+pt]])}return m}getChildren(o){const r=this._getOriginId(o),i=this._getOriginZoom(o),e="No cluster with the specified id.",d=this.trees[i];if(!d)throw new Error(e);const g=d.data;if(r*this.stride>=g.length)throw new Error(e);const s=this.options.radius/(this.options.extent*Math.pow(2,i-1)),a=g[r*this.stride],w=g[r*this.stride+1],m=d.within(a,w,s),v=[];for(const S of m){const x=S*this.stride;g[x+Nt]===o&&v.push(g[x+ut]>1?Gt(g,x,this.clusterProps):this.points[g[x+pt]])}if(v.length===0)throw new Error(e);return v}getLeaves(o,r,i){r=r||10,i=i||0;const e=[];return this._appendLeaves(e,o,r,i,0),e}getTile(o,r,i){const e=this.trees[this._limitZoom(o)],d=Math.pow(2,o),{extent:g,radius:s}=this.options,a=s/g,w=(i-a)/d,m=(i+1+a)/d,v={features:[]};return this._addTileFeatures(e.range((r-a)/d,w,(r+1+a)/d,m),e.data,r,i,d,v),r===0&&this._addTileFeatures(e.range(1-a/d,w,1,m),e.data,d,i,d,v),r===d-1&&this._addTileFeatures(e.range(0,w,a/d,m),e.data,-1,i,d,v),v.features.length?v:null}getClusterExpansionZoom(o){let r=this._getOriginZoom(o)-1;for(;r<=this.options.maxZoom;){const i=this.getChildren(o);if(r++,i.length!==1)break;o=i[0].properties.cluster_id}return r}_appendLeaves(o,r,i,e,d){const g=this.getChildren(r);for(const s of g){const a=s.properties;if(a&&a.cluster?d+a.point_count<=e?d+=a.point_count:d=this._appendLeaves(o,a.cluster_id,i,e,d):d1;let m,v,S;if(w)m=re(r,a,this.clusterProps),v=r[a],S=r[a+1];else{const L=this.points[r[a+pt]];m=L.properties;const[_,k]=L.geometry.coordinates;v=$t(_),S=Mt(k)}const x={type:1,geometry:[[Math.round(this.options.extent*(v*d-i)),Math.round(this.options.extent*(S*d-e))]],tags:m};let b;w||this.options.generateId?b=r[a+pt]:b=this.points[r[a+pt]].id,b!==void 0&&(x.id=b),g.features.push(x)}}_limitZoom(o){return Math.max(this.options.minZoom,Math.min(Math.floor(+o),this.options.maxZoom+1))}_cluster(o,r){const{radius:i,extent:e,reduce:d,minPoints:g}=this.options,s=i/(e*Math.pow(2,r)),a=o.data,w=[],m=this.stride;for(let v=0;vr&&(_+=a[P+ut])}if(_>L&&_>=g){let k=S*L,P=x*L,I,Z=-1;const y=((v/m|0)<<5)+(r+1)+this.points.length;for(const u of b){const j=u*m;if(a[j+mt]<=r)continue;a[j+mt]=r;const K=a[j+ut];k+=a[j]*K,P+=a[j+1]*K,a[j+Nt]=y,d&&(I||(I=this._map(a,v,!0),Z=this.clusterProps.length,this.clusterProps.push(I)),d(I,this._map(a,j)))}a[v+Nt]=y,w.push(k/_,P/_,1/0,y,-1,_),d&&w.push(Z)}else{for(let k=0;k1)for(const k of b){const P=k*m;if(!(a[P+mt]<=r)){a[P+mt]=r;for(let I=0;I>5}_getOriginZoom(o){return(o-this.points.length)%32}_map(o,r,i){if(o[r+ut]>1){const g=this.clusterProps[o[r+oe]];return i?Object.assign({},g):g}const e=this.points[o[r+pt]].properties,d=this.options.map(e);return i&&d===e?Object.assign({},d):d}}function Gt(A,o,r){return{type:"Feature",id:A[o+pt],properties:re(A,o,r),geometry:{type:"Point",coordinates:[Io(A[o]),Uo(A[o+1])]}}}function re(A,o,r){const i=A[o+ut],e=i>=1e4?`${Math.round(i/1e3)}k`:i>=1e3?`${Math.round(i/100)/10}k`:i,d=A[o+oe],g=d===-1?{}:Object.assign({},r[d]);return Object.assign(g,{cluster:!0,cluster_id:A[o+pt],point_count:i,point_count_abbreviated:e})}function $t(A){return A/360+.5}function Mt(A){const o=Math.sin(A*Math.PI/180),r=.5-.25*Math.log((1+o)/(1-o))/Math.PI;return r<0?0:r>1?1:r}function Io(A){return(A-.5)*360}function Uo(A){const o=(180-A*360)*Math.PI/180;return 360*Math.atan(Math.exp(o))/Math.PI-90}const Oo={class:"map-container"},Vo={key:0,class:"flex items-center justify-center h-96 glass-card backdrop-blur border border-black/6 dark:border-white/10 rounded-[12px] shadow-sm dark:shadow-none"},Ho={class:"hidden sm:inline"},Zo={key:3,class:"map-legend"},Wo={class:"legend-footer"},Qo={key:4,class:"map-attribution"},qo=bt({__name:"NetworkMap",props:{adverts:{},baseLatitude:{default:null},baseLongitude:{default:null},showLegend:{type:Boolean,default:!0}},emits:["update:showLegend"],setup(A,{expose:o,emit:r}){typeof window<"u"&&!window.chrome&&(window.chrome={runtime:{}});const i=A,e=r,d=()=>{e("update:showLegend",!i.showLegend)},g=F();let s=null;const a=F(new Map);let w=null;const m=F(new Map),v=F([]),S=F(!0),x=F(60),b=F(14),L=F(document.documentElement.classList.contains("dark")),_=new MutationObserver(()=>{const E=document.documentElement.classList.contains("dark");E!==L.value&&(L.value=E,s&&K())}),k=J(()=>i.baseLatitude!==null&&i.baseLongitude!==null&&typeof i.baseLatitude=="number"&&typeof i.baseLongitude=="number"&&i.baseLatitude!==0&&i.baseLongitude!==0&&Math.abs(i.baseLatitude)<=90&&Math.abs(i.baseLongitude)<=180),P=E=>new Date(E*1e3).toLocaleString(),I=E=>E?`${E} dBm`:"N/A",Z=E=>E?`${E} dB`:"N/A",y=E=>({0:"Transport Flood",1:"Flood",2:"Direct",3:"Transport Direct"})[E||0]||"Unknown",u=(E,c,n,l)=>{const R=(n-E)*Math.PI/180,V=(l-c)*Math.PI/180,G=Math.sin(R/2)*Math.sin(R/2)+Math.cos(E*Math.PI/180)*Math.cos(n*Math.PI/180)*Math.sin(V/2)*Math.sin(V/2);return 6371*(2*Math.atan2(Math.sqrt(G),Math.sqrt(1-G)))},j=()=>{s&&(v.value.forEach(E=>{s&&E.remove()}),v.value.length=0,s.remove(),s=null),a.value.clear(),m.value.clear(),w=null},K=async()=>{const E=s?.getZoom()||11,c=s?.getCenter()||(k.value?[i.baseLatitude,i.baseLongitude]:[0,0]);j(),await Pt(),await at(),s&&s.setView(c,E)},et=E=>{const c=new Map;return E.filter(n=>n.latitude!==null&&n.longitude!==null).map(n=>{let l=n.latitude,B=n.longitude;const R=`${l.toFixed(6)}_${B.toFixed(6)}`,V=c.get(R)||0;if(c.set(R,V+1),V>0){const X=V*60*(Math.PI/180);l+=Math.sin(X)*.001*(V*.5),B+=Math.cos(X)*.001*(V*.5)}return{type:"Feature",properties:{advert:{...n,jittered_latitude:l,jittered_longitude:B}},geometry:{type:"Point",coordinates:[B,l]}}})},lt=E=>{w=new jo({radius:x.value,maxZoom:b.value,minPoints:2}),w.load(E)},at=async()=>{if(!g.value||!k.value){console.warn("Cannot initialize map: missing container or coordinates");return}j(),await Pt();const E=i.baseLatitude,c=i.baseLongitude;s=W.map(g.value,{center:[E,c],zoom:11,zoomControl:!0,attributionControl:!1,preferCanvas:!1});try{const n=L.value?"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png":"https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png",l=L.value?"https://{s}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png":"https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png",B=W.tileLayer(n,{maxZoom:19,attribution:'© OpenStreetMap contributors © CARTO',errorTileUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="}),R=W.tileLayer(l,{maxZoom:19,attribution:"",errorTileUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="});B.addTo(s),R.addTo(s)}catch(n){console.warn("Error loading tiles:",n)}try{const n=(z,H=!1)=>{const h=H?16:12;return W.divIcon({className:"custom-div-icon",html:`
`,iconSize:[h+4,h+4],iconAnchor:[(h+4)/2,(h+4)/2]})},l=z=>{const H=z<10?30:z<100?40:50;return W.divIcon({className:"custom-cluster-icon",html:` +======== +import{a as bt,b as $,g as D,e as t,t as C,s as Lt,p as f,M as Yt,r as F,c as J,D as ht,N as Rt,f as it,T as Ft,l as Dt,O as jt,j as M,F as ct,h as gt,x as It,k as tt,o as Xt,P as te,i as ft,E as Pt,n as At,w as wt,Q as ie,q as Wt,v as le,L as Et}from"./index-BfUIlcDy.js";import{u as Ut}from"./useSignalQuality-BkE4M-We.js";import{L as W}from"./leaflet-src-BtisrQHC.js";/* empty css */import{g as _t,s as Ct}from"./preferences-DtwbSSgO.js";import"./_commonjsHelpers-CqkleIqs.js";const de={class:"bg-gray-50 dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg p-4 mb-6"},ce={class:"flex items-center gap-3"},ue={class:"flex-1 min-w-0"},pe={class:"text-content-primary dark:text-content-primary font-medium truncate"},ge={class:"text-content-secondary dark:text-content-muted text-sm font-mono"},me={key:0,class:"text-white/50 text-xs"},he={key:1,class:"text-white/50 text-xs"},be=bt({__name:"DeleteNeighborModal",props:{show:{type:Boolean},neighbor:{}},emits:["close","delete"],setup(A,{emit:o}){const r=A,i=o,e=()=>{r.neighbor&&(i("delete",r.neighbor.id),d())},d=()=>{i("close")},g=s=>{s.target===s.currentTarget&&d()};return(s,a)=>s.show&&s.neighbor?(f(),$("div",{key:0,onClick:g,class:"fixed inset-0 bg-black/80 backdrop-blur-lg z-[99999] flex items-center justify-center p-4",style:{"backdrop-filter":"blur(8px) saturate(180%)",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"}},[t("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10",onClick:a[0]||(a[0]=Lt(()=>{},["stop"]))},[t("div",{class:"flex items-center gap-3 mb-6"},[a[2]||(a[2]=t("svg",{class:"w-6 h-6 text-accent-red",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"})],-1)),a[3]||(a[3]=t("div",null,[t("h3",{class:"text-xl font-semibold text-content-primary dark:text-content-primary"},"Delete Neighbor"),t("p",{class:"text-content-secondary dark:text-content-muted text-sm mt-1"}," Are you sure you want to delete this neighbor? ")],-1)),t("button",{onClick:d,class:"ml-auto text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors"},a[1]||(a[1]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),t("div",de,[t("div",ce,[t("div",ue,[t("div",pe,C(s.neighbor?.node_name||s.neighbor?.long_name||s.neighbor?.short_name||"Unknown"),1),t("div",ge," ID: "+C(s.neighbor?.node_num_hex||s.neighbor?.node_num||s.neighbor?.id||"N/A"),1),s.neighbor?.contact_type?(f(),$("div",me,C(s.neighbor.contact_type),1)):D("",!0),s.neighbor?.hw_model?(f(),$("div",he,C(s.neighbor.hw_model),1)):D("",!0)])])]),a[4]||(a[4]=t("div",{class:"bg-accent-red/10 border border-accent-red/30 rounded-lg p-4 mb-6"},[t("div",{class:"flex items-center gap-2 text-accent-red text-sm"},[t("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"})]),t("span",null,"This action cannot be undone")])],-1)),t("div",{class:"flex gap-3"},[t("button",{onClick:d,class:"flex-1 px-4 py-3 bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 border border-stroke-subtle dark:border-stroke/20 text-content-primary dark:text-content-primary rounded-lg transition-colors"}," Cancel "),t("button",{onClick:e,class:"flex-1 px-4 py-3 bg-accent-red/20 hover:bg-accent-red/30 border border-accent-red/50 text-accent-red rounded-lg transition-colors font-medium"}," Delete ")])])])):D("",!0)}}),xe={class:"bg-gradient-to-r from-primary/20 to-accent-cyan/20 border-b border-stroke-subtle dark:border-stroke/10 px-6 py-4"},ye={class:"flex items-center justify-between"},ve={class:"flex items-center gap-3"},ke={key:0,class:"text-sm text-content-secondary dark:text-content-muted"},fe={class:"p-6"},we={key:0,class:"text-center py-8"},_e={key:1,class:"text-center py-8"},Ce={class:"text-content-secondary dark:text-content-muted text-sm"},$e={key:2,class:"space-y-4"},Me={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},Ae={class:"flex items-center justify-between mb-2"},Le={class:"flex items-baseline gap-2"},Te={class:"text-3xl font-bold text-content-primary dark:text-content-primary"},Ee={class:"grid grid-cols-2 gap-3"},Se={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},Be={class:"flex items-center gap-2 mb-2"},Ne={class:"flex gap-0.5"},Fe={class:"flex items-baseline gap-1"},De={class:"text-xl font-bold text-content-primary dark:text-content-primary"},Pe={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},ze={class:"flex items-baseline gap-1"},Re={class:"text-xl font-bold text-content-primary dark:text-content-primary"},je={class:"bg-background-mute dark:bg-background/50 border border-stroke-subtle dark:border-stroke/10 rounded-[15px] p-4"},Ie={class:"relative"},Ue={class:"flex items-center gap-2 overflow-x-auto pb-2"},Oe={key:0,class:"relative flex items-center"},Ve={key:0,class:"absolute left-1/2 -translate-x-1/2 animate-pulse"},He={class:"text-content-muted dark:text-content-muted text-xs mt-2 flex items-center justify-between"},Ze={key:0,class:"text-cyan-500 dark:text-primary animate-pulse"},We={class:"flex items-center justify-between text-xs text-content-muted dark:text-content-muted pt-2"},Qe=bt({__name:"PingResultModal",props:{show:{type:Boolean},nodeName:{default:null},result:{default:null},error:{default:null},loading:{type:Boolean,default:!1}},emits:["close"],setup(A,{emit:o}){const r=A,i=o,e=Yt(),{getSignalQuality:d}=Ut(),g=F(0),s=F(!1),a=J(()=>{const x=e.stats?.config?.radio?.spreading_factor??7,b=e.stats?.config?.radio?.bandwidth??125,L=e.stats?.config?.radio?.coding_rate??5,_=Math.pow(2,x)/b,k=8+4.25*(L-4)+20;return _*k}),w=J(()=>{if(!r.result)return{color:"text-gray-400",label:"Unknown"};const x=r.result.rtt_ms,b=a.value,L=r.result.path.length,k=2*b*L+500*L;return x{if(!r.result)return{bars:0,color:"text-gray-400"};const x=d(r.result.rssi);return{bars:x.bars,color:x.color}});ht(()=>r.result,x=>{if(x&&!s.value){s.value=!0,g.value=0;const b=x.path.length,_=1500/(b*2);let k=0;const P=b*2-2,I=()=>{k<=P?(g.value=k/P,k++,setTimeout(I,_)):(s.value=!1,g.value=1)};setTimeout(I,100)}},{immediate:!0});const v=J(()=>{if(!r.result||!s.value)return-1;const x=r.result.path.length;if(x<=1)return-1;const b=g.value,L=.5;if(b<=L)return b/L*(x-1);{const _=(b-L)/L;return(x-1)*(1-_)}}),S=()=>{i("close")};return(x,b)=>(f(),Rt(jt,{to:"body"},[it(Ft,{name:"modal"},{default:Dt(()=>[x.show?(f(),$("div",{key:0,class:"fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-[99999] p-4",onClick:Lt(S,["self"])},[t("div",{class:"glass-card backdrop-blur-xl border border-stroke-subtle dark:border-white/20 rounded-[20px] shadow-2xl w-full max-w-md overflow-hidden",onClick:b[0]||(b[0]=Lt(()=>{},["stop"]))},[t("div",xe,[t("div",ye,[t("div",ve,[b[2]||(b[2]=t("div",{class:"p-2 bg-cyan-400/20 dark:bg-primary/20 rounded-lg"},[t("svg",{class:"w-5 h-5 text-cyan-500 dark:text-primary",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"})])],-1)),t("div",null,[b[1]||(b[1]=t("h2",{class:"text-xl font-bold text-content-primary dark:text-content-primary"},"Ping Result",-1)),x.nodeName?(f(),$("p",ke,C(x.nodeName),1)):D("",!0)])]),t("button",{onClick:S,class:"p-2 hover:bg-stroke-subtle dark:hover:bg-white/10 rounded-lg transition-colors text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary"},b[3]||(b[3]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))])]),t("div",fe,[x.loading?(f(),$("div",we,b[4]||(b[4]=[t("div",{class:"animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"},null,-1),t("p",{class:"text-content-secondary dark:text-content-muted"},"Sending ping...",-1),t("p",{class:"text-content-muted dark:text-content-muted text-sm mt-1"},"Waiting for response...",-1)]))):x.error?(f(),$("div",_e,[b[5]||(b[5]=t("div",{class:"p-3 bg-accent-red/10 rounded-full w-16 h-16 mx-auto mb-4 flex items-center justify-center"},[t("svg",{class:"w-8 h-8 text-accent-red",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-1.964-1.333-2.732 0L3.268 16c-.77 1.333.192 3 1.732 3z"})])],-1)),b[6]||(b[6]=t("h3",{class:"text-accent-red font-semibold mb-2"},"Ping Failed",-1)),t("p",Ce,C(x.error),1)])):x.result?(f(),$("div",$e,[t("div",Me,[t("div",Ae,[b[7]||(b[7]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"Round-Trip Time",-1)),t("span",{class:M(["text-xs font-medium px-2 py-1 rounded-full",w.value.color,"bg-current/10"])},C(w.value.label),3)]),t("div",Le,[t("span",Te,C(x.result.rtt_ms.toFixed(2)),1),b[8]||(b[8]=t("span",{class:"text-content-secondary dark:text-content-muted"},"ms",-1))])]),t("div",Ee,[t("div",Se,[t("div",Be,[b[9]||(b[9]=t("span",{class:"text-content-secondary dark:text-content-muted text-sm"},"RSSI",-1)),t("div",Ne,[(f(),$(ct,null,gt(5,L=>t("div",{key:L,class:M(["w-1 h-3 rounded-sm",L<=m.value.bars?m.value.color:"bg-stroke-subtle dark:bg-stroke/10"])},null,2)),64))])]),t("div",Fe,[t("span",De,C(x.result.rssi),1),b[10]||(b[10]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs"},"dBm",-1))])]),t("div",Pe,[b[12]||(b[12]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-2"},"SNR",-1)),t("div",ze,[t("span",Re,C(x.result.snr_db),1),b[11]||(b[11]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs"},"dB",-1))])])]),t("div",je,[b[15]||(b[15]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-3"},"Network Path",-1)),t("div",Ie,[t("div",Ue,[(f(!0),$(ct,null,gt(x.result.path,(L,_)=>(f(),$("div",{key:_,class:"flex items-center gap-2 flex-shrink-0 relative"},[t("div",{class:M(["bg-cyan-400/20 dark:bg-primary/20 text-cyan-600 dark:text-primary border border-cyan-400/40 dark:border-primary/30 px-3 py-1.5 rounded-lg text-sm font-mono transition-all duration-300",s.value&&Math.floor(v.value)===_?"ring-2 ring-cyan-400/50 dark:ring-primary/50 scale-105":""])},C(L),3),_[s.value&&v.value>=_&&v.value<_+1?(f(),$("div",Ve,b[13]||(b[13]=[t("svg",{class:"w-3 h-3 text-cyan-500 dark:text-primary drop-shadow-[0_0_6px_rgba(6,182,212,0.8)] dark:drop-shadow-[0_0_6px_rgba(59,130,246,0.8)]",fill:"currentColor",viewBox:"0 0 24 24"},[t("circle",{cx:"12",cy:"12",r:"8"})],-1)]))):D("",!0)]),_:2},1024)])):D("",!0)]))),128))])]),t("div",He,[t("span",null,C(x.result.path.length)+" hop"+C(x.result.path.length!==1?"s":""),1),s.value?(f(),$("span",Ze,"● Tracing route...")):D("",!0)])]),t("div",We,[t("span",null,"Target: "+C(x.result.target_id),1),t("span",null,"Tag: #"+C(x.result.tag),1)])])):D("",!0)]),t("div",{class:"border-t border-stroke-subtle dark:border-stroke/10 px-6 py-4"},[t("button",{onClick:S,class:"w-full py-2.5 bg-gradient-to-r from-cyan-400 to-cyan-500 text-white hover:from-cyan-500 hover:to-cyan-600 dark:bg-primary/20 dark:text-primary dark:border dark:border-primary/30 dark:hover:bg-primary/30 dark:from-transparent dark:to-transparent rounded-lg font-medium transition-all shadow-[0_2px_12px_rgba(6,182,212,0.3)] dark:shadow-none"}," Close ")])])])):D("",!0)]),_:1})]))}}),qe=It(Qe,[["__scopeId","data-v-b206e10a"]]),Ke={class:"bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] shadow-2xl border border-stroke-subtle dark:border-white/20 flex flex-col h-full overflow-hidden"},Ge={class:"flex items-center justify-between p-8 pb-4 flex-shrink-0"},Je={class:"flex-1 min-w-0"},Ye={class:"text-2xl font-bold text-content-primary dark:text-content-primary mb-1"},Xe={class:"text-content-secondary dark:text-content-muted text-sm font-mono break-all"},to={class:"flex items-center gap-2"},eo={class:"flex-1 overflow-y-auto custom-scrollbar px-8"},oo={class:"mb-6"},ro={class:"grid grid-cols-1 md:grid-cols-2 gap-4"},no={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},so={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},ao={class:"text-content-primary dark:text-content-primary font-medium"},io={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},lo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},co={class:"text-content-primary dark:text-content-primary font-medium"},uo={class:"mb-6"},po={class:"grid grid-cols-1 md:grid-cols-3 gap-4"},go={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},mo={class:"text-content-primary dark:text-content-primary font-medium"},ho={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},bo={class:"text-content-primary dark:text-content-primary font-medium"},xo={key:0,class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},yo={class:"flex items-center gap-2"},vo={class:"flex gap-0.5"},ko={class:"mb-6"},fo={class:"grid grid-cols-1 md:grid-cols-2 gap-4"},wo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},_o={class:"text-content-primary dark:text-content-primary text-sm"},Co={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},$o={class:"text-content-primary dark:text-content-primary text-sm"},Mo={key:0,class:"mb-6"},Ao={class:"grid grid-cols-1 md:grid-cols-3 gap-4 mb-4"},Lo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},To={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Eo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},So={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Bo={class:"glass-card bg-background-mute dark:bg-black/20 p-4 rounded-[12px]"},No={class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},Fo={key:0,class:"text-content-primary dark:text-content-primary font-medium"},Do={class:"p-8 pt-4 border-t border-stroke-subtle dark:border-white/10 flex-shrink-0"},Po=bt({name:"NeighborDetailsModal",__name:"NeighborDetailsModal",props:{neighbor:{},isOpen:{type:Boolean},baseLatitude:{default:null},baseLongitude:{default:null}},emits:["close"],setup(A,{emit:o}){const{getSignalQuality:r}=Ut(),i=F("Copy"),e=A,d=o,g=F();let s=null;const a=y=>new Date(y*1e3).toLocaleString(),w=y=>y?`${y} dBm`:"N/A",m=y=>y?`${y.toFixed(1)} dB`:"N/A",v=y=>({0:"Transport Flood",1:"Flood",2:"Direct",3:"Transport Direct"})[y||0]||"Unknown",S=y=>({Unknown:"Unknown","Chat Node":"Chat Node",Repeater:"Repeater","Room Server":"Room Server","Hybrid Node":"Hybrid Node"})[y]||y,x=y=>({Unknown:"text-gray-600 dark:text-gray-400","Chat Node":"text-blue-600 dark:text-blue-400",Repeater:"text-emerald-600 dark:text-emerald-400","Room Server":"text-purple-600 dark:text-purple-400","Hybrid Node":"text-amber-600 dark:text-amber-400"})[y]||"text-gray-600 dark:text-gray-400",b=async()=>{if(!e.neighbor?.latitude||!e.neighbor?.longitude)return;const y=e.neighbor.latitude.toFixed(6),u=e.neighbor.longitude.toFixed(6),j=`${y}, ${u}`;try{await navigator.clipboard.writeText(j),i.value="Copied!",setTimeout(()=>{i.value="Copy"},2e3)}catch(K){console.error("Failed to copy coordinates:",K),i.value="Failed",setTimeout(()=>{i.value="Copy"},2e3)}},L=J(()=>{if(!e.neighbor?.latitude||!e.neighbor?.longitude||!e.baseLatitude||!e.baseLongitude)return null;const y=6371,u=(e.neighbor.latitude-e.baseLatitude)*Math.PI/180,j=(e.neighbor.longitude-e.baseLongitude)*Math.PI/180,K=Math.sin(u/2)*Math.sin(u/2)+Math.cos(e.baseLatitude*Math.PI/180)*Math.cos(e.neighbor.latitude*Math.PI/180)*Math.sin(j/2)*Math.sin(j/2),et=2*Math.atan2(Math.sqrt(K),Math.sqrt(1-K));return y*et}),_=J(()=>e.neighbor?.latitude!==null&&e.neighbor?.longitude!==null&&e.neighbor?.latitude!==0&&e.neighbor?.longitude!==0&&Math.abs(e.neighbor?.latitude??0)<=90&&Math.abs(e.neighbor?.longitude??0)<=180),k=()=>{if(!g.value||!e.neighbor||!_.value)return;s&&(s.remove(),s=null);const y=document.documentElement.classList.contains("dark");s=W.map(g.value,{center:[e.neighbor.latitude,e.neighbor.longitude],zoom:13,zoomControl:!0,attributionControl:!1});const u=y?"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png":"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";W.tileLayer(u,{maxZoom:19,attribution:"© OpenStreetMap © CARTO"}).addTo(s);const j=W.divIcon({className:"custom-marker",html:`
${e.neighbor.node_name?.charAt(0)||"?"}
`,iconSize:[32,32],iconAnchor:[16,16]});if(W.marker([e.neighbor.latitude,e.neighbor.longitude],{icon:j}).addTo(s).bindPopup(`${e.neighbor.node_name||"Unknown"}
${e.neighbor.pubkey.slice(0,8)}...`),e.baseLatitude!==null&&e.baseLongitude!==null&&e.baseLatitude!==0&&e.baseLongitude!==0&&Math.abs(e.baseLatitude)<=90&&Math.abs(e.baseLongitude)<=180){const et=W.divIcon({className:"custom-marker",html:'
B
',iconSize:[32,32],iconAnchor:[16,16]});W.marker([e.baseLatitude,e.baseLongitude],{icon:et}).addTo(s).bindPopup("Base Station"),W.polyline([[e.baseLatitude,e.baseLongitude],[e.neighbor.latitude,e.neighbor.longitude]],{color:"#3b82f6",weight:2,opacity:.6,dashArray:"5, 10"}).addTo(s);const lt=W.latLngBounds([e.baseLatitude,e.baseLongitude],[e.neighbor.latitude,e.neighbor.longitude]);s.fitBounds(lt,{padding:[50,50]})}},P=y=>{y.key==="Escape"&&d("close")},I=y=>{y.target===y.currentTarget&&d("close")};ht(()=>e.isOpen,y=>{y?(document.body.style.overflow="hidden",setTimeout(()=>{_.value&&k()},100)):(document.body.style.overflow="",s&&(s.remove(),s=null))},{immediate:!0});const Z=J(()=>e.neighbor?.rssi?r(e.neighbor.rssi):null);return(y,u)=>(f(),Rt(jt,{to:"body"},[it(Ft,{name:"modal",appear:""},{default:Dt(()=>[y.isOpen&&y.neighbor?(f(),$("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 overflow-hidden",onClick:I,onKeydown:P,tabindex:"0"},[u[20]||(u[20]=t("div",{class:"absolute inset-0 bg-black/60 backdrop-blur-md pointer-events-none"},null,-1)),t("div",{class:"relative w-full max-w-4xl max-h-[90vh] flex flex-col",onClick:u[2]||(u[2]=Lt(()=>{},["stop"]))},[t("div",Ke,[t("div",Ge,[t("div",Je,[t("h2",Ye,C(y.neighbor.node_name||"Unknown Node"),1),t("p",Xe,C(y.neighbor.pubkey),1)]),t("div",to,[t("button",{onClick:u[0]||(u[0]=j=>d("close")),class:"w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors duration-200 text-gray-700 dark:text-white hover:text-gray-900 dark:hover:text-white"},u[3]||(u[3]=[t("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))])]),t("div",eo,[t("div",oo,[u[8]||(u[8]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Basic Information",-1)),t("div",ro,[t("div",no,[u[4]||(u[4]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Contact Type",-1)),t("div",{class:M(["font-medium",x(y.neighbor.contact_type)])},C(S(y.neighbor.contact_type)),3)]),t("div",so,[u[5]||(u[5]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Route Type",-1)),t("div",ao,C(v(y.neighbor.route_type)),1)]),t("div",io,[u[6]||(u[6]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Zero Hop",-1)),t("div",{class:M(["font-medium",y.neighbor.zero_hop?"text-green-600 dark:text-green-400":"text-gray-600 dark:text-gray-400"])},C(y.neighbor.zero_hop?"Yes":"No"),3)]),t("div",lo,[u[7]||(u[7]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Advert Count",-1)),t("div",co,C(y.neighbor.advert_count.toLocaleString()),1)])])]),t("div",uo,[u[12]||(u[12]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Signal Quality",-1)),t("div",po,[t("div",go,[u[9]||(u[9]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"RSSI",-1)),t("div",mo,C(w(y.neighbor.rssi)),1)]),t("div",ho,[u[10]||(u[10]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"SNR",-1)),t("div",bo,C(m(y.neighbor.snr)),1)]),Z.value?(f(),$("div",xo,[u[11]||(u[11]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Signal Strength",-1)),t("div",yo,[t("div",vo,[(f(),$(ct,null,gt(4,j=>t("div",{key:j,class:M(["w-1 h-3 rounded-sm",j<=Z.value.bars?Z.value.color:"bg-gray-300 dark:bg-gray-700"])},null,2)),64))]),t("span",{class:M(["text-sm font-medium",Z.value.color])},C(Z.value.quality),3)])])):D("",!0)])]),t("div",ko,[u[15]||(u[15]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Timeline",-1)),t("div",fo,[t("div",wo,[u[13]||(u[13]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"First Seen",-1)),t("div",_o,C(a(y.neighbor.first_seen)),1)]),t("div",Co,[u[14]||(u[14]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Last Seen",-1)),t("div",$o,C(a(y.neighbor.last_seen)),1)])])]),_.value?(f(),$("div",Mo,[u[19]||(u[19]=t("h3",{class:"text-lg font-semibold text-content-primary dark:text-content-primary mb-4"},"Location",-1)),t("div",Ao,[t("div",Lo,[u[16]||(u[16]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Latitude",-1)),t("div",To,C(y.neighbor.latitude?.toFixed(6)),1)]),t("div",Eo,[u[17]||(u[17]=t("div",{class:"text-content-muted dark:text-content-muted text-xs uppercase tracking-wide mb-1"},"Longitude",-1)),t("div",So,C(y.neighbor.longitude?.toFixed(6)),1)]),t("div",Bo,[t("div",No,C(L.value!==null?"Distance":"Coordinates"),1),L.value!==null?(f(),$("div",Fo,C(L.value.toFixed(2))+" km ",1)):(f(),$("button",{key:1,onClick:b,class:"w-full px-3 py-1.5 bg-primary hover:bg-primary/90 dark:bg-gray-700 dark:hover:bg-gray-600 text-white text-sm font-medium rounded-lg transition-colors flex items-center justify-center gap-1.5"},[u[18]||(u[18]=t("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})],-1)),tt(" "+C(i.value),1)]))])]),t("div",{ref_key:"mapContainer",ref:g,class:"w-full h-96 rounded-[12px] overflow-hidden border border-stroke-subtle dark:border-white/10"},null,512)])):D("",!0)]),t("div",Do,[t("button",{onClick:u[1]||(u[1]=j=>d("close")),class:"w-full px-4 py-2.5 bg-primary hover:bg-primary/90 dark:bg-gray-700 dark:hover:bg-gray-600 text-white font-medium rounded-lg transition-colors"}," Close ")])])])],32)):D("",!0)]),_:1})]))}}),zo=It(Po,[["__scopeId","data-v-5669a05a"]]),Qt=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array],St=1,vt=8;class Ot{static from(o){if(!(o instanceof ArrayBuffer))throw new Error("Data must be an instance of ArrayBuffer.");const[r,i]=new Uint8Array(o,0,2);if(r!==219)throw new Error("Data does not appear to be in a KDBush format.");const e=i>>4;if(e!==St)throw new Error(`Got v${e} data when expected v${St}.`);const d=Qt[i&15];if(!d)throw new Error("Unrecognized array type.");const[g]=new Uint16Array(o,2,1),[s]=new Uint32Array(o,4,1);return new Ot(s,g,d,o)}constructor(o,r=64,i=Float64Array,e){if(isNaN(o)||o<0)throw new Error(`Unpexpected numItems value: ${o}.`);this.numItems=+o,this.nodeSize=Math.min(Math.max(+r,2),65535),this.ArrayType=i,this.IndexArrayType=o<65536?Uint16Array:Uint32Array;const d=Qt.indexOf(this.ArrayType),g=o*2*this.ArrayType.BYTES_PER_ELEMENT,s=o*this.IndexArrayType.BYTES_PER_ELEMENT,a=(8-s%8)%8;if(d<0)throw new Error(`Unexpected typed array class: ${i}.`);e&&e instanceof ArrayBuffer?(this.data=e,this.ids=new this.IndexArrayType(this.data,vt,o),this.coords=new this.ArrayType(this.data,vt+s+a,o*2),this._pos=o*2,this._finished=!0):(this.data=new ArrayBuffer(vt+g+s+a),this.ids=new this.IndexArrayType(this.data,vt,o),this.coords=new this.ArrayType(this.data,vt+s+a,o*2),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,(St<<4)+d]),new Uint16Array(this.data,2,1)[0]=r,new Uint32Array(this.data,4,1)[0]=o)}add(o,r){const i=this._pos>>1;return this.ids[i]=i,this.coords[this._pos++]=o,this.coords[this._pos++]=r,i}finish(){const o=this._pos>>1;if(o!==this.numItems)throw new Error(`Added ${o} items when expected ${this.numItems}.`);return zt(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this}range(o,r,i,e){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:d,coords:g,nodeSize:s}=this,a=[0,d.length-1,0],w=[];for(;a.length;){const m=a.pop()||0,v=a.pop()||0,S=a.pop()||0;if(v-S<=s){for(let _=S;_<=v;_++){const k=g[2*_],P=g[2*_+1];k>=o&&k<=i&&P>=r&&P<=e&&w.push(d[_])}continue}const x=S+v>>1,b=g[2*x],L=g[2*x+1];b>=o&&b<=i&&L>=r&&L<=e&&w.push(d[x]),(m===0?o<=b:r<=L)&&(a.push(S),a.push(x-1),a.push(1-m)),(m===0?i>=b:e>=L)&&(a.push(x+1),a.push(v),a.push(1-m))}return w}within(o,r,i){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:e,coords:d,nodeSize:g}=this,s=[0,e.length-1,0],a=[],w=i*i;for(;s.length;){const m=s.pop()||0,v=s.pop()||0,S=s.pop()||0;if(v-S<=g){for(let _=S;_<=v;_++)qt(d[2*_],d[2*_+1],o,r)<=w&&a.push(e[_]);continue}const x=S+v>>1,b=d[2*x],L=d[2*x+1];qt(b,L,o,r)<=w&&a.push(e[x]),(m===0?o-i<=b:r-i<=L)&&(s.push(S),s.push(x-1),s.push(1-m)),(m===0?o+i>=b:r+i>=L)&&(s.push(x+1),s.push(v),s.push(1-m))}return a}}function zt(A,o,r,i,e,d){if(e-i<=r)return;const g=i+e>>1;ee(A,o,g,i,e,d),zt(A,o,r,i,g-1,1-d),zt(A,o,r,g+1,e,1-d)}function ee(A,o,r,i,e,d){for(;e>i;){if(e-i>600){const w=e-i+1,m=r-i+1,v=Math.log(w),S=.5*Math.exp(2*v/3),x=.5*Math.sqrt(v*S*(w-S)/w)*(m-w/2<0?-1:1),b=Math.max(i,Math.floor(r-m*S/w+x)),L=Math.min(e,Math.floor(r+(w-m)*S/w+x));ee(A,o,r,b,L,d)}const g=o[2*r+d];let s=i,a=e;for(kt(A,o,i,r),o[2*e+d]>g&&kt(A,o,i,e);sg;)a--}o[2*i+d]===g?kt(A,o,i,a):(a++,kt(A,o,a,e)),a<=r&&(i=a+1),r<=a&&(e=a-1)}}function kt(A,o,r,i){Bt(A,r,i),Bt(o,2*r,2*i),Bt(o,2*r+1,2*i+1)}function Bt(A,o,r){const i=A[o];A[o]=A[r],A[r]=i}function qt(A,o,r,i){const e=A-r,d=o-i;return e*e+d*d}const Ro={minZoom:0,maxZoom:16,minPoints:2,radius:40,extent:512,nodeSize:64,log:!1,generateId:!1,reduce:null,map:A=>A},Kt=Math.fround||(A=>o=>(A[0]=+o,A[0]))(new Float32Array(1)),mt=2,pt=3,Nt=4,ut=5,oe=6;class jo{constructor(o){this.options=Object.assign(Object.create(Ro),o),this.trees=new Array(this.options.maxZoom+1),this.stride=this.options.reduce?7:6,this.clusterProps=[]}load(o){const{log:r,minZoom:i,maxZoom:e}=this.options;r&&console.time("total time");const d=`prepare ${o.length} points`;r&&console.time(d),this.points=o;const g=[];for(let a=0;a=i;a--){const w=+Date.now();s=this.trees[a]=this._createTree(this._cluster(s,a)),r&&console.log("z%d: %d clusters in %dms",a,s.numItems,+Date.now()-w)}return r&&console.timeEnd("total time"),this}getClusters(o,r){let i=((o[0]+180)%360+360)%360-180;const e=Math.max(-90,Math.min(90,o[1]));let d=o[2]===180?180:((o[2]+180)%360+360)%360-180;const g=Math.max(-90,Math.min(90,o[3]));if(o[2]-o[0]>=360)i=-180,d=180;else if(i>d){const v=this.getClusters([i,e,180,g],r),S=this.getClusters([-180,e,d,g],r);return v.concat(S)}const s=this.trees[this._limitZoom(r)],a=s.range($t(i),Mt(g),$t(d),Mt(e)),w=s.data,m=[];for(const v of a){const S=this.stride*v;m.push(w[S+ut]>1?Gt(w,S,this.clusterProps):this.points[w[S+pt]])}return m}getChildren(o){const r=this._getOriginId(o),i=this._getOriginZoom(o),e="No cluster with the specified id.",d=this.trees[i];if(!d)throw new Error(e);const g=d.data;if(r*this.stride>=g.length)throw new Error(e);const s=this.options.radius/(this.options.extent*Math.pow(2,i-1)),a=g[r*this.stride],w=g[r*this.stride+1],m=d.within(a,w,s),v=[];for(const S of m){const x=S*this.stride;g[x+Nt]===o&&v.push(g[x+ut]>1?Gt(g,x,this.clusterProps):this.points[g[x+pt]])}if(v.length===0)throw new Error(e);return v}getLeaves(o,r,i){r=r||10,i=i||0;const e=[];return this._appendLeaves(e,o,r,i,0),e}getTile(o,r,i){const e=this.trees[this._limitZoom(o)],d=Math.pow(2,o),{extent:g,radius:s}=this.options,a=s/g,w=(i-a)/d,m=(i+1+a)/d,v={features:[]};return this._addTileFeatures(e.range((r-a)/d,w,(r+1+a)/d,m),e.data,r,i,d,v),r===0&&this._addTileFeatures(e.range(1-a/d,w,1,m),e.data,d,i,d,v),r===d-1&&this._addTileFeatures(e.range(0,w,a/d,m),e.data,-1,i,d,v),v.features.length?v:null}getClusterExpansionZoom(o){let r=this._getOriginZoom(o)-1;for(;r<=this.options.maxZoom;){const i=this.getChildren(o);if(r++,i.length!==1)break;o=i[0].properties.cluster_id}return r}_appendLeaves(o,r,i,e,d){const g=this.getChildren(r);for(const s of g){const a=s.properties;if(a&&a.cluster?d+a.point_count<=e?d+=a.point_count:d=this._appendLeaves(o,a.cluster_id,i,e,d):d1;let m,v,S;if(w)m=re(r,a,this.clusterProps),v=r[a],S=r[a+1];else{const L=this.points[r[a+pt]];m=L.properties;const[_,k]=L.geometry.coordinates;v=$t(_),S=Mt(k)}const x={type:1,geometry:[[Math.round(this.options.extent*(v*d-i)),Math.round(this.options.extent*(S*d-e))]],tags:m};let b;w||this.options.generateId?b=r[a+pt]:b=this.points[r[a+pt]].id,b!==void 0&&(x.id=b),g.features.push(x)}}_limitZoom(o){return Math.max(this.options.minZoom,Math.min(Math.floor(+o),this.options.maxZoom+1))}_cluster(o,r){const{radius:i,extent:e,reduce:d,minPoints:g}=this.options,s=i/(e*Math.pow(2,r)),a=o.data,w=[],m=this.stride;for(let v=0;vr&&(_+=a[P+ut])}if(_>L&&_>=g){let k=S*L,P=x*L,I,Z=-1;const y=((v/m|0)<<5)+(r+1)+this.points.length;for(const u of b){const j=u*m;if(a[j+mt]<=r)continue;a[j+mt]=r;const K=a[j+ut];k+=a[j]*K,P+=a[j+1]*K,a[j+Nt]=y,d&&(I||(I=this._map(a,v,!0),Z=this.clusterProps.length,this.clusterProps.push(I)),d(I,this._map(a,j)))}a[v+Nt]=y,w.push(k/_,P/_,1/0,y,-1,_),d&&w.push(Z)}else{for(let k=0;k1)for(const k of b){const P=k*m;if(!(a[P+mt]<=r)){a[P+mt]=r;for(let I=0;I>5}_getOriginZoom(o){return(o-this.points.length)%32}_map(o,r,i){if(o[r+ut]>1){const g=this.clusterProps[o[r+oe]];return i?Object.assign({},g):g}const e=this.points[o[r+pt]].properties,d=this.options.map(e);return i&&d===e?Object.assign({},d):d}}function Gt(A,o,r){return{type:"Feature",id:A[o+pt],properties:re(A,o,r),geometry:{type:"Point",coordinates:[Io(A[o]),Uo(A[o+1])]}}}function re(A,o,r){const i=A[o+ut],e=i>=1e4?`${Math.round(i/1e3)}k`:i>=1e3?`${Math.round(i/100)/10}k`:i,d=A[o+oe],g=d===-1?{}:Object.assign({},r[d]);return Object.assign(g,{cluster:!0,cluster_id:A[o+pt],point_count:i,point_count_abbreviated:e})}function $t(A){return A/360+.5}function Mt(A){const o=Math.sin(A*Math.PI/180),r=.5-.25*Math.log((1+o)/(1-o))/Math.PI;return r<0?0:r>1?1:r}function Io(A){return(A-.5)*360}function Uo(A){const o=(180-A*360)*Math.PI/180;return 360*Math.atan(Math.exp(o))/Math.PI-90}const Oo={class:"map-container"},Vo={key:0,class:"flex items-center justify-center h-96 glass-card backdrop-blur border border-black/6 dark:border-white/10 rounded-[12px] shadow-sm dark:shadow-none"},Ho={class:"hidden sm:inline"},Zo={key:3,class:"map-legend"},Wo={class:"legend-footer"},Qo={key:4,class:"map-attribution"},qo=bt({__name:"NetworkMap",props:{adverts:{},baseLatitude:{default:null},baseLongitude:{default:null},showLegend:{type:Boolean,default:!0}},emits:["update:showLegend"],setup(A,{expose:o,emit:r}){typeof window<"u"&&!window.chrome&&(window.chrome={runtime:{}});const i=A,e=r,d=()=>{e("update:showLegend",!i.showLegend)},g=F();let s=null;const a=F(new Map);let w=null;const m=F(new Map),v=F([]),S=F(!0),x=F(60),b=F(14),L=F(document.documentElement.classList.contains("dark")),_=new MutationObserver(()=>{const E=document.documentElement.classList.contains("dark");E!==L.value&&(L.value=E,s&&K())}),k=J(()=>i.baseLatitude!==null&&i.baseLongitude!==null&&typeof i.baseLatitude=="number"&&typeof i.baseLongitude=="number"&&i.baseLatitude!==0&&i.baseLongitude!==0&&Math.abs(i.baseLatitude)<=90&&Math.abs(i.baseLongitude)<=180),P=E=>new Date(E*1e3).toLocaleString(),I=E=>E?`${E} dBm`:"N/A",Z=E=>E?`${E} dB`:"N/A",y=E=>({0:"Transport Flood",1:"Flood",2:"Direct",3:"Transport Direct"})[E||0]||"Unknown",u=(E,c,n,l)=>{const R=(n-E)*Math.PI/180,V=(l-c)*Math.PI/180,G=Math.sin(R/2)*Math.sin(R/2)+Math.cos(E*Math.PI/180)*Math.cos(n*Math.PI/180)*Math.sin(V/2)*Math.sin(V/2);return 6371*(2*Math.atan2(Math.sqrt(G),Math.sqrt(1-G)))},j=()=>{s&&(v.value.forEach(E=>{s&&E.remove()}),v.value.length=0,s.remove(),s=null),a.value.clear(),m.value.clear(),w=null},K=async()=>{const E=s?.getZoom()||11,c=s?.getCenter()||(k.value?[i.baseLatitude,i.baseLongitude]:[0,0]);j(),await Pt(),await at(),s&&s.setView(c,E)},et=E=>{const c=new Map;return E.filter(n=>n.latitude!==null&&n.longitude!==null).map(n=>{let l=n.latitude,B=n.longitude;const R=`${l.toFixed(6)}_${B.toFixed(6)}`,V=c.get(R)||0;if(c.set(R,V+1),V>0){const X=V*60*(Math.PI/180);l+=Math.sin(X)*.001*(V*.5),B+=Math.cos(X)*.001*(V*.5)}return{type:"Feature",properties:{advert:{...n,jittered_latitude:l,jittered_longitude:B}},geometry:{type:"Point",coordinates:[B,l]}}})},lt=E=>{w=new jo({radius:x.value,maxZoom:b.value,minPoints:2}),w.load(E)},at=async()=>{if(!g.value||!k.value){console.warn("Cannot initialize map: missing container or coordinates");return}j(),await Pt();const E=i.baseLatitude,c=i.baseLongitude;s=W.map(g.value,{center:[E,c],zoom:11,zoomControl:!0,attributionControl:!1,preferCanvas:!1});try{const n=L.value?"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png":"https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png",l=L.value?"https://{s}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png":"https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png",B=W.tileLayer(n,{maxZoom:19,attribution:'© OpenStreetMap contributors © CARTO',errorTileUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="}),R=W.tileLayer(l,{maxZoom:19,attribution:"",errorTileUrl:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="});B.addTo(s),R.addTo(s)}catch(n){console.warn("Error loading tiles:",n)}try{const n=(z,H=!1)=>{const h=H?16:12;return W.divIcon({className:"custom-div-icon",html:`
`,iconSize:[h+4,h+4],iconAnchor:[(h+4)/2,(h+4)/2]})},l=z=>{const H=z<10?30:z<100?40:50;return W.divIcon({className:"custom-cluster-icon",html:` +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/Neighbors-BgIbF2mI.js
{await p(),w.value=!0});async function p(){x.value=!0,v.value=null;try{const a=await b.getACLInfo();a.success&&(_.value=a.data);const s=await b.getACLClients();s.success&&s.data&&(u.value=s.data.clients||[]);const e=await b.getACLStats();e.success&&(l.value=e.data)}catch(a){v.value=a instanceof Error?a.message:"Failed to load ACL data",console.error("Error fetching ACL data:",a)}finally{x.value=!1}}async function C(a,s){if(confirm("Are you sure you want to remove this client from the ACL?"))try{const e=await b.removeACLClient({public_key:a,identity_hash:s});e.success?await p():alert(`Failed to remove client: ${e.error}`)}catch(e){alert(`Error removing client: ${e}`)}}function g(a){return a?new Date(a*1e3).toLocaleString():"Never"}function j(a){c.value=a}const A=S(()=>k.value?u.value.filter(a=>a.identity_name===k.value):u.value),h=S(()=>_.value?_.value.acls||[]:[]);return(a,s)=>(r(),o("div",z,[s[22]||(s[22]=t("div",null,[t("h1",{class:"text-2xl font-bold text-content-primary dark:text-content-primary"},"Sessions & Access Control"),t("p",{class:"text-content-secondary dark:text-content-muted mt-2"},"Manage authenticated clients and access control lists")],-1)),l.value?(r(),o("div",D,[t("div",F,[s[1]||(s[1]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Total Identities",-1)),t("div",T,n(l.value.total_identities),1)]),t("div",$,[s[2]||(s[2]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Authenticated Clients",-1)),t("div",E,n(l.value.total_clients),1)]),t("div",H,[s[3]||(s[3]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Admin Clients",-1)),t("div",P,n(l.value.admin_clients),1)]),t("div",G,[s[4]||(s[4]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Guest Clients",-1)),t("div",O,n(l.value.guest_clients),1)])])):f("",!0),t("div",q,[t("div",U,[(r(),o(m,null,y(M,e=>t("button",{key:e.id,onClick:L=>j(e.id),class:i(["px-4 py-2 text-sm font-medium transition-colors duration-200 border-b-2 mr-6 mb-2",c.value===e.id?"text-cyan-500 dark:text-primary border-cyan-500 dark:border-primary":"text-content-secondary dark:text-content-muted border-transparent hover:text-content-primary dark:hover:text-content-primary hover:border-stroke-subtle dark:hover:border-stroke/30"])},[t("div",K,[e.icon==="overview"?(r(),o("svg",Q,s[5]||(s[5]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"},null,-1)]))):e.icon==="clients"?(r(),o("svg",W,s[6]||(s[6]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"},null,-1)]))):e.icon==="identities"?(r(),o("svg",X,s[7]||(s[7]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"},null,-1)]))):f("",!0),B(" "+n(e.label),1)])],10,J)),64))]),t("div",Y,[x.value&&!w.value?(r(),o("div",Z,s[8]||(s[8]=[t("div",{class:"text-center"},[t("div",{class:"animate-spin w-8 h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-cyan-500 dark:border-t-primary rounded-full mx-auto mb-4"}),t("div",{class:"text-content-secondary dark:text-content-muted"},"Loading ACL data...")],-1)]))):v.value?(r(),o("div",tt,[t("div",et,[s[9]||(s[9]=t("div",{class:"text-red-500 dark:text-red-400 mb-2"},"Failed to load ACL data",-1)),t("div",st,n(v.value),1),t("button",{onClick:p,class:"px-4 py-2 bg-cyan-500/20 dark:bg-primary/20 hover:bg-cyan-500/30 dark:hover:bg-primary/30 text-cyan-900 dark:text-white rounded-lg border border-cyan-500/50 dark:border-primary/50 transition-colors"}," Retry ")])])):c.value==="overview"?(r(),o("div",nt,[h.value.length===0?(r(),o("div",ot," No identities configured ")):(r(),o("div",rt,[(r(!0),o(m,null,y(h.value,e=>(r(),o("div",{key:e.hash,class:"glass-card rounded-[10px] p-4 border border-stroke-subtle dark:border-white/10 hover:border-cyan-400 dark:hover:border-primary/30 transition-colors"},[t("div",at,[t("div",dt,[t("div",it,[t("h3",lt,n(e.name),1),t("span",{class:i(["px-2 py-1 text-xs font-medium rounded",e.type==="repeater"?"bg-cyan-500/20 dark:bg-primary/20 text-cyan-700 dark:text-primary":"bg-yellow-100 dark:bg-yellow-500/20 dark:bg-secondary/20 text-yellow-700 dark:text-secondary"])},n(e.type),3),t("span",ct,n(e.hash),1)]),t("div",xt,[t("div",null,[s[10]||(s[10]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Max Clients",-1)),t("div",ut,n(e.max_clients),1)]),t("div",null,[s[11]||(s[11]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Authenticated",-1)),t("div",mt,n(e.authenticated_clients),1)]),t("div",null,[s[12]||(s[12]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Admin Password",-1)),t("div",{class:i(e.has_admin_password?"text-green-700 dark:text-green-500 dark:text-accent-green":"text-red-500 dark:text-accent-red")},n(e.has_admin_password?"✓ Set":"✗ Not Set"),3)]),t("div",null,[s[13]||(s[13]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Guest Password",-1)),t("div",{class:i(e.has_guest_password?"text-green-700 dark:text-green-500 dark:text-accent-green":"text-red-500 dark:text-accent-red")},n(e.has_guest_password?"✓ Set":"✗ Not Set"),3)])]),t("div",yt,[s[14]||(s[14]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs"},"Read-Only Access:",-1)),t("span",{class:i(e.allow_read_only?"text-green-700 dark:text-green-500 dark:text-accent-green":"text-red-500 dark:text-accent-red")},n(e.allow_read_only?"Allowed":"Disabled"),3)])])])]))),128))]))])):c.value==="clients"?(r(),o("div",vt,[u.value.length===0?(r(),o("div",kt," No authenticated clients ")):(r(),o("div",pt,[t("table",bt,[s[15]||(s[15]=t("thead",null,[t("tr",{class:"border-b border-stroke-subtle dark:border-stroke/10"},[t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Client"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Address"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Identity"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Permissions"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Last Activity"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Actions")])],-1)),t("tbody",null,[(r(!0),o(m,null,y(u.value,e=>(r(),o("tr",{key:e.public_key_full,class:"border-b border-stroke-subtle dark:border-white/5 hover:bg-gray-100/50 dark:hover:bg-white/5 transition-colors"},[t("td",_t,[t("div",gt,n(e.public_key),1)]),t("td",ht,[t("div",ft,n(e.address),1)]),t("td",wt,[t("div",Ct,n(e.identity_name),1),t("div",At,n(e.identity_hash),1)]),t("td",Lt,[t("span",{class:i(["px-2 py-1 text-xs font-medium rounded",e.permissions==="admin"?"bg-green-100 dark:bg-green-500/20 dark:bg-accent-green/20 text-green-700 dark:text-accent-green":"bg-yellow-100 dark:bg-yellow-500/20 dark:bg-secondary/20 text-yellow-700 dark:text-secondary"])},n(e.permissions),3)]),t("td",St,[t("div",Mt,n(g(e.last_activity)),1)]),t("td",jt,[t("button",{onClick:L=>C(e.public_key_full,e.identity_hash),class:"px-3 py-1 bg-red-100 dark:bg-red-500/20 dark:bg-accent-red/20 hover:bg-red-500/30 dark:hover:bg-accent-red/30 text-red-600 dark:text-accent-red rounded text-xs transition-colors"}," Remove ",8,It)])]))),128))])])]))])):c.value==="identities"?(r(),o("div",Nt,[t("div",Rt,[s[17]||(s[17]=t("label",{class:"block text-content-secondary dark:text-content-muted text-sm mb-2"},"Filter by Identity",-1)),R(t("select",{"onUpdate:modelValue":s[0]||(s[0]=e=>k.value=e),class:"bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-2 text-content-primary dark:text-content-primary focus:outline-none focus:border-cyan-500 dark:focus:border-primary/50 transition-colors"},[s[16]||(s[16]=t("option",{value:null},"All Identities",-1)),(r(!0),o(m,null,y(h.value,e=>(r(),o("option",{key:e.name,value:e.name},n(e.name)+" ("+n(e.authenticated_clients)+" clients) ",9,Vt))),128))],512),[[V,k.value]])]),A.value.length===0?(r(),o("div",Bt," No clients for selected identity ")):(r(),o("div",zt,[(r(!0),o(m,null,y(A.value,e=>(r(),o("div",{key:e.public_key_full,class:"glass-card rounded-[10px] p-4 border border-stroke-subtle dark:border-white/10"},[t("div",Dt,[t("div",Ft,[t("div",Tt,[t("span",{class:i(["px-2 py-1 text-xs font-medium rounded",e.permissions==="admin"?"bg-green-100 dark:bg-green-500/20 dark:bg-accent-green/20 text-green-700 dark:text-accent-green":"bg-yellow-100 dark:bg-yellow-500/20 dark:bg-secondary/20 text-yellow-700 dark:text-secondary"])},n(e.permissions),3),t("span",$t,n(e.public_key),1)]),t("div",Et,[t("div",null,[s[18]||(s[18]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Address:",-1)),t("span",Ht,n(e.address),1)]),t("div",null,[s[19]||(s[19]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Identity:",-1)),t("span",Pt,n(e.identity_name)+" ("+n(e.identity_hash)+")",1)]),t("div",null,[s[20]||(s[20]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Last Activity:",-1)),t("span",Gt,n(g(e.last_activity)),1)]),t("div",null,[s[21]||(s[21]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Last Login:",-1)),t("span",Ot,n(g(e.last_login_success)),1)])])]),t("button",{onClick:L=>C(e.public_key_full,e.identity_hash),class:"ml-4 px-3 py-1 bg-red-100 dark:bg-red-500/20 dark:bg-accent-red/20 hover:bg-red-500/30 dark:hover:bg-accent-red/30 text-red-600 dark:text-accent-red rounded text-xs transition-colors"}," Remove ",8,qt)])]))),128))]))])):f("",!0)])]),t("div",Ut,[t("button",{onClick:p,disabled:x.value,class:"px-4 py-2 bg-cyan-500/20 dark:bg-primary/20 hover:bg-cyan-500/30 dark:hover:bg-primary/30 text-cyan-900 dark:text-primary rounded-lg border border-cyan-500/50 dark:border-primary/50 transition-colors disabled:opacity-50"},n(x.value?"Refreshing...":"Refresh Data"),9,Jt)])]))}});export{Wt as default}; +======== +import{a as I,r as d,o as N,L as b,c as S,b as o,e as t,g as f,t as n,F as m,h as y,w as R,q as V,j as i,k as B,p as r}from"./index-BfUIlcDy.js";const z={class:"p-6 space-y-6"},D={key:0,class:"grid grid-cols-1 md:grid-cols-4 gap-4"},F={class:"glass-card rounded-[15px] p-4"},T={class:"text-2xl font-bold text-content-primary dark:text-content-primary"},$={class:"glass-card rounded-[15px] p-4"},E={class:"text-2xl font-bold text-cyan-500 dark:text-primary"},H={class:"glass-card rounded-[15px] p-4"},P={class:"text-2xl font-bold text-green-700 dark:text-green-500 dark:text-accent-green"},G={class:"glass-card rounded-[15px] p-4"},O={class:"text-2xl font-bold text-yellow-500 dark:text-secondary"},q={class:"glass-card rounded-[15px] p-6"},U={class:"flex flex-wrap border-b border-stroke-subtle dark:border-stroke/10 mb-6"},J=["onClick"],K={class:"flex items-center gap-2"},Q={key:0,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},W={key:1,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},X={key:2,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Y={class:"min-h-[400px]"},Z={key:0,class:"flex items-center justify-center py-12"},tt={key:1,class:"flex items-center justify-center py-12"},et={class:"text-center"},st={class:"text-content-secondary dark:text-content-muted text-sm mb-4"},nt={key:2,class:"space-y-4"},ot={key:0,class:"text-center py-12 text-content-secondary dark:text-content-muted"},rt={key:1,class:"space-y-4"},at={class:"flex items-start justify-between"},dt={class:"flex-1"},it={class:"flex items-center gap-3 mb-2"},lt={class:"text-lg font-semibold text-content-primary dark:text-content-primary"},ct={class:"text-content-muted dark:text-content-muted text-sm"},xt={class:"grid grid-cols-2 md:grid-cols-4 gap-4 mt-4"},ut={class:"text-content-primary dark:text-content-primary font-medium"},mt={class:"text-cyan-500 dark:text-primary font-medium"},yt={class:"mt-3 flex items-center gap-2"},vt={key:3,class:"space-y-4"},kt={key:0,class:"text-center py-12 text-content-secondary dark:text-content-muted"},pt={key:1,class:"overflow-x-auto"},bt={class:"w-full"},_t={class:"py-3"},gt={class:"font-mono text-sm text-content-primary dark:text-content-primary"},ht={class:"py-3"},ft={class:"font-mono text-xs text-content-secondary dark:text-content-muted"},wt={class:"py-3"},Ct={class:"text-sm text-content-primary dark:text-content-primary"},At={class:"text-xs text-content-muted dark:text-content-muted"},Lt={class:"py-3"},St={class:"py-3"},Mt={class:"text-sm text-content-secondary dark:text-content-muted"},jt={class:"py-3"},It=["onClick"],Nt={key:4,class:"space-y-4"},Rt={class:"mb-4"},Vt=["value"],Bt={key:0,class:"text-center py-12 text-content-secondary dark:text-content-muted"},zt={key:1,class:"grid grid-cols-1 gap-4"},Dt={class:"flex items-start justify-between"},Ft={class:"flex-1"},Tt={class:"flex items-center gap-3 mb-3"},$t={class:"text-content-primary dark:text-content-primary font-mono text-sm"},Et={class:"grid grid-cols-1 md:grid-cols-2 gap-3 text-sm"},Ht={class:"text-content-primary dark:text-content-primary/90 font-mono ml-2"},Pt={class:"text-content-primary dark:text-content-primary/90 ml-2"},Gt={class:"text-content-primary dark:text-content-primary/90 ml-2"},Ot={class:"text-content-primary dark:text-content-primary/90 ml-2"},qt=["onClick"],Ut={class:"flex justify-end"},Jt=["disabled"],Wt=I({name:"SessionsView",__name:"Sessions",setup(Kt){const c=d("overview"),w=d(!1),x=d(!1),v=d(null),_=d(null),u=d([]),l=d(null),k=d(null),M=[{id:"overview",label:"Overview",icon:"overview"},{id:"clients",label:"Authenticated Clients",icon:"clients"},{id:"identities",label:"By Identity",icon:"identities"}];N(async()=>{await p(),w.value=!0});async function p(){x.value=!0,v.value=null;try{const a=await b.getACLInfo();a.success&&(_.value=a.data);const s=await b.getACLClients();s.success&&s.data&&(u.value=s.data.clients||[]);const e=await b.getACLStats();e.success&&(l.value=e.data)}catch(a){v.value=a instanceof Error?a.message:"Failed to load ACL data",console.error("Error fetching ACL data:",a)}finally{x.value=!1}}async function C(a,s){if(confirm("Are you sure you want to remove this client from the ACL?"))try{const e=await b.removeACLClient({public_key:a,identity_hash:s});e.success?await p():alert(`Failed to remove client: ${e.error}`)}catch(e){alert(`Error removing client: ${e}`)}}function g(a){return a?new Date(a*1e3).toLocaleString():"Never"}function j(a){c.value=a}const A=S(()=>k.value?u.value.filter(a=>a.identity_name===k.value):u.value),h=S(()=>_.value?_.value.acls||[]:[]);return(a,s)=>(r(),o("div",z,[s[22]||(s[22]=t("div",null,[t("h1",{class:"text-2xl font-bold text-content-primary dark:text-content-primary"},"Sessions & Access Control"),t("p",{class:"text-content-secondary dark:text-content-muted mt-2"},"Manage authenticated clients and access control lists")],-1)),l.value?(r(),o("div",D,[t("div",F,[s[1]||(s[1]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Total Identities",-1)),t("div",T,n(l.value.total_identities),1)]),t("div",$,[s[2]||(s[2]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Authenticated Clients",-1)),t("div",E,n(l.value.total_clients),1)]),t("div",H,[s[3]||(s[3]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Admin Clients",-1)),t("div",P,n(l.value.admin_clients),1)]),t("div",G,[s[4]||(s[4]=t("div",{class:"text-content-secondary dark:text-content-muted text-sm mb-1"},"Guest Clients",-1)),t("div",O,n(l.value.guest_clients),1)])])):f("",!0),t("div",q,[t("div",U,[(r(),o(m,null,y(M,e=>t("button",{key:e.id,onClick:L=>j(e.id),class:i(["px-4 py-2 text-sm font-medium transition-colors duration-200 border-b-2 mr-6 mb-2",c.value===e.id?"text-cyan-500 dark:text-primary border-cyan-500 dark:border-primary":"text-content-secondary dark:text-content-muted border-transparent hover:text-content-primary dark:hover:text-content-primary hover:border-stroke-subtle dark:hover:border-stroke/30"])},[t("div",K,[e.icon==="overview"?(r(),o("svg",Q,s[5]||(s[5]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"},null,-1)]))):e.icon==="clients"?(r(),o("svg",W,s[6]||(s[6]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"},null,-1)]))):e.icon==="identities"?(r(),o("svg",X,s[7]||(s[7]=[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"},null,-1)]))):f("",!0),B(" "+n(e.label),1)])],10,J)),64))]),t("div",Y,[x.value&&!w.value?(r(),o("div",Z,s[8]||(s[8]=[t("div",{class:"text-center"},[t("div",{class:"animate-spin w-8 h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-cyan-500 dark:border-t-primary rounded-full mx-auto mb-4"}),t("div",{class:"text-content-secondary dark:text-content-muted"},"Loading ACL data...")],-1)]))):v.value?(r(),o("div",tt,[t("div",et,[s[9]||(s[9]=t("div",{class:"text-red-500 dark:text-red-400 mb-2"},"Failed to load ACL data",-1)),t("div",st,n(v.value),1),t("button",{onClick:p,class:"px-4 py-2 bg-cyan-500/20 dark:bg-primary/20 hover:bg-cyan-500/30 dark:hover:bg-primary/30 text-cyan-900 dark:text-white rounded-lg border border-cyan-500/50 dark:border-primary/50 transition-colors"}," Retry ")])])):c.value==="overview"?(r(),o("div",nt,[h.value.length===0?(r(),o("div",ot," No identities configured ")):(r(),o("div",rt,[(r(!0),o(m,null,y(h.value,e=>(r(),o("div",{key:e.hash,class:"glass-card rounded-[10px] p-4 border border-stroke-subtle dark:border-white/10 hover:border-cyan-400 dark:hover:border-primary/30 transition-colors"},[t("div",at,[t("div",dt,[t("div",it,[t("h3",lt,n(e.name),1),t("span",{class:i(["px-2 py-1 text-xs font-medium rounded",e.type==="repeater"?"bg-cyan-500/20 dark:bg-primary/20 text-cyan-700 dark:text-primary":"bg-yellow-100 dark:bg-yellow-500/20 dark:bg-secondary/20 text-yellow-700 dark:text-secondary"])},n(e.type),3),t("span",ct,n(e.hash),1)]),t("div",xt,[t("div",null,[s[10]||(s[10]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Max Clients",-1)),t("div",ut,n(e.max_clients),1)]),t("div",null,[s[11]||(s[11]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Authenticated",-1)),t("div",mt,n(e.authenticated_clients),1)]),t("div",null,[s[12]||(s[12]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Admin Password",-1)),t("div",{class:i(e.has_admin_password?"text-green-700 dark:text-green-500 dark:text-accent-green":"text-red-500 dark:text-accent-red")},n(e.has_admin_password?"✓ Set":"✗ Not Set"),3)]),t("div",null,[s[13]||(s[13]=t("div",{class:"text-content-secondary dark:text-content-muted text-xs mb-1"},"Guest Password",-1)),t("div",{class:i(e.has_guest_password?"text-green-700 dark:text-green-500 dark:text-accent-green":"text-red-500 dark:text-accent-red")},n(e.has_guest_password?"✓ Set":"✗ Not Set"),3)])]),t("div",yt,[s[14]||(s[14]=t("span",{class:"text-content-secondary dark:text-content-muted text-xs"},"Read-Only Access:",-1)),t("span",{class:i(e.allow_read_only?"text-green-700 dark:text-green-500 dark:text-accent-green":"text-red-500 dark:text-accent-red")},n(e.allow_read_only?"Allowed":"Disabled"),3)])])])]))),128))]))])):c.value==="clients"?(r(),o("div",vt,[u.value.length===0?(r(),o("div",kt," No authenticated clients ")):(r(),o("div",pt,[t("table",bt,[s[15]||(s[15]=t("thead",null,[t("tr",{class:"border-b border-stroke-subtle dark:border-stroke/10"},[t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Client"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Address"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Identity"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Permissions"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Last Activity"),t("th",{class:"text-left text-content-secondary dark:text-content-muted text-sm font-medium pb-3"},"Actions")])],-1)),t("tbody",null,[(r(!0),o(m,null,y(u.value,e=>(r(),o("tr",{key:e.public_key_full,class:"border-b border-stroke-subtle dark:border-white/5 hover:bg-gray-100/50 dark:hover:bg-white/5 transition-colors"},[t("td",_t,[t("div",gt,n(e.public_key),1)]),t("td",ht,[t("div",ft,n(e.address),1)]),t("td",wt,[t("div",Ct,n(e.identity_name),1),t("div",At,n(e.identity_hash),1)]),t("td",Lt,[t("span",{class:i(["px-2 py-1 text-xs font-medium rounded",e.permissions==="admin"?"bg-green-100 dark:bg-green-500/20 dark:bg-accent-green/20 text-green-700 dark:text-accent-green":"bg-yellow-100 dark:bg-yellow-500/20 dark:bg-secondary/20 text-yellow-700 dark:text-secondary"])},n(e.permissions),3)]),t("td",St,[t("div",Mt,n(g(e.last_activity)),1)]),t("td",jt,[t("button",{onClick:L=>C(e.public_key_full,e.identity_hash),class:"px-3 py-1 bg-red-100 dark:bg-red-500/20 dark:bg-accent-red/20 hover:bg-red-500/30 dark:hover:bg-accent-red/30 text-red-600 dark:text-accent-red rounded text-xs transition-colors"}," Remove ",8,It)])]))),128))])])]))])):c.value==="identities"?(r(),o("div",Nt,[t("div",Rt,[s[17]||(s[17]=t("label",{class:"block text-content-secondary dark:text-content-muted text-sm mb-2"},"Filter by Identity",-1)),R(t("select",{"onUpdate:modelValue":s[0]||(s[0]=e=>k.value=e),class:"bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-2 text-content-primary dark:text-content-primary focus:outline-none focus:border-cyan-500 dark:focus:border-primary/50 transition-colors"},[s[16]||(s[16]=t("option",{value:null},"All Identities",-1)),(r(!0),o(m,null,y(h.value,e=>(r(),o("option",{key:e.name,value:e.name},n(e.name)+" ("+n(e.authenticated_clients)+" clients) ",9,Vt))),128))],512),[[V,k.value]])]),A.value.length===0?(r(),o("div",Bt," No clients for selected identity ")):(r(),o("div",zt,[(r(!0),o(m,null,y(A.value,e=>(r(),o("div",{key:e.public_key_full,class:"glass-card rounded-[10px] p-4 border border-stroke-subtle dark:border-white/10"},[t("div",Dt,[t("div",Ft,[t("div",Tt,[t("span",{class:i(["px-2 py-1 text-xs font-medium rounded",e.permissions==="admin"?"bg-green-100 dark:bg-green-500/20 dark:bg-accent-green/20 text-green-700 dark:text-accent-green":"bg-yellow-100 dark:bg-yellow-500/20 dark:bg-secondary/20 text-yellow-700 dark:text-secondary"])},n(e.permissions),3),t("span",$t,n(e.public_key),1)]),t("div",Et,[t("div",null,[s[18]||(s[18]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Address:",-1)),t("span",Ht,n(e.address),1)]),t("div",null,[s[19]||(s[19]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Identity:",-1)),t("span",Pt,n(e.identity_name)+" ("+n(e.identity_hash)+")",1)]),t("div",null,[s[20]||(s[20]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Last Activity:",-1)),t("span",Gt,n(g(e.last_activity)),1)]),t("div",null,[s[21]||(s[21]=t("span",{class:"text-content-secondary dark:text-content-muted"},"Last Login:",-1)),t("span",Ot,n(g(e.last_login_success)),1)])])]),t("button",{onClick:L=>C(e.public_key_full,e.identity_hash),class:"ml-4 px-3 py-1 bg-red-100 dark:bg-red-500/20 dark:bg-accent-red/20 hover:bg-red-500/30 dark:hover:bg-accent-red/30 text-red-600 dark:text-accent-red rounded text-xs transition-colors"}," Remove ",8,qt)])]))),128))]))])):f("",!0)])]),t("div",Ut,[t("button",{onClick:p,disabled:x.value,class:"px-4 py-2 bg-cyan-500/20 dark:bg-primary/20 hover:bg-cyan-500/30 dark:hover:bg-primary/30 text-cyan-900 dark:text-primary rounded-lg border border-cyan-500/50 dark:border-primary/50 transition-colors disabled:opacity-50"},n(x.value?"Refreshing...":"Refresh Data"),9,Jt)])]))}});export{Wt as default}; +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/Sessions-CHLsUNJq.js diff --git a/repeater/web/html/assets/Setup-CLJIlSKT.js b/repeater/web/html/assets/Setup-CLJIlSKT.js index 3ec84b4..8496ba0 100644 --- a/repeater/web/html/assets/Setup-CLJIlSKT.js +++ b/repeater/web/html/assets/Setup-CLJIlSKT.js @@ -1 +1,5 @@ +<<<<<<<< HEAD:repeater/web/html/assets/Setup-CLJIlSKT.js import{d as A,r as l,c as P,a as W,o as I,b as a,e,f as B,_ as Y,t as i,u as o,n as J,g as k,F as N,h as z,i as K,w as h,v as C,j as _,k as V,l as q,T,m as Q,p as n,q as E,s as X,x as Z}from"./index-DyUIpN7m.js";const ee=A("setup",()=>{const m=l(1),r=l(5),y=l(`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`),b=l(null),v=l(null),x=l(""),f=l(""),w=l(!1),c=l({frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"}),R=l([]),j=l([]),g=l(!1),S=l(!1),u=l(null),t=P(()=>{switch(m.value){case 1:return!0;case 2:return y.value.trim().length>0;case 3:return b.value!==null;case 4:return w.value?c.value.frequency&&c.value.spreading_factor&&c.value.bandwidth&&c.value.coding_rate:v.value!==null;case 5:return x.value.length>=6&&x.value===f.value;default:return!1}}),s=P(()=>m.value>1),M=P(()=>m.value===r.value);async function F(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/hardware_options")).json();if(p.error)throw new Error(p.error);R.value=p.hardware||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load hardware options",console.error("Error fetching hardware options:",d)}finally{g.value=!1}}async function H(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/radio_presets")).json();if(p.error)throw new Error(p.error);j.value=p.presets||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load radio presets",console.error("Error fetching radio presets:",d)}finally{g.value=!1}}async function U(){if(!t.value)return{success:!1,error:"Please complete all required fields"};S.value=!0,u.value=null;try{const d=w.value?{title:"Custom Configuration",description:"Custom radio settings",frequency:c.value.frequency,spreading_factor:c.value.spreading_factor,bandwidth:c.value.bandwidth,coding_rate:c.value.coding_rate}:v.value,L=await(await fetch("/api/setup_wizard",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({node_name:y.value.trim(),hardware_key:b.value?.key,radio_preset:d,admin_password:x.value})})).json();if(!L.success)throw new Error(L.error||"Setup failed");return{success:!0,data:L}}catch(d){const p=d instanceof Error?d.message:"Failed to complete setup";return u.value=p,{success:!1,error:p}}finally{S.value=!1}}function O(){t.value&&m.value=1&&d<=r.value&&(m.value=d)}function D(){m.value=1,y.value=`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`,b.value=null,v.value=null,w.value=!1,c.value={frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"},x.value="",f.value="",u.value=null}return{currentStep:m,totalSteps:r,nodeName:y,selectedHardware:b,selectedRadioPreset:v,useCustomRadio:w,customRadio:c,adminPassword:x,confirmPassword:f,hardwareOptions:R,radioPresets:j,isLoading:g,isSubmitting:S,error:u,canGoNext:t,canGoBack:s,isLastStep:M,fetchHardwareOptions:F,fetchRadioPresets:H,completeSetup:U,nextStep:O,previousStep:$,goToStep:G,reset:D}}),te={class:"min-h-screen bg-background dark:bg-background overflow-hidden relative flex items-center justify-center p-4"},re={class:"absolute top-4 right-4 z-20"},oe={class:"w-full max-w-4xl relative z-10"},se={class:"mb-8"},ae={class:"flex justify-between mb-2"},ne={class:"text-content-secondary dark:text-content-muted text-sm"},de={class:"text-content-secondary dark:text-content-muted text-sm"},ie={class:"h-2 bg-stroke-subtle dark:bg-stroke/10 rounded-full overflow-hidden"},le={class:"bg-white dark:bg-surface-elevated backdrop-blur-xl border border-stroke-subtle dark:border-white/10 rounded-[20px] p-6 sm:p-8 md:p-12"},ue={class:"flex justify-center mb-8"},ce={class:"flex gap-2"},pe={class:"mb-8"},me={class:"text-2xl sm:text-3xl font-bold text-content-primary dark:text-content-primary mb-2 text-center"},be={key:0,class:"space-y-6 mt-8"},fe={key:1,class:"space-y-6 mt-8"},xe={class:"max-w-md mx-auto"},ke={key:2,class:"space-y-6 mt-8"},ve={key:0,class:"text-center text-content-secondary dark:text-content-muted"},ge={key:1,class:"text-center text-content-secondary dark:text-content-muted"},ye={key:2,class:"grid grid-cols-1 md:grid-cols-2 gap-4 max-w-3xl mx-auto"},he=["onClick"],we={class:"font-medium text-content-primary dark:text-content-primary mb-1"},_e={class:"text-sm text-content-secondary dark:text-content-muted"},Se={key:3,class:"space-y-6 mt-8"},Ce={key:0,class:"text-center text-content-secondary dark:text-content-muted"},Re={key:1,class:"text-center text-content-secondary dark:text-content-muted"},je={key:2,class:"max-w-5xl mx-auto"},Pe={class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4"},Me=["onClick"],Le={class:"relative z-10"},Be={class:"font-medium text-content-primary dark:text-content-primary mb-1 flex items-start justify-between gap-2"},Ne={class:"flex items-center gap-2"},ze={class:"text-2xl"},Ve={key:0,class:"text-primary flex-shrink-0"},qe={class:"text-xs text-content-secondary dark:text-content-muted mb-3"},Te={class:"grid grid-cols-2 gap-2 text-xs"},Ee={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},Fe={class:"text-content-primary dark:text-content-primary/80 font-medium"},He={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},Ue={class:"text-content-primary dark:text-content-primary/80 font-medium"},Oe={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},$e={class:"text-content-primary dark:text-content-primary/80 font-medium"},Ge={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},De={class:"text-content-primary dark:text-content-primary/80 font-medium"},Ae={class:"border-t border-stroke-subtle dark:border-stroke/10 pt-6"},We={class:"flex items-center justify-between mb-2"},Ie={key:0,class:"text-primary"},Ye={key:0,class:"mt-4 grid grid-cols-2 gap-4"},Je={key:4,class:"space-y-6 mt-8"},Ke={class:"max-w-md mx-auto space-y-4"},Qe={key:0,class:"text-red-600 dark:text-red-400 text-sm"},Xe={key:0,class:"mb-6 bg-red-500/10 border border-red-500/30 rounded-lg p-4 text-red-600 dark:text-red-200"},Ze={class:"flex justify-between gap-4"},et={key:1},tt=["disabled"],rt={key:0,class:"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"},ot={key:1},st={key:2},at={key:3},nt={key:4,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},dt={class:"flex justify-center mb-6"},it={key:0,class:"w-16 h-16 rounded-full bg-green-100 dark:bg-green-500/20 flex items-center justify-center"},lt={key:1,class:"w-16 h-16 rounded-full bg-red-100 dark:bg-red-500/20 flex items-center justify-center"},ut={class:"text-2xl font-bold text-content-primary dark:text-content-primary text-center mb-4"},ct={class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"},pt=W({name:"SetupView",__name:"Setup",setup(m){const r=ee(),y=Q(),b=l(!1),v=l(""),x=l(""),f=l("success"),w=u=>{const t=u.toLowerCase();return t.includes("australia")?"🇦🇺":t.includes("eu")||t.includes("uk")?"🇪🇺":t.includes("czech")?"🇨🇿":t.includes("new zealand")?"🇳🇿":t.includes("portugal")?"🇵🇹":t.includes("switzerland")?"🇨🇭":t.includes("usa")||t.includes("canada")?"🇺🇸":t.includes("vietnam")?"🇻🇳":"🌍"};I(async()=>{await Promise.all([r.fetchHardwareOptions(),r.fetchRadioPresets()])});const c=P(()=>r.currentStep/r.totalSteps*100);async function R(){if(r.isLastStep){const u=await r.completeSetup();u.success?(f.value="success",v.value="Setup Complete!",x.value="Your repeater has been configured successfully. The service is restarting now...",b.value=!0,setTimeout(()=>{b.value=!1,y.push("/login")},5e3)):(f.value="error",v.value="Setup Failed",x.value=u.error||"An unknown error occurred",b.value=!0)}else r.nextStep()}function j(){r.previousStep()}function g(){b.value=!1,f.value==="success"&&y.push("/login")}const S=["Welcome","Repeater Name","Hardware Selection","Radio Configuration","Security Setup"];return(u,t)=>(n(),a("div",te,[e("div",re,[B(Y)]),t[36]||(t[36]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slow -top-[79px] left-[575px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[37]||(t[37]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-75 animate-pulse-slower -top-[94px] -left-[92px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[38]||(t[38]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slowest top-[373px] left-[246px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),e("div",oe,[e("div",se,[e("div",ae,[e("span",ne,"Step "+i(o(r).currentStep)+" of "+i(o(r).totalSteps),1),e("span",de,i(Math.round(c.value))+"% Complete",1)]),e("div",ie,[e("div",{class:"h-full bg-gradient-to-r from-primary to-primary/80 transition-all duration-500",style:J({width:`${c.value}%`})},null,4)])]),e("div",le,[e("div",ue,[e("div",ce,[(n(!0),a(N,null,z(o(r).totalSteps,s=>(n(),a("div",{key:s,class:_(["w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium transition-all",s===o(r).currentStep?"bg-primary text-white":s

Welcome to your pyMC Repeater! Let's get you set up in just a few steps.

You'll configure:

  • Repeater name and identification
  • Hardware board selection
  • Radio frequency and settings
  • Admin password for secure access
',1)]))):o(r).currentStep===2?(n(),a("div",fe,[t[12]||(t[12]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Choose a unique name for your repeater. This will be used for identification on the mesh network. ",-1)),e("div",xe,[t[10]||(t[10]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Repeater Name",-1)),h(e("input",{"onUpdate:modelValue":t[0]||(t[0]=s=>o(r).nodeName=s),type:"text",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-3 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",placeholder:"e.g., pyRpt0001",maxlength:"32"},null,512),[[C,o(r).nodeName]]),t[11]||(t[11]=e("p",{class:"text-content-secondary dark:text-content-muted text-xs mt-2"}," Use letters, numbers, hyphens, or underscores (3-32 characters) ",-1))])])):o(r).currentStep===3?(n(),a("div",ke,[t[13]||(t[13]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Select your hardware board type ",-1)),o(r).isLoading?(n(),a("div",ve," Loading hardware options... ")):o(r).hardwareOptions.length===0?(n(),a("div",ge," No hardware options available ")):(n(),a("div",ye,[(n(!0),a(N,null,z(o(r).hardwareOptions,s=>(n(),a("button",{key:s.key,onClick:M=>o(r).selectedHardware=s,class:_(["p-4 rounded-[12px] border transition-all duration-300 text-left backdrop-blur-sm",o(r).selectedHardware?.key===s.key?"bg-gradient-to-r from-primary/20 to-primary/10 border-primary/50 shadow-lg shadow-primary/20":"bg-background-mute dark:bg-white/5 border-stroke-subtle dark:border-stroke/10 hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20"])},[e("div",we,i(s.name),1),e("div",_e,i(s.description||s.key),1)],10,he))),128))]))])):o(r).currentStep===4?(n(),a("div",Se,[t[28]||(t[28]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Choose a radio configuration preset for your region or create a custom configuration ",-1)),o(r).isLoading?(n(),a("div",Ce," Loading radio presets... ")):o(r).radioPresets.length===0?(n(),a("div",Re," No radio presets available ")):(n(),a("div",je,[e("div",Pe,[(n(!0),a(N,null,z(o(r).radioPresets,s=>(n(),a("button",{key:s.title,onClick:M=>{o(r).selectedRadioPreset=s,o(r).useCustomRadio=!1},class:_(["p-4 rounded-[12px] border transition-all duration-300 text-left backdrop-blur-sm relative overflow-hidden",!o(r).useCustomRadio&&o(r).selectedRadioPreset?.title===s.title?"bg-gradient-to-r from-primary/20 to-primary/10 border-primary/50 shadow-lg shadow-primary/20":"bg-background-mute dark:bg-white/5 border-stroke-subtle dark:border-stroke/10 hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20"])},[e("div",Le,[e("div",Be,[e("span",Ne,[e("span",ze,i(w(s.title)),1),e("span",null,i(s.title),1)]),!o(r).useCustomRadio&&o(r).selectedRadioPreset?.title===s.title?(n(),a("div",Ve,t[14]||(t[14]=[e("svg",{class:"w-5 h-5",fill:"currentColor",viewBox:"0 0 20 20"},[e("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z","clip-rule":"evenodd"})],-1)]))):k("",!0)]),e("div",qe,i(s.description),1),e("div",Te,[e("div",Ee,[t[15]||(t[15]=e("div",{class:"text-content-muted dark:text-content-muted"},"Freq",-1)),e("div",Fe,i(s.frequency),1)]),e("div",He,[t[16]||(t[16]=e("div",{class:"text-content-muted dark:text-content-muted"},"BW",-1)),e("div",Ue,i(s.bandwidth),1)]),e("div",Oe,[t[17]||(t[17]=e("div",{class:"text-content-muted dark:text-content-muted"},"SF",-1)),e("div",$e,i(s.spreading_factor),1)]),e("div",Ge,[t[18]||(t[18]=e("div",{class:"text-content-muted dark:text-content-muted"},"CR",-1)),e("div",De,i(s.coding_rate),1)])])])],10,Me))),128))]),e("div",Ae,[e("button",{onClick:t[1]||(t[1]=s=>{o(r).useCustomRadio=!o(r).useCustomRadio,o(r).useCustomRadio&&(o(r).selectedRadioPreset=null)}),class:_(["w-full p-4 rounded-[12px] border transition-all duration-300 text-left backdrop-blur-sm",o(r).useCustomRadio?"bg-gradient-to-r from-primary/20 to-primary/10 border-primary/50 shadow-lg shadow-primary/20":"bg-background-mute dark:bg-white/5 border-stroke-subtle dark:border-stroke/10 hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20"])},[e("div",We,[t[20]||(t[20]=e("div",{class:"font-medium text-content-primary dark:text-content-primary flex items-center gap-2"},[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"})]),V(" Custom Configuration ")],-1)),o(r).useCustomRadio?(n(),a("div",Ie,t[19]||(t[19]=[e("svg",{class:"w-5 h-5",fill:"currentColor",viewBox:"0 0 20 20"},[e("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z","clip-rule":"evenodd"})],-1)]))):k("",!0)]),t[21]||(t[21]=e("div",{class:"text-xs text-content-secondary dark:text-content-muted"},"Manually configure frequency, bandwidth, spreading factor, and coding rate",-1))],2),B(T,{name:"slide"},{default:q(()=>[o(r).useCustomRadio?(n(),a("div",Ye,[e("div",null,[t[22]||(t[22]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Frequency (MHz)",-1)),h(e("input",{"onUpdate:modelValue":t[2]||(t[2]=s=>o(r).customRadio.frequency=s),type:"number",step:"0.1",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all",placeholder:"915.0"},null,512),[[C,o(r).customRadio.frequency]])]),e("div",null,[t[23]||(t[23]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Bandwidth (kHz)",-1)),h(e("input",{"onUpdate:modelValue":t[3]||(t[3]=s=>o(r).customRadio.bandwidth=s),type:"number",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all",placeholder:"125"},null,512),[[C,o(r).customRadio.bandwidth]])]),e("div",null,[t[25]||(t[25]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Spreading Factor",-1)),h(e("select",{"onUpdate:modelValue":t[4]||(t[4]=s=>o(r).customRadio.spreading_factor=s),class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all"},t[24]||(t[24]=[e("option",{value:"7"},"7",-1),e("option",{value:"8"},"8",-1),e("option",{value:"9"},"9",-1),e("option",{value:"10"},"10",-1),e("option",{value:"11"},"11",-1),e("option",{value:"12"},"12",-1)]),512),[[E,o(r).customRadio.spreading_factor]])]),e("div",null,[t[27]||(t[27]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Coding Rate",-1)),h(e("select",{"onUpdate:modelValue":t[5]||(t[5]=s=>o(r).customRadio.coding_rate=s),class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all"},t[26]||(t[26]=[e("option",{value:"5"},"4/5",-1),e("option",{value:"6"},"4/6",-1),e("option",{value:"7"},"4/7",-1),e("option",{value:"8"},"4/8",-1)]),512),[[E,o(r).customRadio.coding_rate]])])])):k("",!0)]),_:1})])]))])):o(r).currentStep===5?(n(),a("div",Je,[t[32]||(t[32]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Set a secure admin password to protect your repeater ",-1)),e("div",Ke,[e("div",null,[t[29]||(t[29]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Admin Password",-1)),h(e("input",{"onUpdate:modelValue":t[6]||(t[6]=s=>o(r).adminPassword=s),type:"password",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-3 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",placeholder:"Enter password (min 6 characters)",minlength:"6"},null,512),[[C,o(r).adminPassword]])]),e("div",null,[t[30]||(t[30]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Confirm Password",-1)),h(e("input",{"onUpdate:modelValue":t[7]||(t[7]=s=>o(r).confirmPassword=s),type:"password",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-3 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",placeholder:"Confirm password"},null,512),[[C,o(r).confirmPassword]])]),o(r).adminPassword&&o(r).confirmPassword&&o(r).adminPassword!==o(r).confirmPassword?(n(),a("div",Qe," Passwords do not match ")):k("",!0),t[31]||(t[31]=e("div",{class:"bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-3 text-sm text-yellow-800 dark:text-yellow-200"},[e("strong",null,"Important:"),V(" Remember this password - you'll need it to access the dashboard. ")],-1))])])):k("",!0)]),o(r).error?(n(),a("div",Xe,i(o(r).error),1)):k("",!0),e("div",Ze,[o(r).canGoBack?(n(),a("button",{key:0,onClick:j,class:"px-6 py-3 rounded-[12px] bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 text-content-primary dark:text-content-primary hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20 transition-all duration-300 font-medium"}," Back ")):(n(),a("div",et)),e("button",{onClick:R,disabled:!o(r).canGoNext||o(r).isSubmitting,class:_(["px-8 py-3 rounded-[12px] font-semibold transition-all duration-300 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",o(r).canGoNext&&!o(r).isSubmitting?"bg-gradient-to-r from-primary/20 to-primary/10 hover:from-primary/30 hover:to-primary/20 text-white border border-primary/30 hover:border-primary/50":"bg-background-mute dark:bg-stroke/5 text-content-muted dark:text-content-muted border border-stroke-subtle dark:border-stroke/10"])},[o(r).isSubmitting?(n(),a("div",rt)):k("",!0),o(r).isSubmitting?(n(),a("span",ot,"Setting up...")):o(r).isLastStep?(n(),a("span",st,"Complete Setup")):(n(),a("span",at,"Next")),!o(r).isSubmitting&&!o(r).isLastStep?(n(),a("svg",nt,t[33]||(t[33]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5l7 7-7 7"},null,-1)]))):k("",!0)],10,tt)])])]),B(T,{name:"modal"},{default:q(()=>[b.value?(n(),a("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm",onClick:g},[e("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl max-w-md w-full p-8 rounded-[24px] border border-stroke-subtle dark:border-white/20 shadow-[0_8px_32px_0_rgba(0,0,0,0.37)]",onClick:t[8]||(t[8]=X(()=>{},["stop"]))},[e("div",dt,[f.value==="success"?(n(),a("div",it,t[34]||(t[34]=[e("svg",{class:"w-8 h-8 text-green-600 dark:text-green-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 13l4 4L19 7"})],-1)]))):(n(),a("div",lt,t[35]||(t[35]=[e("svg",{class:"w-8 h-8 text-red-600 dark:text-red-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)])))]),e("h3",ut,i(v.value),1),e("p",ct,i(x.value),1),e("button",{onClick:g,class:_(["w-full px-6 py-3 rounded-lg font-medium transition-all",f.value==="success"?"bg-gradient-to-r from-primary/20 to-primary/10 hover:from-primary/30 hover:to-primary/20 text-white":"bg-gradient-to-r from-red-500/20 to-red-500/10 hover:from-red-500/30 hover:to-red-500/20 text-white"])},i(f.value==="success"?"Continue to Login":"Close"),3)])])):k("",!0)]),_:1})]))}}),bt=Z(pt,[["__scopeId","data-v-20a8772f"]]);export{bt as default}; +======== +import{d as A,r as l,c as P,a as W,o as I,b as a,e,f as B,_ as Y,t as i,u as o,n as J,g as k,F as N,h as z,i as K,w as h,v as C,j as _,k as V,l as q,T,m as Q,p as n,q as E,s as X,x as Z}from"./index-BfUIlcDy.js";const ee=A("setup",()=>{const m=l(1),r=l(5),y=l(`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`),b=l(null),v=l(null),x=l(""),f=l(""),w=l(!1),c=l({frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"}),R=l([]),j=l([]),g=l(!1),S=l(!1),u=l(null),t=P(()=>{switch(m.value){case 1:return!0;case 2:return y.value.trim().length>0;case 3:return b.value!==null;case 4:return w.value?c.value.frequency&&c.value.spreading_factor&&c.value.bandwidth&&c.value.coding_rate:v.value!==null;case 5:return x.value.length>=6&&x.value===f.value;default:return!1}}),s=P(()=>m.value>1),M=P(()=>m.value===r.value);async function F(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/hardware_options")).json();if(p.error)throw new Error(p.error);R.value=p.hardware||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load hardware options",console.error("Error fetching hardware options:",d)}finally{g.value=!1}}async function H(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/radio_presets")).json();if(p.error)throw new Error(p.error);j.value=p.presets||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load radio presets",console.error("Error fetching radio presets:",d)}finally{g.value=!1}}async function U(){if(!t.value)return{success:!1,error:"Please complete all required fields"};S.value=!0,u.value=null;try{const d=w.value?{title:"Custom Configuration",description:"Custom radio settings",frequency:c.value.frequency,spreading_factor:c.value.spreading_factor,bandwidth:c.value.bandwidth,coding_rate:c.value.coding_rate}:v.value,L=await(await fetch("/api/setup_wizard",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({node_name:y.value.trim(),hardware_key:b.value?.key,radio_preset:d,admin_password:x.value})})).json();if(!L.success)throw new Error(L.error||"Setup failed");return{success:!0,data:L}}catch(d){const p=d instanceof Error?d.message:"Failed to complete setup";return u.value=p,{success:!1,error:p}}finally{S.value=!1}}function O(){t.value&&m.value=1&&d<=r.value&&(m.value=d)}function D(){m.value=1,y.value=`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`,b.value=null,v.value=null,w.value=!1,c.value={frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"},x.value="",f.value="",u.value=null}return{currentStep:m,totalSteps:r,nodeName:y,selectedHardware:b,selectedRadioPreset:v,useCustomRadio:w,customRadio:c,adminPassword:x,confirmPassword:f,hardwareOptions:R,radioPresets:j,isLoading:g,isSubmitting:S,error:u,canGoNext:t,canGoBack:s,isLastStep:M,fetchHardwareOptions:F,fetchRadioPresets:H,completeSetup:U,nextStep:O,previousStep:$,goToStep:G,reset:D}}),te={class:"min-h-screen bg-background dark:bg-background overflow-hidden relative flex items-center justify-center p-4"},re={class:"absolute top-4 right-4 z-20"},oe={class:"w-full max-w-4xl relative z-10"},se={class:"mb-8"},ae={class:"flex justify-between mb-2"},ne={class:"text-content-secondary dark:text-content-muted text-sm"},de={class:"text-content-secondary dark:text-content-muted text-sm"},ie={class:"h-2 bg-stroke-subtle dark:bg-stroke/10 rounded-full overflow-hidden"},le={class:"bg-white dark:bg-surface-elevated backdrop-blur-xl border border-stroke-subtle dark:border-white/10 rounded-[20px] p-6 sm:p-8 md:p-12"},ue={class:"flex justify-center mb-8"},ce={class:"flex gap-2"},pe={class:"mb-8"},me={class:"text-2xl sm:text-3xl font-bold text-content-primary dark:text-content-primary mb-2 text-center"},be={key:0,class:"space-y-6 mt-8"},fe={key:1,class:"space-y-6 mt-8"},xe={class:"max-w-md mx-auto"},ke={key:2,class:"space-y-6 mt-8"},ve={key:0,class:"text-center text-content-secondary dark:text-content-muted"},ge={key:1,class:"text-center text-content-secondary dark:text-content-muted"},ye={key:2,class:"grid grid-cols-1 md:grid-cols-2 gap-4 max-w-3xl mx-auto"},he=["onClick"],we={class:"font-medium text-content-primary dark:text-content-primary mb-1"},_e={class:"text-sm text-content-secondary dark:text-content-muted"},Se={key:3,class:"space-y-6 mt-8"},Ce={key:0,class:"text-center text-content-secondary dark:text-content-muted"},Re={key:1,class:"text-center text-content-secondary dark:text-content-muted"},je={key:2,class:"max-w-5xl mx-auto"},Pe={class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4"},Me=["onClick"],Le={class:"relative z-10"},Be={class:"font-medium text-content-primary dark:text-content-primary mb-1 flex items-start justify-between gap-2"},Ne={class:"flex items-center gap-2"},ze={class:"text-2xl"},Ve={key:0,class:"text-primary flex-shrink-0"},qe={class:"text-xs text-content-secondary dark:text-content-muted mb-3"},Te={class:"grid grid-cols-2 gap-2 text-xs"},Ee={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},Fe={class:"text-content-primary dark:text-content-primary/80 font-medium"},He={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},Ue={class:"text-content-primary dark:text-content-primary/80 font-medium"},Oe={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},$e={class:"text-content-primary dark:text-content-primary/80 font-medium"},Ge={class:"bg-gray-50 dark:bg-white/5 rounded px-2 py-1"},De={class:"text-content-primary dark:text-content-primary/80 font-medium"},Ae={class:"border-t border-stroke-subtle dark:border-stroke/10 pt-6"},We={class:"flex items-center justify-between mb-2"},Ie={key:0,class:"text-primary"},Ye={key:0,class:"mt-4 grid grid-cols-2 gap-4"},Je={key:4,class:"space-y-6 mt-8"},Ke={class:"max-w-md mx-auto space-y-4"},Qe={key:0,class:"text-red-600 dark:text-red-400 text-sm"},Xe={key:0,class:"mb-6 bg-red-500/10 border border-red-500/30 rounded-lg p-4 text-red-600 dark:text-red-200"},Ze={class:"flex justify-between gap-4"},et={key:1},tt=["disabled"],rt={key:0,class:"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"},ot={key:1},st={key:2},at={key:3},nt={key:4,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},dt={class:"flex justify-center mb-6"},it={key:0,class:"w-16 h-16 rounded-full bg-green-100 dark:bg-green-500/20 flex items-center justify-center"},lt={key:1,class:"w-16 h-16 rounded-full bg-red-100 dark:bg-red-500/20 flex items-center justify-center"},ut={class:"text-2xl font-bold text-content-primary dark:text-content-primary text-center mb-4"},ct={class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"},pt=W({name:"SetupView",__name:"Setup",setup(m){const r=ee(),y=Q(),b=l(!1),v=l(""),x=l(""),f=l("success"),w=u=>{const t=u.toLowerCase();return t.includes("australia")?"🇦🇺":t.includes("eu")||t.includes("uk")?"🇪🇺":t.includes("czech")?"🇨🇿":t.includes("new zealand")?"🇳🇿":t.includes("portugal")?"🇵🇹":t.includes("switzerland")?"🇨🇭":t.includes("usa")||t.includes("canada")?"🇺🇸":t.includes("vietnam")?"🇻🇳":"🌍"};I(async()=>{await Promise.all([r.fetchHardwareOptions(),r.fetchRadioPresets()])});const c=P(()=>r.currentStep/r.totalSteps*100);async function R(){if(r.isLastStep){const u=await r.completeSetup();u.success?(f.value="success",v.value="Setup Complete!",x.value="Your repeater has been configured successfully. The service is restarting now...",b.value=!0,setTimeout(()=>{b.value=!1,y.push("/login")},5e3)):(f.value="error",v.value="Setup Failed",x.value=u.error||"An unknown error occurred",b.value=!0)}else r.nextStep()}function j(){r.previousStep()}function g(){b.value=!1,f.value==="success"&&y.push("/login")}const S=["Welcome","Repeater Name","Hardware Selection","Radio Configuration","Security Setup"];return(u,t)=>(n(),a("div",te,[e("div",re,[B(Y)]),t[36]||(t[36]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slow -top-[79px] left-[575px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[37]||(t[37]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-75 animate-pulse-slower -top-[94px] -left-[92px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),t[38]||(t[38]=e("div",{class:"bg-gradient-light dark:bg-gradient-dark absolute rounded-full -rotate-[24.22deg] w-[705px] h-[512px] blur-[120px] opacity-80 animate-pulse-slowest top-[373px] left-[246px] mix-blend-multiply dark:mix-blend-screen pointer-events-none"},null,-1)),e("div",oe,[e("div",se,[e("div",ae,[e("span",ne,"Step "+i(o(r).currentStep)+" of "+i(o(r).totalSteps),1),e("span",de,i(Math.round(c.value))+"% Complete",1)]),e("div",ie,[e("div",{class:"h-full bg-gradient-to-r from-primary to-primary/80 transition-all duration-500",style:J({width:`${c.value}%`})},null,4)])]),e("div",le,[e("div",ue,[e("div",ce,[(n(!0),a(N,null,z(o(r).totalSteps,s=>(n(),a("div",{key:s,class:_(["w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium transition-all",s===o(r).currentStep?"bg-primary text-white":s

Welcome to your pyMC Repeater! Let's get you set up in just a few steps.

You'll configure:

  • Repeater name and identification
  • Hardware board selection
  • Radio frequency and settings
  • Admin password for secure access
',1)]))):o(r).currentStep===2?(n(),a("div",fe,[t[12]||(t[12]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Choose a unique name for your repeater. This will be used for identification on the mesh network. ",-1)),e("div",xe,[t[10]||(t[10]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Repeater Name",-1)),h(e("input",{"onUpdate:modelValue":t[0]||(t[0]=s=>o(r).nodeName=s),type:"text",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-3 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",placeholder:"e.g., pyRpt0001",maxlength:"32"},null,512),[[C,o(r).nodeName]]),t[11]||(t[11]=e("p",{class:"text-content-secondary dark:text-content-muted text-xs mt-2"}," Use letters, numbers, hyphens, or underscores (3-32 characters) ",-1))])])):o(r).currentStep===3?(n(),a("div",ke,[t[13]||(t[13]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Select your hardware board type ",-1)),o(r).isLoading?(n(),a("div",ve," Loading hardware options... ")):o(r).hardwareOptions.length===0?(n(),a("div",ge," No hardware options available ")):(n(),a("div",ye,[(n(!0),a(N,null,z(o(r).hardwareOptions,s=>(n(),a("button",{key:s.key,onClick:M=>o(r).selectedHardware=s,class:_(["p-4 rounded-[12px] border transition-all duration-300 text-left backdrop-blur-sm",o(r).selectedHardware?.key===s.key?"bg-gradient-to-r from-primary/20 to-primary/10 border-primary/50 shadow-lg shadow-primary/20":"bg-background-mute dark:bg-white/5 border-stroke-subtle dark:border-stroke/10 hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20"])},[e("div",we,i(s.name),1),e("div",_e,i(s.description||s.key),1)],10,he))),128))]))])):o(r).currentStep===4?(n(),a("div",Se,[t[28]||(t[28]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Choose a radio configuration preset for your region or create a custom configuration ",-1)),o(r).isLoading?(n(),a("div",Ce," Loading radio presets... ")):o(r).radioPresets.length===0?(n(),a("div",Re," No radio presets available ")):(n(),a("div",je,[e("div",Pe,[(n(!0),a(N,null,z(o(r).radioPresets,s=>(n(),a("button",{key:s.title,onClick:M=>{o(r).selectedRadioPreset=s,o(r).useCustomRadio=!1},class:_(["p-4 rounded-[12px] border transition-all duration-300 text-left backdrop-blur-sm relative overflow-hidden",!o(r).useCustomRadio&&o(r).selectedRadioPreset?.title===s.title?"bg-gradient-to-r from-primary/20 to-primary/10 border-primary/50 shadow-lg shadow-primary/20":"bg-background-mute dark:bg-white/5 border-stroke-subtle dark:border-stroke/10 hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20"])},[e("div",Le,[e("div",Be,[e("span",Ne,[e("span",ze,i(w(s.title)),1),e("span",null,i(s.title),1)]),!o(r).useCustomRadio&&o(r).selectedRadioPreset?.title===s.title?(n(),a("div",Ve,t[14]||(t[14]=[e("svg",{class:"w-5 h-5",fill:"currentColor",viewBox:"0 0 20 20"},[e("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z","clip-rule":"evenodd"})],-1)]))):k("",!0)]),e("div",qe,i(s.description),1),e("div",Te,[e("div",Ee,[t[15]||(t[15]=e("div",{class:"text-content-muted dark:text-content-muted"},"Freq",-1)),e("div",Fe,i(s.frequency),1)]),e("div",He,[t[16]||(t[16]=e("div",{class:"text-content-muted dark:text-content-muted"},"BW",-1)),e("div",Ue,i(s.bandwidth),1)]),e("div",Oe,[t[17]||(t[17]=e("div",{class:"text-content-muted dark:text-content-muted"},"SF",-1)),e("div",$e,i(s.spreading_factor),1)]),e("div",Ge,[t[18]||(t[18]=e("div",{class:"text-content-muted dark:text-content-muted"},"CR",-1)),e("div",De,i(s.coding_rate),1)])])])],10,Me))),128))]),e("div",Ae,[e("button",{onClick:t[1]||(t[1]=s=>{o(r).useCustomRadio=!o(r).useCustomRadio,o(r).useCustomRadio&&(o(r).selectedRadioPreset=null)}),class:_(["w-full p-4 rounded-[12px] border transition-all duration-300 text-left backdrop-blur-sm",o(r).useCustomRadio?"bg-gradient-to-r from-primary/20 to-primary/10 border-primary/50 shadow-lg shadow-primary/20":"bg-background-mute dark:bg-white/5 border-stroke-subtle dark:border-stroke/10 hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20"])},[e("div",We,[t[20]||(t[20]=e("div",{class:"font-medium text-content-primary dark:text-content-primary flex items-center gap-2"},[e("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"})]),V(" Custom Configuration ")],-1)),o(r).useCustomRadio?(n(),a("div",Ie,t[19]||(t[19]=[e("svg",{class:"w-5 h-5",fill:"currentColor",viewBox:"0 0 20 20"},[e("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z","clip-rule":"evenodd"})],-1)]))):k("",!0)]),t[21]||(t[21]=e("div",{class:"text-xs text-content-secondary dark:text-content-muted"},"Manually configure frequency, bandwidth, spreading factor, and coding rate",-1))],2),B(T,{name:"slide"},{default:q(()=>[o(r).useCustomRadio?(n(),a("div",Ye,[e("div",null,[t[22]||(t[22]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Frequency (MHz)",-1)),h(e("input",{"onUpdate:modelValue":t[2]||(t[2]=s=>o(r).customRadio.frequency=s),type:"number",step:"0.1",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all",placeholder:"915.0"},null,512),[[C,o(r).customRadio.frequency]])]),e("div",null,[t[23]||(t[23]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Bandwidth (kHz)",-1)),h(e("input",{"onUpdate:modelValue":t[3]||(t[3]=s=>o(r).customRadio.bandwidth=s),type:"number",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all",placeholder:"125"},null,512),[[C,o(r).customRadio.bandwidth]])]),e("div",null,[t[25]||(t[25]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Spreading Factor",-1)),h(e("select",{"onUpdate:modelValue":t[4]||(t[4]=s=>o(r).customRadio.spreading_factor=s),class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all"},t[24]||(t[24]=[e("option",{value:"7"},"7",-1),e("option",{value:"8"},"8",-1),e("option",{value:"9"},"9",-1),e("option",{value:"10"},"10",-1),e("option",{value:"11"},"11",-1),e("option",{value:"12"},"12",-1)]),512),[[E,o(r).customRadio.spreading_factor]])]),e("div",null,[t[27]||(t[27]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Coding Rate",-1)),h(e("select",{"onUpdate:modelValue":t[5]||(t[5]=s=>o(r).customRadio.coding_rate=s),class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-[12px] px-4 py-2.5 text-content-primary dark:text-content-primary focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all"},t[26]||(t[26]=[e("option",{value:"5"},"4/5",-1),e("option",{value:"6"},"4/6",-1),e("option",{value:"7"},"4/7",-1),e("option",{value:"8"},"4/8",-1)]),512),[[E,o(r).customRadio.coding_rate]])])])):k("",!0)]),_:1})])]))])):o(r).currentStep===5?(n(),a("div",Je,[t[32]||(t[32]=e("p",{class:"text-content-secondary dark:text-content-primary/70 text-center mb-6"}," Set a secure admin password to protect your repeater ",-1)),e("div",Ke,[e("div",null,[t[29]||(t[29]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Admin Password",-1)),h(e("input",{"onUpdate:modelValue":t[6]||(t[6]=s=>o(r).adminPassword=s),type:"password",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-3 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",placeholder:"Enter password (min 6 characters)",minlength:"6"},null,512),[[C,o(r).adminPassword]])]),e("div",null,[t[30]||(t[30]=e("label",{class:"block text-content-primary dark:text-content-primary/90 text-sm font-medium mb-2"},"Confirm Password",-1)),h(e("input",{"onUpdate:modelValue":t[7]||(t[7]=s=>o(r).confirmPassword=s),type:"password",class:"w-full bg-white dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 rounded-lg px-4 py-3 text-content-primary dark:text-content-primary placeholder-gray-500 dark:placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",placeholder:"Confirm password"},null,512),[[C,o(r).confirmPassword]])]),o(r).adminPassword&&o(r).confirmPassword&&o(r).adminPassword!==o(r).confirmPassword?(n(),a("div",Qe," Passwords do not match ")):k("",!0),t[31]||(t[31]=e("div",{class:"bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-3 text-sm text-yellow-800 dark:text-yellow-200"},[e("strong",null,"Important:"),V(" Remember this password - you'll need it to access the dashboard. ")],-1))])])):k("",!0)]),o(r).error?(n(),a("div",Xe,i(o(r).error),1)):k("",!0),e("div",Ze,[o(r).canGoBack?(n(),a("button",{key:0,onClick:j,class:"px-6 py-3 rounded-[12px] bg-background-mute dark:bg-white/5 border border-stroke-subtle dark:border-stroke/10 text-content-primary dark:text-content-primary hover:bg-stroke-subtle dark:hover:bg-white/10 hover:border-stroke dark:hover:border-stroke/20 transition-all duration-300 font-medium"}," Back ")):(n(),a("div",et)),e("button",{onClick:R,disabled:!o(r).canGoNext||o(r).isSubmitting,class:_(["px-8 py-3 rounded-[12px] font-semibold transition-all duration-300 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",o(r).canGoNext&&!o(r).isSubmitting?"bg-gradient-to-r from-primary/20 to-primary/10 hover:from-primary/30 hover:to-primary/20 text-white border border-primary/30 hover:border-primary/50":"bg-background-mute dark:bg-stroke/5 text-content-muted dark:text-content-muted border border-stroke-subtle dark:border-stroke/10"])},[o(r).isSubmitting?(n(),a("div",rt)):k("",!0),o(r).isSubmitting?(n(),a("span",ot,"Setting up...")):o(r).isLastStep?(n(),a("span",st,"Complete Setup")):(n(),a("span",at,"Next")),!o(r).isSubmitting&&!o(r).isLastStep?(n(),a("svg",nt,t[33]||(t[33]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5l7 7-7 7"},null,-1)]))):k("",!0)],10,tt)])])]),B(T,{name:"modal"},{default:q(()=>[b.value?(n(),a("div",{key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm",onClick:g},[e("div",{class:"bg-white dark:bg-surface-elevated backdrop-blur-xl max-w-md w-full p-8 rounded-[24px] border border-stroke-subtle dark:border-white/20 shadow-[0_8px_32px_0_rgba(0,0,0,0.37)]",onClick:t[8]||(t[8]=X(()=>{},["stop"]))},[e("div",dt,[f.value==="success"?(n(),a("div",it,t[34]||(t[34]=[e("svg",{class:"w-8 h-8 text-green-600 dark:text-green-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 13l4 4L19 7"})],-1)]))):(n(),a("div",lt,t[35]||(t[35]=[e("svg",{class:"w-8 h-8 text-red-600 dark:text-red-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)])))]),e("h3",ut,i(v.value),1),e("p",ct,i(x.value),1),e("button",{onClick:g,class:_(["w-full px-6 py-3 rounded-lg font-medium transition-all",f.value==="success"?"bg-gradient-to-r from-primary/20 to-primary/10 hover:from-primary/30 hover:to-primary/20 text-white":"bg-gradient-to-r from-red-500/20 to-red-500/10 hover:from-red-500/30 hover:to-red-500/20 text-white"])},i(f.value==="success"?"Continue to Login":"Close"),3)])])):k("",!0)]),_:1})]))}}),bt=Z(pt,[["__scopeId","data-v-20a8772f"]]);export{bt as default}; +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/Setup-y7L83LCe.js diff --git a/repeater/web/html/assets/Statistics-CtRs0-I6.js b/repeater/web/html/assets/Statistics-CtRs0-I6.js new file mode 100644 index 0000000..94e71d4 --- /dev/null +++ b/repeater/web/html/assets/Statistics-CtRs0-I6.js @@ -0,0 +1 @@ +import{a as Oe,J as Le,K as ze,r as u,D as pe,c as me,o as He,E as ve,R as M,H as Je,b as h,e as a,g as B,w as Ue,q as je,F as ge,h as fe,f as G,u as xe,i as $e,t as Y,L as H,I as le,n as Ke,p as k,x as Ve}from"./index-BfUIlcDy.js";import{S as Z}from"./chartjs-adapter-date-fns.esm-Do2apMon.js";import{g as Ie,s as Xe}from"./preferences-DtwbSSgO.js";import{C as q,a as We,L as Ge,P as Ye,b as Ze,c as qe,B as Qe,D as et,S as tt,p as at,d as st,e as rt,A as ot,f as lt,i as nt,T as it}from"./chart-B185MtDy.js";import{P as J}from"./plotly.min-DO11Gp-n.js";import"./_commonjsHelpers-CqkleIqs.js";const ct={class:"p-3 sm:p-6 space-y-4 sm:space-y-6"},dt={class:"flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3"},ut={class:"flex items-center gap-2 sm:gap-3"},pt=["value"],mt={class:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"},vt={class:"glass-card rounded-[15px] p-3 sm:p-6"},gt={class:"relative h-40 sm:h-48 rounded-lg p-2 sm:p-4"},ft={key:0,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 backdrop-blur-xs z-20"},xt={key:1,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 z-20"},bt={class:"grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6 items-stretch"},yt={class:"glass-card rounded-[15px] p-3 sm:p-6 flex flex-col"},ht={class:"relative flex-1 min-h-[12rem] sm:min-h-[16rem] rounded-lg"},kt={key:0,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 backdrop-blur-xs z-20"},Ct={class:"glass-card rounded-[15px] p-3 sm:p-6 flex flex-col"},_t={class:"flex-1 flex flex-col justify-evenly"},wt={key:0,class:"flex items-center justify-center flex-1"},St={key:1,class:"flex items-center justify-center flex-1"},Tt={class:"w-28 sm:w-32 text-sm text-content-primary dark:text-content-primary truncate"},Rt={class:"flex-1 h-12 bg-background-mute dark:bg-stroke/10 rounded overflow-hidden"},Dt={class:"w-20 text-sm text-content-secondary dark:text-content-muted text-right tabular-nums"},Et={key:0,class:"glass-card rounded-[15px] p-6 sm:p-8 text-center"},Ft={key:1,class:"glass-card rounded-[15px] p-6 sm:p-8 text-center"},Mt={class:"text-content-secondary dark:text-content-muted text-sm"},Pt=Oe({name:"StatisticsView",__name:"Statistics",setup(Bt){q.register(We,Ge,Ye,Ze,qe,Qe,et,tt,at,st,rt,ot,lt,nt,it);const A=Le(),Q=ze(),N=u(null),ee=u(!1),ne=()=>{N.value||Q.isConnected||(N.value=window.setInterval(X,3e4))},ie=()=>{N.value&&(clearInterval(N.value),N.value=null)},T=()=>{const e=document.documentElement.classList.contains("dark");return{gridColor:e?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)",tickColor:e?"rgba(255, 255, 255, 0.7)":"rgba(0, 0, 0, 0.7)",legendColor:e?"rgba(255, 255, 255, 0.8)":"rgba(0, 0, 0, 0.8)",titleColor:e?"rgba(255, 255, 255, 0.8)":"rgba(0, 0, 0, 0.8)"}},v=u(Ie("statistics_selectedHours",24)),be=[{value:1,label:"1 Hour"},{value:6,label:"6 Hours"},{value:12,label:"12 Hours"},{value:24,label:"24 Hours"},{value:48,label:"2 Days"},{value:168,label:"1 Week"}];pe(v,e=>Xe("statistics_selectedHours",e));const R=u(null),O=u(null),U=u([]),D=u(null),te=u([]),j=u([]),$=u(!0),K=u(null),C=u({packetRate:!0,packetType:!0,noiseFloor:!1,routePie:!0,sparklines:!0}),L=u(!1),V=u(!1),z=u(!1),_=u(null),w=u(null),y=u(null),I=u(null),ae=u(null),se=u(null),E=u(null),ce=me(()=>{const e=A.packetStats;return e?{totalRx:e.total_packets||0,totalTx:e.transmitted_packets||0}:{totalRx:0,totalTx:0}}),re=(e,t)=>{if(e.length===0)return[];const o=Math.round(t*60*60*1e3/72),r=new Map;return e.forEach(([i,g])=>{let f=i;i>1e15?f=i/1e3:i>1e9&&i<1e12&&(f=i*1e3);const S=Math.floor(f/o)*o;r.has(S)||r.set(S,[]),r.get(S).push(g)}),Array.from(r.entries()).sort((i,g)=>i[0]-g[0]).map(([,i])=>i.reduce((g,f)=>g+f,0)/i.length)},oe=me(()=>{let e=[],t=[];if(R.value?.series){const s=R.value.series.find(r=>r.type==="rx_count"),o=R.value.series.find(r=>r.type==="tx_count");s?.data&&(e=re(s.data,v.value)),o?.data&&(t=re(o.data,v.value))}return{totalPackets:e,transmittedPackets:t,droppedPackets:[],crcErrors:re(j.value.map(s=>[s.timestamp>1e12?s.timestamp:s.timestamp*1e3,s.count]),v.value)}}),X=async()=>{try{$.value=!0,K.value=null,await Promise.all([A.fetchPacketStats({hours:v.value}),A.fetchSystemStats()]),$.value=!1,ye()}catch(e){K.value=e instanceof Error?e.message:"Failed to fetch data",$.value=!1}},ye=async()=>{C.value={packetRate:!0,packetType:!0,noiseFloor:!0,routePie:!0,sparklines:!0};const e=[he(),ke(),Ce(),_e(),we()];try{await Promise.allSettled(e),await ve(),!I.value||!ae.value?setTimeout(()=>{de()},100):de()}catch(t){console.error("Error loading chart data:",t)}},he=async()=>{try{const e=await H.get("/metrics_graph_data",{hours:v.value,resolution:"average",metrics:"rx_count,tx_count"});e?.success&&(R.value=e.data)}catch{R.value=null}},ke=async()=>{try{const e=await H.get("/packet_type_graph_data",{hours:v.value,resolution:"average",types:"all"});if(e?.success&&e.data){const t=e.data;U.value=t.series||[]}}catch{U.value=[]}},Ce=async()=>{try{const e=await H.get("/route_stats",{hours:v.value});e?.success&&e.data&&(D.value=e.data)}catch{D.value=null}},_e=async()=>{try{const e={hours:v.value},t=await H.get("/noise_floor_history",e);if(t.success&&t.data){const o=t.data.history||[];Array.isArray(o)&&o.length>0&&(O.value={chart_data:o.map(r=>({timestamp:r.timestamp||Date.now()/1e3,noise_floor_dbm:r.noise_floor_dbm||r.noise_floor||-120}))},Te())}}catch{O.value={chart_data:[]}}},we=async()=>{try{const e=await H.get("/crc_error_history",{hours:v.value});if(e?.success&&e.data){const t=e.data;j.value=t.history||[]}}catch{j.value=[]}},Se=()=>{C.value={packetRate:!0,packetType:!0,noiseFloor:!0,routePie:!0,sparklines:!0},ue(),L.value=!1,V.value=!1,z.value=!1,X()},Te=()=>{if(te.value=[],O.value?.chart_data&&O.value.chart_data.length>0){const e=O.value.chart_data;te.value=e.map(t=>({timestamp:t.timestamp*1e3,snr:null,rssi:null,noiseFloor:t.noise_floor_dbm}))}},de=()=>{if(!ee.value){ee.value=!0;try{Re(),De(),Ee(),Fe(),setTimeout(()=>{C.value={packetRate:!1,packetType:!1,noiseFloor:!1,routePie:!1,sparklines:!1},setTimeout(()=>{const e=M(_.value),t=M(w.value),s=M(y.value);e&&e.update("none"),t&&t.update("none"),s&&s.update("none")},50)},100)}catch(e){console.error("Error creating/updating charts:",e),ue()}finally{ee.value=!1}}},ue=()=>{try{_.value&&(_.value.destroy(),_.value=null),w.value&&(w.value.destroy(),w.value=null),y.value&&(y.value.destroy(),y.value=null),E.value&&J.purge(E.value)}catch(e){console.error("Error destroying charts:",e)}},Re=()=>{if(!I.value)return;const e=I.value.getContext("2d");if(!e)return;let t=[],s=[];if(R.value?.series){const p=R.value.series.find(c=>c.type==="rx_count"),b=R.value.series.find(c=>c.type==="tx_count");p?.data&&(t=p.data.map(([c,d])=>{let n=c;return c>1e15?n=c/1e3:c>1e12?n=c:c>1e9?n=c*1e3:n=Date.now(),{x:n,y:d}})),b?.data&&(s=b.data.map(([c,d])=>{let n=c;return c>1e15?n=c/1e3:c>1e12?n=c:c>1e9?n=c*1e3:n=Date.now(),{x:n,y:d}}))}if(t.length===0&&s.length===0){L.value=!0;return}L.value=!1,_.value&&(_.value.destroy(),_.value=null);const r=Math.round(v.value*60*60*1e3/72),i=p=>{if(p.length===0)return[];const b=new Map;return p.forEach(d=>{const n=Math.floor(d.x/r)*r;b.has(n)||b.set(n,[]),b.get(n).push(d.y)}),Array.from(b.entries()).map(([d,n])=>({x:d,y:n.reduce((F,W)=>F+W,0)/n.length})).sort((d,n)=>d.x-n.x)},g=(p,b=3)=>{if(p.lengthAe+Ne.y,0)/W.length;c.push({x:p[d].x,y:Be})}return c},f=g(i(t)),S=g(i(s)),P=[...f.map(p=>p.y),...S.map(p=>p.y)],l=Math.min(...P),m=Math.max(...P),x=m-l||m*.1||.001,Me=Math.max(0,l-x*.05),Pe=m+x*.05;try{const p=JSON.parse(JSON.stringify(f)),b=JSON.parse(JSON.stringify(S)),c=new q(e,{type:"line",data:{datasets:[{label:"TX/hr",data:b,borderColor:"#F59E0B",backgroundColor:"#F59E0B",borderWidth:2,fill:"origin",tension:.4,pointRadius:0,pointHoverRadius:3,order:1},{label:"RX/hr",data:p,borderColor:"#C084FC",backgroundColor:"#C084FC",borderWidth:2,fill:"origin",tension:.4,pointRadius:0,pointHoverRadius:3,order:2}]},options:{responsive:!0,maintainAspectRatio:!1,animation:{duration:0},interaction:{mode:"index",intersect:!1},plugins:{legend:{display:!1},title:{display:!1},tooltip:{enabled:!0,backgroundColor:"rgba(0, 0, 0, 0.8)",titleColor:"rgba(255, 255, 255, 0.9)",bodyColor:"rgba(255, 255, 255, 0.8)",borderColor:"rgba(255, 255, 255, 0.2)",borderWidth:1,padding:12,displayColors:!0,callbacks:{title:function(d){const n=d[0]?.parsed?.x;return n==null?"":new Date(n).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})},label:function(d){const n=d.dataset?.label||"",F=d.parsed?.y;return F==null?n:`${n}: ${F.toFixed(3)}`}}}},scales:{x:{type:"time",time:{unit:"hour",displayFormats:{hour:"HH:mm"}},min:Date.now()-v.value*3600*1e3,max:Date.now(),grid:{color:T().gridColor},ticks:{color:T().tickColor,maxTicksLimit:8}},y:{beginAtZero:!1,grid:{color:T().gridColor},ticks:{color:T().tickColor,callback:function(d){return typeof d=="number"?d.toFixed(3):d}},min:Me,max:Pe}}}});_.value=le(c)}catch(p){console.error("Error creating packet rate chart:",p),L.value=!0}},De=()=>{if(!ae.value)return;const e=ae.value.getContext("2d");if(!e)return;const t=[],s=[],o=["#60A5FA","#34D399","#FBBF24","#A78BFA","#F87171","#06B6D4","#84CC16","#F472B6","#10B981"];if(U.value.length>0)U.value.forEach(r=>{const i=r.data?r.data.reduce((g,f)=>g+f[1],0):0;i>0&&(t.push(r.name.replace(/\([^)]*\)/g,"").trim()),s.push(i))});else{V.value=!0;return}V.value=!1,w.value&&(w.value.destroy(),w.value=null);try{const r=JSON.parse(JSON.stringify(t)),i=JSON.parse(JSON.stringify(s)),g=new q(e,{type:"bar",data:{labels:r,datasets:[{data:i,backgroundColor:o.slice(0,i.length),borderRadius:8,borderSkipped:!1}]},options:{responsive:!0,maintainAspectRatio:!1,animation:{duration:0},plugins:{legend:{display:!1}},scales:{x:{grid:{display:!1},ticks:{color:"rgba(255, 255, 255, 0.7)",font:{size:10}}},y:{beginAtZero:!0,grid:{color:"rgba(255, 255, 255, 0.1)"},ticks:{color:"rgba(255, 255, 255, 0.7)"}}}}});w.value=le(g)}catch(r){console.error("Error creating packet type chart:",r),V.value=!0}},Ee=()=>{if(!se.value)return;const e=se.value.getContext("2d");if(!e)return;const t=te.value.map(l=>({x:l.timestamp,y:l.noiseFloor})).filter(l=>l.y!==null&&l.y!==void 0),s=t.map(l=>l.y),o=s.length>0?Math.min(...s):-120,r=s.length>0?Math.max(...s):-110,i=r-o||1,g=o-i*.05,f=r+i*.05;if(y.value)try{const l=M(y.value),m=JSON.parse(JSON.stringify(t));l.data.datasets[0]&&(l.data.datasets[0].data=m),l.options?.scales?.x&&(l.options.scales.x.min=Date.now()-v.value*3600*1e3,l.options.scales.x.max=Date.now()),l.update("active");return}catch{y.value.destroy(),y.value=null}const S=JSON.parse(JSON.stringify(t)),P=new q(e,{type:"scatter",data:{datasets:[{label:"Noise Floor (dBm)",data:S,borderWidth:0,backgroundColor:"rgba(245, 158, 11, 0.8)",pointRadius:3,pointHoverRadius:5,pointStyle:"circle"}]},options:{responsive:!0,maintainAspectRatio:!1,animation:{duration:0},interaction:{mode:"index",intersect:!1},plugins:{legend:{display:!0,position:"top",labels:{color:T().legendColor,usePointStyle:!0,padding:20}},tooltip:{enabled:!0,backgroundColor:"rgba(0, 0, 0, 0.8)",titleColor:"rgba(255, 255, 255, 0.9)",bodyColor:"rgba(255, 255, 255, 0.8)",borderColor:"rgba(255, 255, 255, 0.2)",borderWidth:1,padding:12,displayColors:!0,callbacks:{title:function(l){const m=l[0]?.parsed?.x;return m==null?"":new Date(m).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})},label:function(l){const m=l.dataset?.label||"",x=l.parsed?.y;return x==null?m:`${m}: ${x.toFixed(1)} dBm`}}}},scales:{x:{type:"time",time:{unit:"hour",displayFormats:{hour:"HH:mm"}},min:Date.now()-v.value*3600*1e3,max:Date.now(),grid:{color:T().gridColor},ticks:{color:T().tickColor,maxTicksLimit:8}},y:{type:"linear",display:!0,title:{display:!0,text:"Noise Floor (dBm)",color:T().titleColor},grid:{color:"rgba(245, 158, 11, 0.2)"},ticks:{color:"#F59E0B",callback:function(l){return typeof l=="number"?l.toFixed(1):l}},min:g,max:f}}}});y.value=le(P)},Fe=()=>{if(!E.value)return;if(!D.value||!D.value.route_totals){z.value=!0;return}z.value=!1;const e=D.value.route_totals,t=Object.keys(e),s=Object.values(e),o=["#3B82F6","#10B981","#F59E0B","#A78BFA","#F87171"];try{const r=JSON.parse(JSON.stringify(t)),i=JSON.parse(JSON.stringify(s)),g=i.reduce((m,x)=>m+x,0),f=i.map(m=>m/g*100),S=r.map((m,x)=>({type:"bar",name:m,x:[f[x]],y:[""],orientation:"h",marker:{color:o[x%o.length]},text:f[x]>=5?`${m} ${f[x].toFixed(0)}%`:"",textposition:"inside",textfont:{color:"white",size:11},hoverinfo:"none",insidetextanchor:"middle"})),P={paper_bgcolor:"rgba(0,0,0,0)",plot_bgcolor:"rgba(0,0,0,0)",font:{color:"rgba(255, 255, 255, 0.8)",size:11},margin:{t:10,b:60,l:10,r:10},barmode:"stack",showlegend:!0,legend:{orientation:"h",x:0,y:-.3,xanchor:"left",font:{color:"rgba(255, 255, 255, 0.8)",size:10}},xaxis:{showgrid:!1,showticklabels:!1,zeroline:!1,range:[0,100]},yaxis:{showgrid:!1,showticklabels:!1,zeroline:!1},hovermode:!1,bargap:0},l={responsive:!0,displayModeBar:!1,staticPlot:!0};J.newPlot(E.value,S,P,l)}catch(r){console.error("Error creating route treemap chart:",r),z.value=!0}};return He(async()=>{await ve(),X(),Q.isConnected||ne(),pe(()=>Q.isConnected,e=>{e?ie():ne()}),window.addEventListener("resize",()=>{setTimeout(()=>{M(_.value)?.resize(),M(w.value)?.resize(),M(y.value)?.resize(),E.value&&J.Plots&&J.Plots.resize(E.value)},100)})}),Je(()=>{ie(),_.value?.destroy(),w.value?.destroy(),y.value?.destroy(),E.value&&J.purge(E.value),window.removeEventListener("resize",()=>{})}),(e,t)=>(k(),h("div",ct,[a("div",dt,[t[2]||(t[2]=a("h2",{class:"text-xl sm:text-2xl font-bold text-content-primary dark:text-content-primary"},"Statistics",-1)),a("div",ut,[t[1]||(t[1]=a("label",{class:"text-content-secondary dark:text-content-muted text-xs sm:text-sm"},"Time Range:",-1)),Ue(a("select",{"onUpdate:modelValue":t[0]||(t[0]=s=>v.value=s),onChange:Se,class:"bg-white dark:bg-white/10 border border-stroke-subtle dark:border-stroke/20 rounded-lg px-2 sm:px-3 py-1.5 sm:py-2 text-content-primary dark:text-content-primary text-xs sm:text-sm focus:outline-hidden focus:border-primary dark:focus:border-accent-purple/50 transition-colors"},[(k(),h(ge,null,fe(be,s=>a("option",{key:s.value,value:s.value,class:"bg-white dark:bg-gray-800 text-content-primary dark:text-content-primary"},Y(s.label),9,pt)),64))],544),[[je,v.value]])])]),a("div",mt,[G(Z,{title:"Total RX",value:ce.value.totalRx,color:"#AAE8E8",data:oe.value.totalPackets,loading:C.value.sparklines,variant:"classic"},null,8,["value","data","loading"]),G(Z,{title:"Total TX",value:ce.value.totalTx,color:"#FFC246",data:oe.value.transmittedPackets,loading:C.value.sparklines,variant:"classic"},null,8,["value","data","loading"]),G(Z,{title:"CRC Errors",value:j.value.reduce((s,o)=>s+o.count,0),color:"#F59E0B",data:oe.value.crcErrors,loading:C.value.sparklines,variant:"classic"},null,8,["value","data","loading"]),G(Z,{title:"Packet Hash Cache",value:xe(A).systemStats?.duplicate_cache_size??0,color:"#9F7AEA",data:[],loading:!1,variant:"smooth",subtitle:`Entries expire after ${(()=>{const s=xe(A).systemStats?.cache_ttl??3600,o=Math.floor(s/60);return o>=60?`${Math.floor(o/60)}h`:`${o}m`})()}`},null,8,["value","subtitle"])]),a("div",vt,[t[6]||(t[6]=a("h3",{class:"text-content-primary dark:text-content-primary text-lg sm:text-xl font-semibold mb-3 sm:mb-4"},"Performance Metrics",-1)),a("div",null,[t[5]||(t[5]=$e('

Packet Rate (RX/TX PER HOUR)

RX/hr
TX/hr
',2)),a("div",gt,[a("canvas",{ref_key:"packetRateCanvasRef",ref:I,class:"w-full h-full relative z-10"},null,512),C.value.packetRate?(k(),h("div",ft,t[3]||(t[3]=[a("div",{class:"text-center"},[a("div",{class:"animate-spin w-6 h-6 sm:w-8 sm:h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-purple-600 dark:border-t-purple-400 rounded-full mx-auto mb-2"}),a("div",{class:"text-content-secondary dark:text-content-muted text-[10px] sm:text-xs"},"Loading packet rate data...")],-1)]))):B("",!0),L.value&&!C.value.packetRate?(k(),h("div",xt,t[4]||(t[4]=[a("div",{class:"text-center"},[a("div",{class:"text-red-700 dark:text-red-400 text-sm font-semibold mb-1"},"No Data Available"),a("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Packet rate data not found")],-1)]))):B("",!0)])])]),a("div",bt,[a("div",yt,[t[8]||(t[8]=a("h3",{class:"text-content-primary dark:text-content-primary text-lg sm:text-xl font-semibold mb-3 sm:mb-4"}," Noise Floor Over Time ",-1)),a("div",ht,[a("canvas",{ref_key:"signalMetricsCanvasRef",ref:se,class:"w-full h-full"},null,512),C.value.noiseFloor?(k(),h("div",kt,t[7]||(t[7]=[a("div",{class:"text-center"},[a("div",{class:"animate-spin w-6 h-6 sm:w-8 sm:h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-amber-600 dark:border-t-amber-400 rounded-full mx-auto mb-2"}),a("div",{class:"text-content-secondary dark:text-content-muted text-[10px] sm:text-xs"},"Loading noise floor data...")],-1)]))):B("",!0)])]),a("div",Ct,[t[11]||(t[11]=a("h3",{class:"text-content-primary dark:text-content-primary text-lg sm:text-xl font-semibold mb-3 sm:mb-4"},"Route Distribution",-1)),a("div",_t,[C.value.routePie?(k(),h("div",wt,t[9]||(t[9]=[a("div",{class:"text-center"},[a("div",{class:"animate-spin w-6 h-6 border-2 border-stroke-subtle dark:border-stroke/20 border-t-green-600 dark:border-t-green-400 rounded-full mx-auto mb-2"}),a("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Loading route data...")],-1)]))):z.value?(k(),h("div",St,t[10]||(t[10]=[a("div",{class:"text-center"},[a("div",{class:"text-red-700 dark:text-red-400 text-sm font-semibold mb-1"},"No Data Available"),a("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Route statistics not found")],-1)]))):D.value?.route_totals?(k(!0),h(ge,{key:2},fe(D.value.route_totals,(s,o,r)=>(k(),h("div",{key:o,class:"flex items-center gap-3"},[a("div",Tt,Y(o),1),a("div",Rt,[a("div",{class:"h-full rounded transition-all duration-300",style:Ke({width:`${s/Math.max(...Object.values(D.value.route_totals))*100}%`,backgroundColor:["#3B82F6","#10B981","#F59E0B","#A78BFA","#F87171"][r%5]})},null,4)]),a("div",Dt,Y(s.toLocaleString()),1)]))),128)):B("",!0)])])]),$.value?(k(),h("div",Et,t[12]||(t[12]=[a("div",{class:"text-content-secondary dark:text-content-muted mb-2 text-sm"},"Loading statistics...",-1),a("div",{class:"animate-spin w-6 h-6 sm:w-8 sm:h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-content-primary dark:border-t-white/70 rounded-full mx-auto"},null,-1)]))):B("",!0),K.value?(k(),h("div",Ft,[t[13]||(t[13]=a("div",{class:"text-red-700 dark:text-red-400 mb-2 text-sm font-semibold"},"Failed to load statistics",-1)),a("p",Mt,Y(K.value),1),a("button",{onClick:X,class:"mt-4 px-4 py-2 bg-primary hover:bg-primary/90 dark:bg-primary dark:hover:bg-primary/80 text-white font-medium rounded-lg border border-primary/20 dark:border-primary/30 transition-colors shadow-sm"}," Retry ")])):B("",!0)]))}}),Jt=Ve(Pt,[["__scopeId","data-v-8daccd7e"]]);export{Jt as default}; diff --git a/repeater/web/html/assets/SystemStats-DiLdS6K6.js b/repeater/web/html/assets/SystemStats-DiLdS6K6.js index 135d54a..f9921ba 100644 --- a/repeater/web/html/assets/SystemStats-DiLdS6K6.js +++ b/repeater/web/html/assets/SystemStats-DiLdS6K6.js @@ -1,2 +1,6 @@ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/plotly.min-DO11Gp-n.js","assets/_commonjsHelpers-CqkleIqs.js"])))=>i.map(i=>d[i]); +<<<<<<<< HEAD:repeater/web/html/assets/SystemStats-DiLdS6K6.js import{a as ot,r as u,c as W,E as N,o as nt,R as X,S as O,H as lt,b as d,e as t,g as v,f as A,t as o,F as Y,h as G,I as K,L as Q,j as V,p as i,x as dt}from"./index-DyUIpN7m.js";import{S as P}from"./chartjs-adapter-date-fns.esm-BYg_FBhT.js";import{C as j,a as it,L as ct,P as ut,b as mt,c as vt,B as pt,D as xt,p as yt,d as gt,e as ft,A as bt,f as kt,i as ht,T as _t}from"./chart-B185MtDy.js";const Ct={class:"p-6 space-y-6"},wt={class:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"},Ft={class:"grid grid-cols-1 lg:grid-cols-2 gap-6"},St={class:"glass-card rounded-[15px] p-6"},Ut={class:"relative h-32 bg-gray-100/50 dark:bg-white/5 rounded-lg p-4 mb-4 chart-container"},Bt={key:0,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 backdrop-blur-sm z-20"},Et={key:1,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 z-20"},At={key:0,class:"grid grid-cols-2 gap-4 text-sm"},Pt={class:"text-content-primary dark:text-content-primary font-semibold"},Lt={class:"text-content-primary dark:text-content-primary font-semibold"},Mt={class:"text-content-primary dark:text-content-primary font-semibold"},Dt={class:"text-content-primary dark:text-content-primary font-semibold"},Rt={class:"glass-card rounded-[15px] p-6"},Tt={class:"relative h-32 bg-gray-100/50 dark:bg-white/5 rounded-lg p-4 mb-4 chart-container"},zt={key:0,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 backdrop-blur-sm z-20"},$t={key:1,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 z-20"},It={key:0,class:"grid grid-cols-2 gap-4 text-sm"},Nt={class:"text-content-primary dark:text-content-primary font-semibold"},Ot={class:"text-content-primary dark:text-content-primary font-semibold"},Vt={class:"text-content-primary dark:text-content-primary font-semibold"},jt={class:"text-content-primary dark:text-content-primary font-semibold"},Ht={class:"grid grid-cols-1 lg:grid-cols-2 gap-6"},qt={class:"glass-card rounded-[15px] p-6"},Jt={class:"relative h-48"},Wt={key:0,class:"grid grid-cols-3 gap-4 text-sm mt-4"},Xt={class:"text-center"},Yt={class:"text-content-primary dark:text-content-primary font-semibold"},Gt={class:"text-center"},Kt={class:"font-semibold text-red-500 dark:text-red-400"},Qt={class:"text-center"},Zt={class:"font-semibold text-green-700 dark:text-green-400"},te={class:"glass-card rounded-[15px] p-6"},ee={key:0,class:"space-y-4"},ae={class:"grid grid-cols-2 gap-4 text-sm"},se={class:"text-content-primary dark:text-content-primary font-semibold"},re={class:"text-content-primary dark:text-content-primary font-semibold"},oe={class:"text-content-primary dark:text-content-primary font-semibold"},ne={class:"text-content-primary dark:text-content-primary font-semibold"},le={key:0,class:"pt-4 border-t border-stroke-subtle dark:border-stroke/10"},de={class:"grid grid-cols-2 gap-2 text-sm"},ie={class:"text-content-secondary dark:text-content-muted"},ce={class:"text-content-primary dark:text-content-primary font-semibold ml-1"},ue={class:"glass-card rounded-[15px] p-6"},me={key:0,class:"overflow-x-auto"},ve={class:"w-full text-sm"},pe={class:"text-content-secondary dark:text-content-primary/80 py-2 transition-all duration-300"},xe={class:"text-content-primary dark:text-content-primary font-semibold py-2 transition-all duration-300"},ye={class:"text-center text-orange-500 dark:text-orange-400 py-2 transition-all duration-300"},ge={class:"text-center text-green-700 dark:text-green-400 py-2 transition-all duration-300"},fe={class:"text-right text-content-secondary dark:text-content-primary/80 py-2 transition-all duration-300"},be={key:0,class:"mt-4 text-center text-content-secondary dark:text-content-muted text-sm transition-all duration-300"},ke={key:1,class:"text-center text-content-secondary dark:text-content-muted py-8"},he={key:0,class:"glass-card rounded-[15px] p-8 text-center"},_e={key:1,class:"glass-card rounded-[15px] p-8 text-center"},Ce={class:"text-content-secondary dark:text-content-muted text-sm"},we=ot({name:"SystemStatsView",__name:"SystemStats",setup(Fe){j.register(it,ct,ut,mt,vt,pt,xt,yt,gt,ft,bt,kt,ht,_t);const L=u(null),_=u(!0),C=u(null),s=u(null),b=u(null),x=u([]),M=u(null),p=u({cpuChart:!0,memoryChart:!0,diskChart:!1,processChart:!0}),D=u(!1),R=u(!1),y=u(null),g=u(null),T=u(null),z=u(null),k=u(null),B=W(()=>s.value?{cpuUsage:s.value.cpu.usage_percent,memoryUsage:s.value.memory.usage_percent,diskUsage:s.value.disk.usage_percent,uptime:s.value.system.uptime}:{cpuUsage:0,memoryUsage:0,diskUsage:0,uptime:0}),E=W(()=>x.value.length===0?{cpu:[],memory:[],disk:[],network:[]}:{cpu:x.value.map(a=>a.cpu.usage_percent),memory:x.value.map(a=>a.memory.usage_percent),disk:x.value.map(a=>a.disk.usage_percent),network:x.value.map(a=>a.network.bytes_recv/1024/1024)}),f=a=>{const e=["B","KB","MB","GB","TB"];if(a===0)return"0 B";const r=Math.floor(Math.log(a)/Math.log(1024));return parseFloat((a/Math.pow(1024,r)).toFixed(2))+" "+e[r]},Z=a=>{const e=Math.floor(a/86400),r=Math.floor(a%86400/3600),n=Math.floor(a%3600/60);return e>0?`${e}d ${r}h ${n}m`:r>0?`${r}h ${n}m`:`${n}m`},tt=async()=>{try{const a=await Q.get("/hardware_stats");if(a?.success&&a.data){const e=a.data;if(s.value=e,x.value.length===0)for(let n=0;n<12;n++)x.value.push(JSON.parse(JSON.stringify(e)));else x.value.push(e),x.value.length>20&&x.value.shift()}}catch(a){console.error("Failed to fetch hardware stats:",a),C.value="Failed to fetch hardware stats"}},et=async()=>{try{const a=await Q.get("/hardware_processes");a?.success&&a.data&&(M.value=b.value,b.value=a.data)}catch(a){console.error("Failed to fetch process stats:",a)}},$=(a,e)=>{if(!M.value)return!1;const r=M.value.processes.find(n=>n.pid===a.pid);return r?r[e]!==a[e]:!0},I=async()=>{try{_.value=!0,C.value=null,await Promise.all([tt(),et()]),_.value=!1,await N(),H()}catch(a){C.value=a instanceof Error?a.message:"Failed to fetch system data",_.value=!1}},H=()=>{s.value&&(at(),st(),rt())},at=()=>{if(!T.value||!s.value){p.value.cpuChart=!1;return}const a=T.value.getContext("2d");if(!a){p.value.cpuChart=!1;return}const e=s.value.cpu.usage_percent,r=100-e;if(y.value)try{y.value.data.datasets[0].data=[e,r],y.value.update("none");return}catch(m){console.warn("Failed to update CPU chart, recreating...",m),y.value.destroy(),y.value=null}const n=document.documentElement.classList.contains("dark"),h=n?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)",w=n?"rgba(255, 255, 255, 0.2)":"rgba(0, 0, 0, 0.2)",F=n?"rgba(255, 255, 255, 0.6)":"rgba(0, 0, 0, 0.6)";try{const m=new j(a,{type:"doughnut",data:{labels:["Used","Available"],datasets:[{data:[e,r],backgroundColor:["#FFC246",h],borderColor:["#FFC246",w],borderWidth:2}]},options:{responsive:!0,maintainAspectRatio:!1,cutout:"70%",animation:{animateRotate:!1,animateScale:!1,duration:0},plugins:{legend:{display:!1},tooltip:{callbacks:{label:function(c){return`${c.label}: ${c.parsed.toFixed(1)}%`}}}}},plugins:[{id:"centerText",beforeDraw:function(c){const l=c.ctx;l.save();const S=(c.chartArea.left+c.chartArea.right)/2,U=(c.chartArea.top+c.chartArea.bottom)/2;l.textAlign="center",l.textBaseline="middle",l.fillStyle="#FFC246",l.font="bold 18px sans-serif",l.fillText(`${e.toFixed(1)}%`,S,U-5),l.fillStyle=F,l.font="10px sans-serif",l.fillText("CPU",S,U+12),l.restore()}}]});y.value=K(m),D.value=!1,p.value.cpuChart=!1}catch(m){console.error("Error creating CPU chart:",m),D.value=!0,p.value.cpuChart=!1}},st=()=>{if(!z.value||!s.value){p.value.memoryChart=!1;return}const a=z.value.getContext("2d");if(!a){p.value.memoryChart=!1;return}const e=s.value.memory.usage_percent,r=100-e;if(g.value)try{g.value.data.datasets[0].data=[e,r],g.value.update("none");return}catch(m){console.warn("Failed to update Memory chart, recreating...",m),g.value.destroy(),g.value=null}const n=document.documentElement.classList.contains("dark"),h=n?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)",w=n?"rgba(255, 255, 255, 0.2)":"rgba(0, 0, 0, 0.2)",F=n?"rgba(255, 255, 255, 0.6)":"rgba(0, 0, 0, 0.6)";try{const m=new j(a,{type:"doughnut",data:{labels:["Used","Available"],datasets:[{data:[e,r],backgroundColor:["#A5E5B6",h],borderColor:["#A5E5B6",w],borderWidth:2}]},options:{responsive:!0,maintainAspectRatio:!1,cutout:"70%",animation:{animateRotate:!1,animateScale:!1,duration:0},plugins:{legend:{display:!1},tooltip:{callbacks:{label:function(c){return`${c.label}: ${c.parsed.toFixed(1)}%`}}}}},plugins:[{id:"centerText",beforeDraw:function(c){const l=c.ctx;l.save();const S=(c.chartArea.left+c.chartArea.right)/2,U=(c.chartArea.top+c.chartArea.bottom)/2;l.textAlign="center",l.textBaseline="middle",l.fillStyle="#A5E5B6",l.font="bold 18px sans-serif",l.fillText(`${e.toFixed(1)}%`,S,U-5),l.fillStyle=F,l.font="10px sans-serif",l.fillText("Memory",S,U+12),l.restore()}}]});g.value=K(m),R.value=!1,p.value.memoryChart=!1}catch(m){console.error("Error creating Memory chart:",m),R.value=!0,p.value.memoryChart=!1}},rt=()=>{if(!k.value||!s.value)return;const e=document.documentElement.classList.contains("dark")?"rgba(255, 255, 255, 0.8)":"rgba(0, 0, 0, 0.8)";try{O(()=>import("./plotly.min-DO11Gp-n.js").then(r=>r.p),__vite__mapDeps([0,1])).then(r=>{const n=r.default||r,h=s.value.disk,w=[{type:"pie",labels:["Used","Free"],values:[h.used,h.free],marker:{colors:["#FB787B","#A5E5B6"]},hovertemplate:"%{label}
Size: %{value}
Percentage: %{percent}",textinfo:"label+percent",textposition:"auto",hole:.4}],F={title:{text:"",font:{color:e}},paper_bgcolor:"rgba(0,0,0,0)",plot_bgcolor:"rgba(0,0,0,0)",font:{color:e,size:11},margin:{t:20,b:20,l:20,r:20},showlegend:!0,legend:{orientation:"h",x:0,y:-.2,font:{color:e,size:10}}},m={responsive:!0,displayModeBar:!1,staticPlot:!1};n.newPlot(k.value,w,F,m)})}catch(r){console.error("Error creating disk chart:",r)}},q=()=>{try{if(y.value&&(y.value.destroy(),y.value=null),g.value&&(g.value.destroy(),g.value=null),k.value)try{O(()=>import("./plotly.min-DO11Gp-n.js").then(a=>a.p),__vite__mapDeps([0,1])).then(a=>{const e=a?.default||a;e?.purge&&e.purge(k.value)}).catch(()=>{})}catch{}}catch(a){console.error("Error destroying charts:",a)}},J=new MutationObserver(a=>{a.forEach(e=>{e.attributeName==="class"&&(q(),N(()=>{H()}))})});return nt(async()=>{await N(),I(),L.value=window.setInterval(I,5e3),J.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]}),window.addEventListener("resize",()=>{setTimeout(()=>{X(y.value)?.resize(),X(g.value)?.resize();try{O(()=>import("./plotly.min-DO11Gp-n.js").then(a=>a.p),__vite__mapDeps([0,1])).then(a=>{const e=a?.default||a;e?.Plots&&e.Plots.resize(k.value)}).catch(()=>{})}catch{}},100)})}),lt(()=>{L.value&&clearInterval(L.value),J.disconnect(),q(),window.removeEventListener("resize",()=>{})}),(a,e)=>(i(),d("div",Ct,[e[28]||(e[28]=t("div",{class:"flex justify-between items-center"},[t("h2",{class:"text-2xl font-bold text-content-primary dark:text-content-primary"},"System Statistics"),t("div",{class:"text-content-secondary dark:text-content-muted text-sm"}," Updates every 5 seconds ")],-1)),t("div",wt,[A(P,{title:"CPU Usage",value:`${B.value.cpuUsage.toFixed(1)}%`,color:"#FFC246",data:E.value.cpu},null,8,["value","data"]),A(P,{title:"Memory Usage",value:`${B.value.memoryUsage.toFixed(1)}%`,color:"#A5E5B6",data:E.value.memory},null,8,["value","data"]),A(P,{title:"Disk Usage",value:`${B.value.diskUsage.toFixed(1)}%`,color:"#FB787B",data:E.value.disk},null,8,["value","data"]),A(P,{title:"Uptime",value:Z(B.value.uptime),color:"#EBA0FC",data:E.value.network},null,8,["value","data"])]),t("div",Ft,[t("div",St,[e[6]||(e[6]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"CPU Performance",-1)),t("div",Ut,[t("canvas",{ref_key:"cpuCanvasRef",ref:T,class:"w-full h-full relative z-10"},null,512),p.value.cpuChart?(i(),d("div",Bt,e[0]||(e[0]=[t("div",{class:"text-center"},[t("div",{class:"animate-spin w-6 h-6 border-2 border-stroke-subtle dark:border-stroke/20 border-t-orange-400 rounded-full mx-auto mb-2"}),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Loading CPU data...")],-1)]))):v("",!0),D.value&&!p.value.cpuChart?(i(),d("div",Et,e[1]||(e[1]=[t("div",{class:"text-center"},[t("div",{class:"text-red-500 dark:text-red-400 text-sm mb-1"},"No Data Available"),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"CPU data not found")],-1)]))):v("",!0)]),s.value?(i(),d("div",At,[t("div",null,[e[2]||(e[2]=t("div",{class:"text-content-secondary dark:text-content-muted"},"CPU Count",-1)),t("div",Pt,o(s.value.cpu.count)+" cores",1)]),t("div",null,[e[3]||(e[3]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Frequency",-1)),t("div",Lt,o(s.value.cpu.frequency.toFixed(0))+" MHz",1)]),t("div",null,[e[4]||(e[4]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Load (1m)",-1)),t("div",Mt,o(s.value.cpu.load_avg["1min"].toFixed(2)),1)]),t("div",null,[e[5]||(e[5]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Load (5m)",-1)),t("div",Dt,o(s.value.cpu.load_avg["5min"].toFixed(2)),1)])])):v("",!0)]),t("div",Rt,[e[13]||(e[13]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Memory Usage",-1)),t("div",Tt,[t("canvas",{ref_key:"memoryCanvasRef",ref:z,class:"w-full h-full relative z-10"},null,512),p.value.memoryChart?(i(),d("div",zt,e[7]||(e[7]=[t("div",{class:"text-center"},[t("div",{class:"animate-spin w-6 h-6 border-2 border-stroke-subtle dark:border-stroke/20 border-t-green-400 rounded-full mx-auto mb-2"}),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Loading memory data...")],-1)]))):v("",!0),R.value&&!p.value.memoryChart?(i(),d("div",$t,e[8]||(e[8]=[t("div",{class:"text-center"},[t("div",{class:"text-red-500 dark:text-red-400 text-sm mb-1"},"No Data Available"),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Memory data not found")],-1)]))):v("",!0)]),s.value?(i(),d("div",It,[t("div",null,[e[9]||(e[9]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Total",-1)),t("div",Nt,o(f(s.value.memory.total)),1)]),t("div",null,[e[10]||(e[10]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Used",-1)),t("div",Ot,o(f(s.value.memory.used)),1)]),t("div",null,[e[11]||(e[11]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Available",-1)),t("div",Vt,o(f(s.value.memory.available)),1)]),t("div",null,[e[12]||(e[12]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Usage",-1)),t("div",jt,o(s.value.memory.usage_percent.toFixed(1))+"%",1)])])):v("",!0)])]),t("div",Ht,[t("div",qt,[e[17]||(e[17]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Storage Usage",-1)),t("div",Jt,[t("div",{ref_key:"diskCanvasRef",ref:k,class:"w-full h-full"},null,512)]),s.value?(i(),d("div",Wt,[t("div",Xt,[e[14]||(e[14]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Total",-1)),t("div",Yt,o(f(s.value.disk.total)),1)]),t("div",Gt,[e[15]||(e[15]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Used",-1)),t("div",Kt,o(f(s.value.disk.used)),1)]),t("div",Qt,[e[16]||(e[16]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Free",-1)),t("div",Zt,o(f(s.value.disk.free)),1)])])):v("",!0)]),t("div",te,[e[23]||(e[23]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Network Statistics",-1)),s.value?(i(),d("div",ee,[t("div",ae,[t("div",null,[e[18]||(e[18]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Bytes Sent",-1)),t("div",se,o(f(s.value.network.bytes_sent)),1)]),t("div",null,[e[19]||(e[19]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Bytes Received",-1)),t("div",re,o(f(s.value.network.bytes_recv)),1)]),t("div",null,[e[20]||(e[20]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Packets Sent",-1)),t("div",oe,o(s.value.network.packets_sent.toLocaleString()),1)]),t("div",null,[e[21]||(e[21]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Packets Received",-1)),t("div",ne,o(s.value.network.packets_recv.toLocaleString()),1)])]),s.value.temperatures&&Object.keys(s.value.temperatures).length>0?(i(),d("div",le,[e[22]||(e[22]=t("div",{class:"text-content-secondary dark:text-content-muted mb-2"},"System Temperatures",-1)),t("div",de,[(i(!0),d(Y,null,G(s.value.temperatures,(r,n)=>(i(),d("div",{key:n},[t("span",ie,o(n)+":",1),t("span",ce,o(r.toFixed(1))+"°C",1)]))),128))])])):v("",!0)])):v("",!0)])]),t("div",ue,[e[25]||(e[25]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Top Processes",-1)),b.value?.processes&&b.value.processes.length>0?(i(),d("div",me,[t("table",ve,[e[24]||(e[24]=t("thead",null,[t("tr",{class:"border-b border-stroke-subtle dark:border-stroke/10"},[t("th",{class:"text-left text-content-secondary dark:text-content-muted py-2"},"PID"),t("th",{class:"text-left text-content-secondary dark:text-content-muted py-2"},"Name"),t("th",{class:"text-center text-content-secondary dark:text-content-muted py-2"},"CPU %"),t("th",{class:"text-center text-content-secondary dark:text-content-muted py-2"},"Memory %"),t("th",{class:"text-right text-content-secondary dark:text-content-muted py-2"},"Memory")])],-1)),t("tbody",null,[(i(!0),d(Y,null,G(b.value.processes.slice(0,10),r=>(i(),d("tr",{key:r.pid,class:"border-b border-stroke-subtle dark:border-white/5 process-row"},[t("td",pe,o(r.pid),1),t("td",xe,o(r.name),1),t("td",ye,[t("span",{class:V(["cpu-value",{"value-updated":$(r,"cpu_percent")}])},o(r.cpu_percent.toFixed(1))+"% ",3)]),t("td",ge,[t("span",{class:V(["memory-value",{"value-updated":$(r,"memory_percent")}])},o(r.memory_percent.toFixed(1))+"% ",3)]),t("td",fe,[t("span",{class:V({"value-updated":$(r,"memory_mb")})},o(r.memory_mb.toFixed(1))+" MB ",3)])]))),128))])]),b.value.total_processes?(i(),d("div",be," Showing top 10 of "+o(b.value.total_processes)+" total processes ",1)):v("",!0)])):_.value?v("",!0):(i(),d("div",ke," No process data available "))]),_.value?(i(),d("div",he,e[26]||(e[26]=[t("div",{class:"text-content-secondary dark:text-content-muted mb-2"},"Loading system statistics...",-1),t("div",{class:"animate-spin w-8 h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-gray-900 dark:border-t-white/70 rounded-full mx-auto"},null,-1)]))):v("",!0),C.value?(i(),d("div",_e,[e[27]||(e[27]=t("div",{class:"text-red-500 dark:text-red-400 mb-2"},"Failed to load system statistics",-1)),t("p",Ce,o(C.value),1),t("button",{onClick:I,class:"mt-4 px-4 py-2 bg-purple-500/20 dark:bg-accent-purple/20 hover:bg-purple-500/30 dark:hover:bg-accent-purple/30 text-content-primary dark:text-content-primary rounded-lg border border-purple-500/50 dark:border-accent-purple/50 transition-colors"}," Retry ")])):v("",!0)]))}}),Ee=dt(we,[["__scopeId","data-v-eab6d04d"]]);export{Ee as default}; +======== +import{a as ot,r as u,c as W,E as N,o as nt,R as X,S as O,H as lt,b as d,e as t,g as v,f as A,t as o,F as Y,h as G,I as K,L as Q,j as V,p as i,x as dt}from"./index-BfUIlcDy.js";import{S as P}from"./chartjs-adapter-date-fns.esm-Do2apMon.js";import{C as j,a as it,L as ct,P as ut,b as mt,c as vt,B as pt,D as xt,p as yt,d as gt,e as ft,A as bt,f as kt,i as ht,T as _t}from"./chart-B185MtDy.js";const Ct={class:"p-6 space-y-6"},wt={class:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"},Ft={class:"grid grid-cols-1 lg:grid-cols-2 gap-6"},St={class:"glass-card rounded-[15px] p-6"},Ut={class:"relative h-32 bg-gray-100/50 dark:bg-white/5 rounded-lg p-4 mb-4 chart-container"},Bt={key:0,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 backdrop-blur-sm z-20"},Et={key:1,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 z-20"},At={key:0,class:"grid grid-cols-2 gap-4 text-sm"},Pt={class:"text-content-primary dark:text-content-primary font-semibold"},Lt={class:"text-content-primary dark:text-content-primary font-semibold"},Mt={class:"text-content-primary dark:text-content-primary font-semibold"},Dt={class:"text-content-primary dark:text-content-primary font-semibold"},Rt={class:"glass-card rounded-[15px] p-6"},Tt={class:"relative h-32 bg-gray-100/50 dark:bg-white/5 rounded-lg p-4 mb-4 chart-container"},zt={key:0,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 backdrop-blur-sm z-20"},$t={key:1,class:"absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-white/5 z-20"},It={key:0,class:"grid grid-cols-2 gap-4 text-sm"},Nt={class:"text-content-primary dark:text-content-primary font-semibold"},Ot={class:"text-content-primary dark:text-content-primary font-semibold"},Vt={class:"text-content-primary dark:text-content-primary font-semibold"},jt={class:"text-content-primary dark:text-content-primary font-semibold"},Ht={class:"grid grid-cols-1 lg:grid-cols-2 gap-6"},qt={class:"glass-card rounded-[15px] p-6"},Jt={class:"relative h-48"},Wt={key:0,class:"grid grid-cols-3 gap-4 text-sm mt-4"},Xt={class:"text-center"},Yt={class:"text-content-primary dark:text-content-primary font-semibold"},Gt={class:"text-center"},Kt={class:"font-semibold text-red-500 dark:text-red-400"},Qt={class:"text-center"},Zt={class:"font-semibold text-green-700 dark:text-green-400"},te={class:"glass-card rounded-[15px] p-6"},ee={key:0,class:"space-y-4"},ae={class:"grid grid-cols-2 gap-4 text-sm"},se={class:"text-content-primary dark:text-content-primary font-semibold"},re={class:"text-content-primary dark:text-content-primary font-semibold"},oe={class:"text-content-primary dark:text-content-primary font-semibold"},ne={class:"text-content-primary dark:text-content-primary font-semibold"},le={key:0,class:"pt-4 border-t border-stroke-subtle dark:border-stroke/10"},de={class:"grid grid-cols-2 gap-2 text-sm"},ie={class:"text-content-secondary dark:text-content-muted"},ce={class:"text-content-primary dark:text-content-primary font-semibold ml-1"},ue={class:"glass-card rounded-[15px] p-6"},me={key:0,class:"overflow-x-auto"},ve={class:"w-full text-sm"},pe={class:"text-content-secondary dark:text-content-primary/80 py-2 transition-all duration-300"},xe={class:"text-content-primary dark:text-content-primary font-semibold py-2 transition-all duration-300"},ye={class:"text-center text-orange-500 dark:text-orange-400 py-2 transition-all duration-300"},ge={class:"text-center text-green-700 dark:text-green-400 py-2 transition-all duration-300"},fe={class:"text-right text-content-secondary dark:text-content-primary/80 py-2 transition-all duration-300"},be={key:0,class:"mt-4 text-center text-content-secondary dark:text-content-muted text-sm transition-all duration-300"},ke={key:1,class:"text-center text-content-secondary dark:text-content-muted py-8"},he={key:0,class:"glass-card rounded-[15px] p-8 text-center"},_e={key:1,class:"glass-card rounded-[15px] p-8 text-center"},Ce={class:"text-content-secondary dark:text-content-muted text-sm"},we=ot({name:"SystemStatsView",__name:"SystemStats",setup(Fe){j.register(it,ct,ut,mt,vt,pt,xt,yt,gt,ft,bt,kt,ht,_t);const L=u(null),_=u(!0),C=u(null),s=u(null),b=u(null),x=u([]),M=u(null),p=u({cpuChart:!0,memoryChart:!0,diskChart:!1,processChart:!0}),D=u(!1),R=u(!1),y=u(null),g=u(null),T=u(null),z=u(null),k=u(null),B=W(()=>s.value?{cpuUsage:s.value.cpu.usage_percent,memoryUsage:s.value.memory.usage_percent,diskUsage:s.value.disk.usage_percent,uptime:s.value.system.uptime}:{cpuUsage:0,memoryUsage:0,diskUsage:0,uptime:0}),E=W(()=>x.value.length===0?{cpu:[],memory:[],disk:[],network:[]}:{cpu:x.value.map(a=>a.cpu.usage_percent),memory:x.value.map(a=>a.memory.usage_percent),disk:x.value.map(a=>a.disk.usage_percent),network:x.value.map(a=>a.network.bytes_recv/1024/1024)}),f=a=>{const e=["B","KB","MB","GB","TB"];if(a===0)return"0 B";const r=Math.floor(Math.log(a)/Math.log(1024));return parseFloat((a/Math.pow(1024,r)).toFixed(2))+" "+e[r]},Z=a=>{const e=Math.floor(a/86400),r=Math.floor(a%86400/3600),n=Math.floor(a%3600/60);return e>0?`${e}d ${r}h ${n}m`:r>0?`${r}h ${n}m`:`${n}m`},tt=async()=>{try{const a=await Q.get("/hardware_stats");if(a?.success&&a.data){const e=a.data;if(s.value=e,x.value.length===0)for(let n=0;n<12;n++)x.value.push(JSON.parse(JSON.stringify(e)));else x.value.push(e),x.value.length>20&&x.value.shift()}}catch(a){console.error("Failed to fetch hardware stats:",a),C.value="Failed to fetch hardware stats"}},et=async()=>{try{const a=await Q.get("/hardware_processes");a?.success&&a.data&&(M.value=b.value,b.value=a.data)}catch(a){console.error("Failed to fetch process stats:",a)}},$=(a,e)=>{if(!M.value)return!1;const r=M.value.processes.find(n=>n.pid===a.pid);return r?r[e]!==a[e]:!0},I=async()=>{try{_.value=!0,C.value=null,await Promise.all([tt(),et()]),_.value=!1,await N(),H()}catch(a){C.value=a instanceof Error?a.message:"Failed to fetch system data",_.value=!1}},H=()=>{s.value&&(at(),st(),rt())},at=()=>{if(!T.value||!s.value){p.value.cpuChart=!1;return}const a=T.value.getContext("2d");if(!a){p.value.cpuChart=!1;return}const e=s.value.cpu.usage_percent,r=100-e;if(y.value)try{y.value.data.datasets[0].data=[e,r],y.value.update("none");return}catch(m){console.warn("Failed to update CPU chart, recreating...",m),y.value.destroy(),y.value=null}const n=document.documentElement.classList.contains("dark"),h=n?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)",w=n?"rgba(255, 255, 255, 0.2)":"rgba(0, 0, 0, 0.2)",F=n?"rgba(255, 255, 255, 0.6)":"rgba(0, 0, 0, 0.6)";try{const m=new j(a,{type:"doughnut",data:{labels:["Used","Available"],datasets:[{data:[e,r],backgroundColor:["#FFC246",h],borderColor:["#FFC246",w],borderWidth:2}]},options:{responsive:!0,maintainAspectRatio:!1,cutout:"70%",animation:{animateRotate:!1,animateScale:!1,duration:0},plugins:{legend:{display:!1},tooltip:{callbacks:{label:function(c){return`${c.label}: ${c.parsed.toFixed(1)}%`}}}}},plugins:[{id:"centerText",beforeDraw:function(c){const l=c.ctx;l.save();const S=(c.chartArea.left+c.chartArea.right)/2,U=(c.chartArea.top+c.chartArea.bottom)/2;l.textAlign="center",l.textBaseline="middle",l.fillStyle="#FFC246",l.font="bold 18px sans-serif",l.fillText(`${e.toFixed(1)}%`,S,U-5),l.fillStyle=F,l.font="10px sans-serif",l.fillText("CPU",S,U+12),l.restore()}}]});y.value=K(m),D.value=!1,p.value.cpuChart=!1}catch(m){console.error("Error creating CPU chart:",m),D.value=!0,p.value.cpuChart=!1}},st=()=>{if(!z.value||!s.value){p.value.memoryChart=!1;return}const a=z.value.getContext("2d");if(!a){p.value.memoryChart=!1;return}const e=s.value.memory.usage_percent,r=100-e;if(g.value)try{g.value.data.datasets[0].data=[e,r],g.value.update("none");return}catch(m){console.warn("Failed to update Memory chart, recreating...",m),g.value.destroy(),g.value=null}const n=document.documentElement.classList.contains("dark"),h=n?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)",w=n?"rgba(255, 255, 255, 0.2)":"rgba(0, 0, 0, 0.2)",F=n?"rgba(255, 255, 255, 0.6)":"rgba(0, 0, 0, 0.6)";try{const m=new j(a,{type:"doughnut",data:{labels:["Used","Available"],datasets:[{data:[e,r],backgroundColor:["#A5E5B6",h],borderColor:["#A5E5B6",w],borderWidth:2}]},options:{responsive:!0,maintainAspectRatio:!1,cutout:"70%",animation:{animateRotate:!1,animateScale:!1,duration:0},plugins:{legend:{display:!1},tooltip:{callbacks:{label:function(c){return`${c.label}: ${c.parsed.toFixed(1)}%`}}}}},plugins:[{id:"centerText",beforeDraw:function(c){const l=c.ctx;l.save();const S=(c.chartArea.left+c.chartArea.right)/2,U=(c.chartArea.top+c.chartArea.bottom)/2;l.textAlign="center",l.textBaseline="middle",l.fillStyle="#A5E5B6",l.font="bold 18px sans-serif",l.fillText(`${e.toFixed(1)}%`,S,U-5),l.fillStyle=F,l.font="10px sans-serif",l.fillText("Memory",S,U+12),l.restore()}}]});g.value=K(m),R.value=!1,p.value.memoryChart=!1}catch(m){console.error("Error creating Memory chart:",m),R.value=!0,p.value.memoryChart=!1}},rt=()=>{if(!k.value||!s.value)return;const e=document.documentElement.classList.contains("dark")?"rgba(255, 255, 255, 0.8)":"rgba(0, 0, 0, 0.8)";try{O(()=>import("./plotly.min-DO11Gp-n.js").then(r=>r.p),__vite__mapDeps([0,1])).then(r=>{const n=r.default||r,h=s.value.disk,w=[{type:"pie",labels:["Used","Free"],values:[h.used,h.free],marker:{colors:["#FB787B","#A5E5B6"]},hovertemplate:"%{label}
Size: %{value}
Percentage: %{percent}",textinfo:"label+percent",textposition:"auto",hole:.4}],F={title:{text:"",font:{color:e}},paper_bgcolor:"rgba(0,0,0,0)",plot_bgcolor:"rgba(0,0,0,0)",font:{color:e,size:11},margin:{t:20,b:20,l:20,r:20},showlegend:!0,legend:{orientation:"h",x:0,y:-.2,font:{color:e,size:10}}},m={responsive:!0,displayModeBar:!1,staticPlot:!1};n.newPlot(k.value,w,F,m)})}catch(r){console.error("Error creating disk chart:",r)}},q=()=>{try{if(y.value&&(y.value.destroy(),y.value=null),g.value&&(g.value.destroy(),g.value=null),k.value)try{O(()=>import("./plotly.min-DO11Gp-n.js").then(a=>a.p),__vite__mapDeps([0,1])).then(a=>{const e=a?.default||a;e?.purge&&e.purge(k.value)}).catch(()=>{})}catch{}}catch(a){console.error("Error destroying charts:",a)}},J=new MutationObserver(a=>{a.forEach(e=>{e.attributeName==="class"&&(q(),N(()=>{H()}))})});return nt(async()=>{await N(),I(),L.value=window.setInterval(I,5e3),J.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]}),window.addEventListener("resize",()=>{setTimeout(()=>{X(y.value)?.resize(),X(g.value)?.resize();try{O(()=>import("./plotly.min-DO11Gp-n.js").then(a=>a.p),__vite__mapDeps([0,1])).then(a=>{const e=a?.default||a;e?.Plots&&e.Plots.resize(k.value)}).catch(()=>{})}catch{}},100)})}),lt(()=>{L.value&&clearInterval(L.value),J.disconnect(),q(),window.removeEventListener("resize",()=>{})}),(a,e)=>(i(),d("div",Ct,[e[28]||(e[28]=t("div",{class:"flex justify-between items-center"},[t("h2",{class:"text-2xl font-bold text-content-primary dark:text-content-primary"},"System Statistics"),t("div",{class:"text-content-secondary dark:text-content-muted text-sm"}," Updates every 5 seconds ")],-1)),t("div",wt,[A(P,{title:"CPU Usage",value:`${B.value.cpuUsage.toFixed(1)}%`,color:"#FFC246",data:E.value.cpu},null,8,["value","data"]),A(P,{title:"Memory Usage",value:`${B.value.memoryUsage.toFixed(1)}%`,color:"#A5E5B6",data:E.value.memory},null,8,["value","data"]),A(P,{title:"Disk Usage",value:`${B.value.diskUsage.toFixed(1)}%`,color:"#FB787B",data:E.value.disk},null,8,["value","data"]),A(P,{title:"Uptime",value:Z(B.value.uptime),color:"#EBA0FC",data:E.value.network},null,8,["value","data"])]),t("div",Ft,[t("div",St,[e[6]||(e[6]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"CPU Performance",-1)),t("div",Ut,[t("canvas",{ref_key:"cpuCanvasRef",ref:T,class:"w-full h-full relative z-10"},null,512),p.value.cpuChart?(i(),d("div",Bt,e[0]||(e[0]=[t("div",{class:"text-center"},[t("div",{class:"animate-spin w-6 h-6 border-2 border-stroke-subtle dark:border-stroke/20 border-t-orange-400 rounded-full mx-auto mb-2"}),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Loading CPU data...")],-1)]))):v("",!0),D.value&&!p.value.cpuChart?(i(),d("div",Et,e[1]||(e[1]=[t("div",{class:"text-center"},[t("div",{class:"text-red-500 dark:text-red-400 text-sm mb-1"},"No Data Available"),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"CPU data not found")],-1)]))):v("",!0)]),s.value?(i(),d("div",At,[t("div",null,[e[2]||(e[2]=t("div",{class:"text-content-secondary dark:text-content-muted"},"CPU Count",-1)),t("div",Pt,o(s.value.cpu.count)+" cores",1)]),t("div",null,[e[3]||(e[3]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Frequency",-1)),t("div",Lt,o(s.value.cpu.frequency.toFixed(0))+" MHz",1)]),t("div",null,[e[4]||(e[4]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Load (1m)",-1)),t("div",Mt,o(s.value.cpu.load_avg["1min"].toFixed(2)),1)]),t("div",null,[e[5]||(e[5]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Load (5m)",-1)),t("div",Dt,o(s.value.cpu.load_avg["5min"].toFixed(2)),1)])])):v("",!0)]),t("div",Rt,[e[13]||(e[13]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Memory Usage",-1)),t("div",Tt,[t("canvas",{ref_key:"memoryCanvasRef",ref:z,class:"w-full h-full relative z-10"},null,512),p.value.memoryChart?(i(),d("div",zt,e[7]||(e[7]=[t("div",{class:"text-center"},[t("div",{class:"animate-spin w-6 h-6 border-2 border-stroke-subtle dark:border-stroke/20 border-t-green-400 rounded-full mx-auto mb-2"}),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Loading memory data...")],-1)]))):v("",!0),R.value&&!p.value.memoryChart?(i(),d("div",$t,e[8]||(e[8]=[t("div",{class:"text-center"},[t("div",{class:"text-red-500 dark:text-red-400 text-sm mb-1"},"No Data Available"),t("div",{class:"text-content-secondary dark:text-content-muted text-xs"},"Memory data not found")],-1)]))):v("",!0)]),s.value?(i(),d("div",It,[t("div",null,[e[9]||(e[9]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Total",-1)),t("div",Nt,o(f(s.value.memory.total)),1)]),t("div",null,[e[10]||(e[10]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Used",-1)),t("div",Ot,o(f(s.value.memory.used)),1)]),t("div",null,[e[11]||(e[11]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Available",-1)),t("div",Vt,o(f(s.value.memory.available)),1)]),t("div",null,[e[12]||(e[12]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Usage",-1)),t("div",jt,o(s.value.memory.usage_percent.toFixed(1))+"%",1)])])):v("",!0)])]),t("div",Ht,[t("div",qt,[e[17]||(e[17]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Storage Usage",-1)),t("div",Jt,[t("div",{ref_key:"diskCanvasRef",ref:k,class:"w-full h-full"},null,512)]),s.value?(i(),d("div",Wt,[t("div",Xt,[e[14]||(e[14]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Total",-1)),t("div",Yt,o(f(s.value.disk.total)),1)]),t("div",Gt,[e[15]||(e[15]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Used",-1)),t("div",Kt,o(f(s.value.disk.used)),1)]),t("div",Qt,[e[16]||(e[16]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Free",-1)),t("div",Zt,o(f(s.value.disk.free)),1)])])):v("",!0)]),t("div",te,[e[23]||(e[23]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Network Statistics",-1)),s.value?(i(),d("div",ee,[t("div",ae,[t("div",null,[e[18]||(e[18]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Bytes Sent",-1)),t("div",se,o(f(s.value.network.bytes_sent)),1)]),t("div",null,[e[19]||(e[19]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Bytes Received",-1)),t("div",re,o(f(s.value.network.bytes_recv)),1)]),t("div",null,[e[20]||(e[20]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Packets Sent",-1)),t("div",oe,o(s.value.network.packets_sent.toLocaleString()),1)]),t("div",null,[e[21]||(e[21]=t("div",{class:"text-content-secondary dark:text-content-muted"},"Packets Received",-1)),t("div",ne,o(s.value.network.packets_recv.toLocaleString()),1)])]),s.value.temperatures&&Object.keys(s.value.temperatures).length>0?(i(),d("div",le,[e[22]||(e[22]=t("div",{class:"text-content-secondary dark:text-content-muted mb-2"},"System Temperatures",-1)),t("div",de,[(i(!0),d(Y,null,G(s.value.temperatures,(r,n)=>(i(),d("div",{key:n},[t("span",ie,o(n)+":",1),t("span",ce,o(r.toFixed(1))+"°C",1)]))),128))])])):v("",!0)])):v("",!0)])]),t("div",ue,[e[25]||(e[25]=t("h3",{class:"text-content-primary dark:text-content-primary text-xl font-semibold mb-4"},"Top Processes",-1)),b.value?.processes&&b.value.processes.length>0?(i(),d("div",me,[t("table",ve,[e[24]||(e[24]=t("thead",null,[t("tr",{class:"border-b border-stroke-subtle dark:border-stroke/10"},[t("th",{class:"text-left text-content-secondary dark:text-content-muted py-2"},"PID"),t("th",{class:"text-left text-content-secondary dark:text-content-muted py-2"},"Name"),t("th",{class:"text-center text-content-secondary dark:text-content-muted py-2"},"CPU %"),t("th",{class:"text-center text-content-secondary dark:text-content-muted py-2"},"Memory %"),t("th",{class:"text-right text-content-secondary dark:text-content-muted py-2"},"Memory")])],-1)),t("tbody",null,[(i(!0),d(Y,null,G(b.value.processes.slice(0,10),r=>(i(),d("tr",{key:r.pid,class:"border-b border-stroke-subtle dark:border-white/5 process-row"},[t("td",pe,o(r.pid),1),t("td",xe,o(r.name),1),t("td",ye,[t("span",{class:V(["cpu-value",{"value-updated":$(r,"cpu_percent")}])},o(r.cpu_percent.toFixed(1))+"% ",3)]),t("td",ge,[t("span",{class:V(["memory-value",{"value-updated":$(r,"memory_percent")}])},o(r.memory_percent.toFixed(1))+"% ",3)]),t("td",fe,[t("span",{class:V({"value-updated":$(r,"memory_mb")})},o(r.memory_mb.toFixed(1))+" MB ",3)])]))),128))])]),b.value.total_processes?(i(),d("div",be," Showing top 10 of "+o(b.value.total_processes)+" total processes ",1)):v("",!0)])):_.value?v("",!0):(i(),d("div",ke," No process data available "))]),_.value?(i(),d("div",he,e[26]||(e[26]=[t("div",{class:"text-content-secondary dark:text-content-muted mb-2"},"Loading system statistics...",-1),t("div",{class:"animate-spin w-8 h-8 border-2 border-stroke-subtle dark:border-stroke/20 border-t-gray-900 dark:border-t-white/70 rounded-full mx-auto"},null,-1)]))):v("",!0),C.value?(i(),d("div",_e,[e[27]||(e[27]=t("div",{class:"text-red-500 dark:text-red-400 mb-2"},"Failed to load system statistics",-1)),t("p",Ce,o(C.value),1),t("button",{onClick:I,class:"mt-4 px-4 py-2 bg-purple-500/20 dark:bg-accent-purple/20 hover:bg-purple-500/30 dark:hover:bg-accent-purple/30 text-content-primary dark:text-content-primary rounded-lg border border-purple-500/50 dark:border-accent-purple/50 transition-colors"}," Retry ")])):v("",!0)]))}}),Ee=dt(we,[["__scopeId","data-v-eab6d04d"]]);export{Ee as default}; +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/SystemStats-BCpuZIIB.js diff --git a/repeater/web/html/assets/Terminal-BAoTtMQy.js b/repeater/web/html/assets/Terminal-BAoTtMQy.js index 971020a..d2488a4 100644 --- a/repeater/web/html/assets/Terminal-BAoTtMQy.js +++ b/repeater/web/html/assets/Terminal-BAoTtMQy.js @@ -1,4 +1,8 @@ +<<<<<<<< HEAD:repeater/web/html/assets/Terminal-BAoTtMQy.js import{L as J,a as zl,r as ut,o as Hl,$ as Ul,P as Rn,D as ql,b as tt,e as Z,g as Yt,t as Is,w as Kl,v as Vl,X as Ji,j as Tn,s as jl,p as it,x as Gl}from"./index-DyUIpN7m.js";/** +======== +import{L as J,a as zl,r as ut,o as Hl,$ as Ul,P as Rn,D as ql,b as tt,e as Z,g as Yt,t as Is,w as Kl,v as Vl,X as Ji,j as Tn,s as jl,p as it,x as Gl}from"./index-BfUIlcDy.js";/** +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/Terminal-Z9bSifl0.js * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved. * @license MIT * diff --git a/repeater/web/html/assets/chartjs-adapter-date-fns.esm-BYg_FBhT.js b/repeater/web/html/assets/chartjs-adapter-date-fns.esm-BYg_FBhT.js index 6b7af6a..3d4e6c9 100644 --- a/repeater/web/html/assets/chartjs-adapter-date-fns.esm-BYg_FBhT.js +++ b/repeater/web/html/assets/chartjs-adapter-date-fns.esm-BYg_FBhT.js @@ -1,4 +1,8 @@ +<<<<<<<< HEAD:repeater/web/html/assets/chartjs-adapter-date-fns.esm-BYg_FBhT.js import{a as Ae,c as j,b as O,e as I,g as U,t as Z,n as ye,F as ge,p as Y,x as Ge}from"./index-DyUIpN7m.js";import{g as Ve}from"./chart-B185MtDy.js";const ze={class:"sparkline-card"},je={class:"card-header"},Ue={class:"card-title"},Ze={key:0,class:"card-subtitle"},Je={key:0,class:"card-chart"},Ke={key:0,class:"chart-loader"},Se={key:1,class:"chart-text"},et={class:"percent-value"},tt=["id","viewBox"],nt=["d","fill"],rt=["d","stroke"],J=100,K=40,at=Ae({name:"SparklineChart",__name:"Sparkline",props:{title:{},value:{},color:{},data:{default:()=>[]},showChart:{type:Boolean,default:!0},variant:{default:"smooth"},loading:{type:Boolean,default:!1},centerText:{default:""},subtitle:{default:""}},setup(r){const e=r,t=i=>{if(i.length<3)return i;const d=Math.min(15,Math.max(3,Math.floor(i.length*.2))),f=[];for(let D=0;DR+H,0)/k.length)}const y=Math.min(10,f.length),T=f.length/y,N=[];for(let D=0;D!e.data||e.data.length===0?[]:e.variant==="smooth"?t(e.data):e.data),a=i=>{if(i.length<2)return"";const d=Math.max(...i),f=Math.min(...i),y=d-f||1,T=e.variant==="classic"?4:2;let N="";return i.forEach((D,P)=>{const l=P/(i.length-1)*J,w=(D-f)/y,k=T+(K-T*2)*(1-w);if(P===0)N+=`M ${l.toFixed(2)} ${k.toFixed(2)}`;else{const H=((P-1)/(i.length-1)*J+l)/2;N+=` Q ${H.toFixed(2)} ${k.toFixed(2)} ${l.toFixed(2)} ${k.toFixed(2)}`}}),N},s=j(()=>a(n.value)),o=j(()=>s.value?`${s.value} L ${J} ${K} L 0 ${K} Z`:""),c=j(()=>`sparkline-${e.title.replace(/\s+/g,"-").toLowerCase()}`);return(i,d)=>(Y(),O("div",ze,[I("div",je,[I("div",null,[I("p",Ue,Z(i.title),1),i.subtitle?(Y(),O("p",Ze,Z(i.subtitle),1)):U("",!0)]),I("span",{class:"card-value",style:ye({color:i.color})},Z(typeof i.value=="number"?i.value.toLocaleString():i.value),5)]),i.showChart?(Y(),O("div",Je,[i.loading&&i.variant==="classic"?(Y(),O("div",Ke,[I("div",{class:"loader-spinner",style:ye({borderTopColor:i.color})},null,4)])):i.centerText?(Y(),O("div",Se,[I("span",et,Z(i.centerText),1)])):(Y(),O("svg",{key:2,id:c.value,class:"chart-svg",viewBox:`0 0 ${J} ${K}`,preserveAspectRatio:"none"},[i.variant==="classic"?(Y(),O(ge,{key:0},[n.value.length>1?(Y(),O("path",{key:0,d:o.value,fill:i.color,"fill-opacity":"0.8",class:"sparkline-path"},null,8,nt)):U("",!0)],64)):(Y(),O(ge,{key:1},[n.value.length>1?(Y(),O("path",{key:0,d:s.value,stroke:i.color,"stroke-width":"2.5","stroke-linecap":"round","stroke-linejoin":"round",fill:"none",class:"sparkline-path"},null,8,rt)):U("",!0)],64))],8,tt))])):U("",!0)]))}}),Vr=Ge(at,[["__scopeId","data-v-257cbdca"]]),Te=6048e5,st=864e5,G=6e4,V=36e5,ot=1e3,pe=Symbol.for("constructDateFrom");function p(r,e){return typeof r=="function"?r(e):r&&typeof r=="object"&&pe in r?r[pe](e):r instanceof Date?new r.constructor(e):new Date(e)}function u(r,e){return p(e||r,r)}function ne(r,e,t){const n=u(r,t?.in);return isNaN(e)?p(t?.in||r,NaN):(e&&n.setDate(n.getDate()+e),n)}function ce(r,e,t){const n=u(r,t?.in);if(isNaN(e))return p(r,NaN);if(!e)return n;const a=n.getDate(),s=p(r,n.getTime());s.setMonth(n.getMonth()+e+1,0);const o=s.getDate();return a>=o?s:(n.setFullYear(s.getFullYear(),s.getMonth(),a),n)}function ue(r,e,t){return p(r,+u(r)+e)}function it(r,e,t){return ue(r,e*V)}let ct={};function F(){return ct}function W(r,e){const t=F(),n=e?.weekStartsOn??e?.locale?.options?.weekStartsOn??t.weekStartsOn??t.locale?.options?.weekStartsOn??0,a=u(r,e?.in),s=a.getDay(),o=(s=s.getTime()?n+1:t.getTime()>=c.getTime()?n:n-1}function ee(r){const e=u(r),t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()));return t.setUTCFullYear(e.getFullYear()),+r-+t}function C(r,...e){const t=p.bind(null,e.find(n=>typeof n=="object"));return e.map(t)}function se(r,e){const t=u(r,e?.in);return t.setHours(0,0,0,0),t}function Oe(r,e,t){const[n,a]=C(t?.in,r,e),s=se(n),o=se(a),c=+s-ee(s),i=+o-ee(o);return Math.round((c-i)/st)}function ut(r,e){const t=Pe(r,e),n=p(r,0);return n.setFullYear(t,0,4),n.setHours(0,0,0,0),Q(n)}function dt(r,e,t){const n=u(r,t?.in);return n.setTime(n.getTime()+e*G),n}function lt(r,e,t){return ce(r,e*3,t)}function ft(r,e,t){return ue(r,e*1e3)}function ht(r,e,t){return ne(r,e*7,t)}function mt(r,e,t){return ce(r,e*12,t)}function A(r,e){const t=+u(r)-+u(e);return t<0?-1:t>0?1:t}function wt(r){return r instanceof Date||typeof r=="object"&&Object.prototype.toString.call(r)==="[object Date]"}function Ye(r){return!(!wt(r)&&typeof r!="number"||isNaN(+u(r)))}function yt(r,e,t){const[n,a]=C(t?.in,r,e),s=n.getFullYear()-a.getFullYear(),o=n.getMonth()-a.getMonth();return s*12+o}function gt(r,e,t){const[n,a]=C(t?.in,r,e);return n.getFullYear()-a.getFullYear()}function ve(r,e,t){const[n,a]=C(t?.in,r,e),s=be(n,a),o=Math.abs(Oe(n,a));n.setDate(n.getDate()-s*o);const c=+(be(n,a)===-s),i=s*(o-c);return i===0?0:i}function be(r,e){const t=r.getFullYear()-e.getFullYear()||r.getMonth()-e.getMonth()||r.getDate()-e.getDate()||r.getHours()-e.getHours()||r.getMinutes()-e.getMinutes()||r.getSeconds()-e.getSeconds()||r.getMilliseconds()-e.getMilliseconds();return t<0?-1:t>0?1:t}function z(r){return e=>{const n=(r?Math[r]:Math.trunc)(e);return n===0?0:n}}function pt(r,e,t){const[n,a]=C(t?.in,r,e),s=(+n-+a)/V;return z(t?.roundingMethod)(s)}function de(r,e){return+u(r)-+u(e)}function bt(r,e,t){const n=de(r,e)/G;return z(t?.roundingMethod)(n)}function _e(r,e){const t=u(r,e?.in);return t.setHours(23,59,59,999),t}function We(r,e){const t=u(r,e?.in),n=t.getMonth();return t.setFullYear(t.getFullYear(),n+1,0),t.setHours(23,59,59,999),t}function xt(r,e){const t=u(r,e?.in);return+_e(t,e)==+We(t,e)}function Ne(r,e,t){const[n,a,s]=C(t?.in,r,r,e),o=A(a,s),c=Math.abs(yt(a,s));if(c<1)return 0;a.getMonth()===1&&a.getDate()>27&&a.setDate(30),a.setMonth(a.getMonth()-o*c);let i=A(a,s)===-o;xt(n)&&c===1&&A(n,s)===1&&(i=!1);const d=o*(c-+i);return d===0?0:d}function Mt(r,e,t){const n=Ne(r,e,t)/3;return z(t?.roundingMethod)(n)}function Dt(r,e,t){const n=de(r,e)/1e3;return z(t?.roundingMethod)(n)}function kt(r,e,t){const n=ve(r,e,t)/7;return z(t?.roundingMethod)(n)}function Tt(r,e,t){const[n,a]=C(t?.in,r,e),s=A(n,a),o=Math.abs(gt(n,a));n.setFullYear(1584),a.setFullYear(1584);const c=A(n,a)===-s,i=s*(o-+c);return i===0?0:i}function Pt(r,e){const t=u(r,e?.in),n=t.getMonth(),a=n-n%3;return t.setMonth(a,1),t.setHours(0,0,0,0),t}function Ot(r,e){const t=u(r,e?.in);return t.setDate(1),t.setHours(0,0,0,0),t}function Yt(r,e){const t=u(r,e?.in),n=t.getFullYear();return t.setFullYear(n+1,0,0),t.setHours(23,59,59,999),t}function Ee(r,e){const t=u(r,e?.in);return t.setFullYear(t.getFullYear(),0,1),t.setHours(0,0,0,0),t}function vt(r,e){const t=u(r,e?.in);return t.setMinutes(59,59,999),t}function _t(r,e){const t=F(),n=t.weekStartsOn??t.locale?.options?.weekStartsOn??0,a=u(r,e?.in),s=a.getDay(),o=(s{let n;const a=Ht[r];return typeof a=="string"?n=a:e===1?n=a.one:n=a.other.replace("{{count}}",e.toString()),t?.addSuffix?t.comparison&&t.comparison>0?"in "+n:n+" ago":n};function re(r){return(e={})=>{const t=e.width?String(e.width):r.defaultWidth;return r.formats[t]||r.formats[r.defaultWidth]}}const Ft={full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},Ct={full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},It={full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},Lt={date:re({formats:Ft,defaultWidth:"full"}),time:re({formats:Ct,defaultWidth:"full"}),dateTime:re({formats:It,defaultWidth:"full"})},Qt={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"},Rt=(r,e,t,n)=>Qt[r];function B(r){return(e,t)=>{const n=t?.context?String(t.context):"standalone";let a;if(n==="formatting"&&r.formattingValues){const o=r.defaultFormattingWidth||r.defaultWidth,c=t?.width?String(t.width):o;a=r.formattingValues[c]||r.formattingValues[o]}else{const o=r.defaultWidth,c=t?.width?String(t.width):r.defaultWidth;a=r.values[c]||r.values[o]}const s=r.argumentCallback?r.argumentCallback(e):e;return a[s]}}const Bt={narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},Xt={narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},$t={narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},At={narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},Gt={narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},Vt={narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},zt=(r,e)=>{const t=Number(r),n=t%100;if(n>20||n<10)switch(n%10){case 1:return t+"st";case 2:return t+"nd";case 3:return t+"rd"}return t+"th"},jt={ordinalNumber:zt,era:B({values:Bt,defaultWidth:"wide"}),quarter:B({values:Xt,defaultWidth:"wide",argumentCallback:r=>r-1}),month:B({values:$t,defaultWidth:"wide"}),day:B({values:At,defaultWidth:"wide"}),dayPeriod:B({values:Gt,defaultWidth:"wide",formattingValues:Vt,defaultFormattingWidth:"wide"})};function X(r){return(e,t={})=>{const n=t.width,a=n&&r.matchPatterns[n]||r.matchPatterns[r.defaultMatchWidth],s=e.match(a);if(!s)return null;const o=s[0],c=n&&r.parsePatterns[n]||r.parsePatterns[r.defaultParseWidth],i=Array.isArray(c)?Zt(c,y=>y.test(o)):Ut(c,y=>y.test(o));let d;d=r.valueCallback?r.valueCallback(i):i,d=t.valueCallback?t.valueCallback(d):d;const f=e.slice(o.length);return{value:d,rest:f}}}function Ut(r,e){for(const t in r)if(Object.prototype.hasOwnProperty.call(r,t)&&e(r[t]))return t}function Zt(r,e){for(let t=0;t{const n=e.match(r.matchPattern);if(!n)return null;const a=n[0],s=e.match(r.parsePattern);if(!s)return null;let o=r.valueCallback?r.valueCallback(s[0]):s[0];o=t.valueCallback?t.valueCallback(o):o;const c=e.slice(a.length);return{value:o,rest:c}}}const Kt=/^(\d+)(th|st|nd|rd)?/i,St=/\d+/i,en={narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},tn={any:[/^b/i,/^(a|c)/i]},nn={narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},rn={any:[/1/i,/2/i,/3/i,/4/i]},an={narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},sn={narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},on={narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},cn={narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},un={narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},dn={any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},ln={ordinalNumber:Jt({matchPattern:Kt,parsePattern:St,valueCallback:r=>parseInt(r,10)}),era:X({matchPatterns:en,defaultMatchWidth:"wide",parsePatterns:tn,defaultParseWidth:"any"}),quarter:X({matchPatterns:nn,defaultMatchWidth:"wide",parsePatterns:rn,defaultParseWidth:"any",valueCallback:r=>r+1}),month:X({matchPatterns:an,defaultMatchWidth:"wide",parsePatterns:sn,defaultParseWidth:"any"}),day:X({matchPatterns:on,defaultMatchWidth:"wide",parsePatterns:cn,defaultParseWidth:"any"}),dayPeriod:X({matchPatterns:un,defaultMatchWidth:"any",parsePatterns:dn,defaultParseWidth:"any"})},He={code:"en-US",formatDistance:qt,formatLong:Lt,formatRelative:Rt,localize:jt,match:ln,options:{weekStartsOn:0,firstWeekContainsDate:1}};function fn(r,e){const t=u(r,e?.in);return Oe(t,Ee(t))+1}function qe(r,e){const t=u(r,e?.in),n=+Q(t)-+ut(t);return Math.round(n/Te)+1}function le(r,e){const t=u(r,e?.in),n=t.getFullYear(),a=F(),s=e?.firstWeekContainsDate??e?.locale?.options?.firstWeekContainsDate??a.firstWeekContainsDate??a.locale?.options?.firstWeekContainsDate??1,o=p(e?.in||r,0);o.setFullYear(n+1,0,s),o.setHours(0,0,0,0);const c=W(o,e),i=p(e?.in||r,0);i.setFullYear(n,0,s),i.setHours(0,0,0,0);const d=W(i,e);return+t>=+c?n+1:+t>=+d?n:n-1}function hn(r,e){const t=F(),n=e?.firstWeekContainsDate??e?.locale?.options?.firstWeekContainsDate??t.firstWeekContainsDate??t.locale?.options?.firstWeekContainsDate??1,a=le(r,e),s=p(e?.in||r,0);return s.setFullYear(a,0,n),s.setHours(0,0,0,0),W(s,e)}function Fe(r,e){const t=u(r,e?.in),n=+W(t,e)-+hn(t,e);return Math.round(n/Te)+1}function m(r,e){const t=r<0?"-":"",n=Math.abs(r).toString().padStart(e,"0");return t+n}const E={y(r,e){const t=r.getFullYear(),n=t>0?t:1-t;return m(e==="yy"?n%100:n,e.length)},M(r,e){const t=r.getMonth();return e==="M"?String(t+1):m(t+1,2)},d(r,e){return m(r.getDate(),e.length)},a(r,e){const t=r.getHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return t.toUpperCase();case"aaa":return t;case"aaaaa":return t[0];case"aaaa":default:return t==="am"?"a.m.":"p.m."}},h(r,e){return m(r.getHours()%12||12,e.length)},H(r,e){return m(r.getHours(),e.length)},m(r,e){return m(r.getMinutes(),e.length)},s(r,e){return m(r.getSeconds(),e.length)},S(r,e){const t=e.length,n=r.getMilliseconds(),a=Math.trunc(n*Math.pow(10,t-3));return m(a,e.length)}},L={midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},xe={G:function(r,e,t){const n=r.getFullYear()>0?1:0;switch(e){case"G":case"GG":case"GGG":return t.era(n,{width:"abbreviated"});case"GGGGG":return t.era(n,{width:"narrow"});case"GGGG":default:return t.era(n,{width:"wide"})}},y:function(r,e,t){if(e==="yo"){const n=r.getFullYear(),a=n>0?n:1-n;return t.ordinalNumber(a,{unit:"year"})}return E.y(r,e)},Y:function(r,e,t,n){const a=le(r,n),s=a>0?a:1-a;if(e==="YY"){const o=s%100;return m(o,2)}return e==="Yo"?t.ordinalNumber(s,{unit:"year"}):m(s,e.length)},R:function(r,e){const t=Pe(r);return m(t,e.length)},u:function(r,e){const t=r.getFullYear();return m(t,e.length)},Q:function(r,e,t){const n=Math.ceil((r.getMonth()+1)/3);switch(e){case"Q":return String(n);case"QQ":return m(n,2);case"Qo":return t.ordinalNumber(n,{unit:"quarter"});case"QQQ":return t.quarter(n,{width:"abbreviated",context:"formatting"});case"QQQQQ":return t.quarter(n,{width:"narrow",context:"formatting"});case"QQQQ":default:return t.quarter(n,{width:"wide",context:"formatting"})}},q:function(r,e,t){const n=Math.ceil((r.getMonth()+1)/3);switch(e){case"q":return String(n);case"qq":return m(n,2);case"qo":return t.ordinalNumber(n,{unit:"quarter"});case"qqq":return t.quarter(n,{width:"abbreviated",context:"standalone"});case"qqqqq":return t.quarter(n,{width:"narrow",context:"standalone"});case"qqqq":default:return t.quarter(n,{width:"wide",context:"standalone"})}},M:function(r,e,t){const n=r.getMonth();switch(e){case"M":case"MM":return E.M(r,e);case"Mo":return t.ordinalNumber(n+1,{unit:"month"});case"MMM":return t.month(n,{width:"abbreviated",context:"formatting"});case"MMMMM":return t.month(n,{width:"narrow",context:"formatting"});case"MMMM":default:return t.month(n,{width:"wide",context:"formatting"})}},L:function(r,e,t){const n=r.getMonth();switch(e){case"L":return String(n+1);case"LL":return m(n+1,2);case"Lo":return t.ordinalNumber(n+1,{unit:"month"});case"LLL":return t.month(n,{width:"abbreviated",context:"standalone"});case"LLLLL":return t.month(n,{width:"narrow",context:"standalone"});case"LLLL":default:return t.month(n,{width:"wide",context:"standalone"})}},w:function(r,e,t,n){const a=Fe(r,n);return e==="wo"?t.ordinalNumber(a,{unit:"week"}):m(a,e.length)},I:function(r,e,t){const n=qe(r);return e==="Io"?t.ordinalNumber(n,{unit:"week"}):m(n,e.length)},d:function(r,e,t){return e==="do"?t.ordinalNumber(r.getDate(),{unit:"date"}):E.d(r,e)},D:function(r,e,t){const n=fn(r);return e==="Do"?t.ordinalNumber(n,{unit:"dayOfYear"}):m(n,e.length)},E:function(r,e,t){const n=r.getDay();switch(e){case"E":case"EE":case"EEE":return t.day(n,{width:"abbreviated",context:"formatting"});case"EEEEE":return t.day(n,{width:"narrow",context:"formatting"});case"EEEEEE":return t.day(n,{width:"short",context:"formatting"});case"EEEE":default:return t.day(n,{width:"wide",context:"formatting"})}},e:function(r,e,t,n){const a=r.getDay(),s=(a-n.weekStartsOn+8)%7||7;switch(e){case"e":return String(s);case"ee":return m(s,2);case"eo":return t.ordinalNumber(s,{unit:"day"});case"eee":return t.day(a,{width:"abbreviated",context:"formatting"});case"eeeee":return t.day(a,{width:"narrow",context:"formatting"});case"eeeeee":return t.day(a,{width:"short",context:"formatting"});case"eeee":default:return t.day(a,{width:"wide",context:"formatting"})}},c:function(r,e,t,n){const a=r.getDay(),s=(a-n.weekStartsOn+8)%7||7;switch(e){case"c":return String(s);case"cc":return m(s,e.length);case"co":return t.ordinalNumber(s,{unit:"day"});case"ccc":return t.day(a,{width:"abbreviated",context:"standalone"});case"ccccc":return t.day(a,{width:"narrow",context:"standalone"});case"cccccc":return t.day(a,{width:"short",context:"standalone"});case"cccc":default:return t.day(a,{width:"wide",context:"standalone"})}},i:function(r,e,t){const n=r.getDay(),a=n===0?7:n;switch(e){case"i":return String(a);case"ii":return m(a,e.length);case"io":return t.ordinalNumber(a,{unit:"day"});case"iii":return t.day(n,{width:"abbreviated",context:"formatting"});case"iiiii":return t.day(n,{width:"narrow",context:"formatting"});case"iiiiii":return t.day(n,{width:"short",context:"formatting"});case"iiii":default:return t.day(n,{width:"wide",context:"formatting"})}},a:function(r,e,t){const a=r.getHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"});case"aaa":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"}).toLowerCase();case"aaaaa":return t.dayPeriod(a,{width:"narrow",context:"formatting"});case"aaaa":default:return t.dayPeriod(a,{width:"wide",context:"formatting"})}},b:function(r,e,t){const n=r.getHours();let a;switch(n===12?a=L.noon:n===0?a=L.midnight:a=n/12>=1?"pm":"am",e){case"b":case"bb":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"});case"bbb":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"}).toLowerCase();case"bbbbb":return t.dayPeriod(a,{width:"narrow",context:"formatting"});case"bbbb":default:return t.dayPeriod(a,{width:"wide",context:"formatting"})}},B:function(r,e,t){const n=r.getHours();let a;switch(n>=17?a=L.evening:n>=12?a=L.afternoon:n>=4?a=L.morning:a=L.night,e){case"B":case"BB":case"BBB":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"});case"BBBBB":return t.dayPeriod(a,{width:"narrow",context:"formatting"});case"BBBB":default:return t.dayPeriod(a,{width:"wide",context:"formatting"})}},h:function(r,e,t){if(e==="ho"){let n=r.getHours()%12;return n===0&&(n=12),t.ordinalNumber(n,{unit:"hour"})}return E.h(r,e)},H:function(r,e,t){return e==="Ho"?t.ordinalNumber(r.getHours(),{unit:"hour"}):E.H(r,e)},K:function(r,e,t){const n=r.getHours()%12;return e==="Ko"?t.ordinalNumber(n,{unit:"hour"}):m(n,e.length)},k:function(r,e,t){let n=r.getHours();return n===0&&(n=24),e==="ko"?t.ordinalNumber(n,{unit:"hour"}):m(n,e.length)},m:function(r,e,t){return e==="mo"?t.ordinalNumber(r.getMinutes(),{unit:"minute"}):E.m(r,e)},s:function(r,e,t){return e==="so"?t.ordinalNumber(r.getSeconds(),{unit:"second"}):E.s(r,e)},S:function(r,e){return E.S(r,e)},X:function(r,e,t){const n=r.getTimezoneOffset();if(n===0)return"Z";switch(e){case"X":return De(n);case"XXXX":case"XX":return q(n);case"XXXXX":case"XXX":default:return q(n,":")}},x:function(r,e,t){const n=r.getTimezoneOffset();switch(e){case"x":return De(n);case"xxxx":case"xx":return q(n);case"xxxxx":case"xxx":default:return q(n,":")}},O:function(r,e,t){const n=r.getTimezoneOffset();switch(e){case"O":case"OO":case"OOO":return"GMT"+Me(n,":");case"OOOO":default:return"GMT"+q(n,":")}},z:function(r,e,t){const n=r.getTimezoneOffset();switch(e){case"z":case"zz":case"zzz":return"GMT"+Me(n,":");case"zzzz":default:return"GMT"+q(n,":")}},t:function(r,e,t){const n=Math.trunc(+r/1e3);return m(n,e.length)},T:function(r,e,t){return m(+r,e.length)}};function Me(r,e=""){const t=r>0?"-":"+",n=Math.abs(r),a=Math.trunc(n/60),s=n%60;return s===0?t+String(a):t+String(a)+e+m(s,2)}function De(r,e){return r%60===0?(r>0?"-":"+")+m(Math.abs(r)/60,2):q(r,e)}function q(r,e=""){const t=r>0?"-":"+",n=Math.abs(r),a=m(Math.trunc(n/60),2),s=m(n%60,2);return t+a+e+s}const ke=(r,e)=>{switch(r){case"P":return e.date({width:"short"});case"PP":return e.date({width:"medium"});case"PPP":return e.date({width:"long"});case"PPPP":default:return e.date({width:"full"})}},Ce=(r,e)=>{switch(r){case"p":return e.time({width:"short"});case"pp":return e.time({width:"medium"});case"ppp":return e.time({width:"long"});case"pppp":default:return e.time({width:"full"})}},mn=(r,e)=>{const t=r.match(/(P+)(p+)?/)||[],n=t[1],a=t[2];if(!a)return ke(r,e);let s;switch(n){case"P":s=e.dateTime({width:"short"});break;case"PP":s=e.dateTime({width:"medium"});break;case"PPP":s=e.dateTime({width:"long"});break;case"PPPP":default:s=e.dateTime({width:"full"});break}return s.replace("{{date}}",ke(n,e)).replace("{{time}}",Ce(a,e))},oe={p:Ce,P:mn},wn=/^D+$/,yn=/^Y+$/,gn=["D","DD","YY","YYYY"];function Ie(r){return wn.test(r)}function Le(r){return yn.test(r)}function ie(r,e,t){const n=pn(r,e,t);if(console.warn(n),gn.includes(r))throw new RangeError(n)}function pn(r,e,t){const n=r[0]==="Y"?"years":"days of the month";return`Use \`${r.toLowerCase()}\` instead of \`${r}\` (in \`${e}\`) for formatting ${n} to the input \`${t}\`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md`}const bn=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,xn=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,Mn=/^'([^]*?)'?$/,Dn=/''/g,kn=/[a-zA-Z]/;function Tn(r,e,t){const n=F(),a=t?.locale??n.locale??He,s=t?.firstWeekContainsDate??t?.locale?.options?.firstWeekContainsDate??n.firstWeekContainsDate??n.locale?.options?.firstWeekContainsDate??1,o=t?.weekStartsOn??t?.locale?.options?.weekStartsOn??n.weekStartsOn??n.locale?.options?.weekStartsOn??0,c=u(r,t?.in);if(!Ye(c))throw new RangeError("Invalid time value");let i=e.match(xn).map(f=>{const y=f[0];if(y==="p"||y==="P"){const T=oe[y];return T(f,a.formatLong)}return f}).join("").match(bn).map(f=>{if(f==="''")return{isToken:!1,value:"'"};const y=f[0];if(y==="'")return{isToken:!1,value:Pn(f)};if(xe[y])return{isToken:!0,value:f};if(y.match(kn))throw new RangeError("Format string contains an unescaped latin alphabet character `"+y+"`");return{isToken:!1,value:f}});a.localize.preprocessor&&(i=a.localize.preprocessor(c,i));const d={firstWeekContainsDate:s,weekStartsOn:o,locale:a};return i.map(f=>{if(!f.isToken)return f.value;const y=f.value;(!t?.useAdditionalWeekYearTokens&&Le(y)||!t?.useAdditionalDayOfYearTokens&&Ie(y))&&ie(y,e,String(r));const T=xe[y[0]];return T(c,y,a.localize,d)}).join("")}function Pn(r){const e=r.match(Mn);return e?e[1].replace(Dn,"'"):r}function On(){return Object.assign({},F())}function Yn(r,e){const t=u(r,e?.in).getDay();return t===0?7:t}function vn(r,e){const t=_n(e)?new e(0):p(e,0);return t.setFullYear(r.getFullYear(),r.getMonth(),r.getDate()),t.setHours(r.getHours(),r.getMinutes(),r.getSeconds(),r.getMilliseconds()),t}function _n(r){return typeof r=="function"&&r.prototype?.constructor===r}const Wn=10;class Qe{subPriority=0;validate(e,t){return!0}}class Nn extends Qe{constructor(e,t,n,a,s){super(),this.value=e,this.validateValue=t,this.setValue=n,this.priority=a,s&&(this.subPriority=s)}validate(e,t){return this.validateValue(e,this.value,t)}set(e,t,n){return this.setValue(e,t,this.value,n)}}class En extends Qe{priority=Wn;subPriority=-1;constructor(e,t){super(),this.context=e||(n=>p(t,n))}set(e,t){return t.timestampIsSet?e:p(e,vn(e,this.context))}}class h{run(e,t,n,a){const s=this.parse(e,t,n,a);return s?{setter:new Nn(s.value,this.validate,this.set,this.priority,this.subPriority),rest:s.rest}:null}validate(e,t,n){return!0}}class Hn extends h{priority=140;parse(e,t,n){switch(t){case"G":case"GG":case"GGG":return n.era(e,{width:"abbreviated"})||n.era(e,{width:"narrow"});case"GGGGG":return n.era(e,{width:"narrow"});case"GGGG":default:return n.era(e,{width:"wide"})||n.era(e,{width:"abbreviated"})||n.era(e,{width:"narrow"})}}set(e,t,n){return t.era=n,e.setFullYear(n,0,1),e.setHours(0,0,0,0),e}incompatibleTokens=["R","u","t","T"]}const x={month:/^(1[0-2]|0?\d)/,date:/^(3[0-1]|[0-2]?\d)/,dayOfYear:/^(36[0-6]|3[0-5]\d|[0-2]?\d?\d)/,week:/^(5[0-3]|[0-4]?\d)/,hour23h:/^(2[0-3]|[0-1]?\d)/,hour24h:/^(2[0-4]|[0-1]?\d)/,hour11h:/^(1[0-1]|0?\d)/,hour12h:/^(1[0-2]|0?\d)/,minute:/^[0-5]?\d/,second:/^[0-5]?\d/,singleDigit:/^\d/,twoDigits:/^\d{1,2}/,threeDigits:/^\d{1,3}/,fourDigits:/^\d{1,4}/,anyDigitsSigned:/^-?\d+/,singleDigitSigned:/^-?\d/,twoDigitsSigned:/^-?\d{1,2}/,threeDigitsSigned:/^-?\d{1,3}/,fourDigitsSigned:/^-?\d{1,4}/},v={basicOptionalMinutes:/^([+-])(\d{2})(\d{2})?|Z/,basic:/^([+-])(\d{2})(\d{2})|Z/,basicOptionalSeconds:/^([+-])(\d{2})(\d{2})((\d{2}))?|Z/,extended:/^([+-])(\d{2}):(\d{2})|Z/,extendedOptionalSeconds:/^([+-])(\d{2}):(\d{2})(:(\d{2}))?|Z/};function M(r,e){return r&&{value:e(r.value),rest:r.rest}}function g(r,e){const t=e.match(r);return t?{value:parseInt(t[0],10),rest:e.slice(t[0].length)}:null}function _(r,e){const t=e.match(r);if(!t)return null;if(t[0]==="Z")return{value:0,rest:e.slice(1)};const n=t[1]==="+"?1:-1,a=t[2]?parseInt(t[2],10):0,s=t[3]?parseInt(t[3],10):0,o=t[5]?parseInt(t[5],10):0;return{value:n*(a*V+s*G+o*ot),rest:e.slice(t[0].length)}}function Re(r){return g(x.anyDigitsSigned,r)}function b(r,e){switch(r){case 1:return g(x.singleDigit,e);case 2:return g(x.twoDigits,e);case 3:return g(x.threeDigits,e);case 4:return g(x.fourDigits,e);default:return g(new RegExp("^\\d{1,"+r+"}"),e)}}function te(r,e){switch(r){case 1:return g(x.singleDigitSigned,e);case 2:return g(x.twoDigitsSigned,e);case 3:return g(x.threeDigitsSigned,e);case 4:return g(x.fourDigitsSigned,e);default:return g(new RegExp("^-?\\d{1,"+r+"}"),e)}}function fe(r){switch(r){case"morning":return 4;case"evening":return 17;case"pm":case"noon":case"afternoon":return 12;case"am":case"midnight":case"night":default:return 0}}function Be(r,e){const t=e>0,n=t?e:1-e;let a;if(n<=50)a=r||100;else{const s=n+50,o=Math.trunc(s/100)*100,c=r>=s%100;a=r+o-(c?100:0)}return t?a:1-a}function Xe(r){return r%400===0||r%4===0&&r%100!==0}class qn extends h{priority=130;incompatibleTokens=["Y","R","u","w","I","i","e","c","t","T"];parse(e,t,n){const a=s=>({year:s,isTwoDigitYear:t==="yy"});switch(t){case"y":return M(b(4,e),a);case"yo":return M(n.ordinalNumber(e,{unit:"year"}),a);default:return M(b(t.length,e),a)}}validate(e,t){return t.isTwoDigitYear||t.year>0}set(e,t,n){const a=e.getFullYear();if(n.isTwoDigitYear){const o=Be(n.year,a);return e.setFullYear(o,0,1),e.setHours(0,0,0,0),e}const s=!("era"in t)||t.era===1?n.year:1-n.year;return e.setFullYear(s,0,1),e.setHours(0,0,0,0),e}}class Fn extends h{priority=130;parse(e,t,n){const a=s=>({year:s,isTwoDigitYear:t==="YY"});switch(t){case"Y":return M(b(4,e),a);case"Yo":return M(n.ordinalNumber(e,{unit:"year"}),a);default:return M(b(t.length,e),a)}}validate(e,t){return t.isTwoDigitYear||t.year>0}set(e,t,n,a){const s=le(e,a);if(n.isTwoDigitYear){const c=Be(n.year,s);return e.setFullYear(c,0,a.firstWeekContainsDate),e.setHours(0,0,0,0),W(e,a)}const o=!("era"in t)||t.era===1?n.year:1-n.year;return e.setFullYear(o,0,a.firstWeekContainsDate),e.setHours(0,0,0,0),W(e,a)}incompatibleTokens=["y","R","u","Q","q","M","L","I","d","D","i","t","T"]}class Cn extends h{priority=130;parse(e,t){return te(t==="R"?4:t.length,e)}set(e,t,n){const a=p(e,0);return a.setFullYear(n,0,4),a.setHours(0,0,0,0),Q(a)}incompatibleTokens=["G","y","Y","u","Q","q","M","L","w","d","D","e","c","t","T"]}class In extends h{priority=130;parse(e,t){return te(t==="u"?4:t.length,e)}set(e,t,n){return e.setFullYear(n,0,1),e.setHours(0,0,0,0),e}incompatibleTokens=["G","y","Y","R","w","I","i","e","c","t","T"]}class Ln extends h{priority=120;parse(e,t,n){switch(t){case"Q":case"QQ":return b(t.length,e);case"Qo":return n.ordinalNumber(e,{unit:"quarter"});case"QQQ":return n.quarter(e,{width:"abbreviated",context:"formatting"})||n.quarter(e,{width:"narrow",context:"formatting"});case"QQQQQ":return n.quarter(e,{width:"narrow",context:"formatting"});case"QQQQ":default:return n.quarter(e,{width:"wide",context:"formatting"})||n.quarter(e,{width:"abbreviated",context:"formatting"})||n.quarter(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=1&&t<=4}set(e,t,n){return e.setMonth((n-1)*3,1),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","M","L","w","I","d","D","i","e","c","t","T"]}class Qn extends h{priority=120;parse(e,t,n){switch(t){case"q":case"qq":return b(t.length,e);case"qo":return n.ordinalNumber(e,{unit:"quarter"});case"qqq":return n.quarter(e,{width:"abbreviated",context:"standalone"})||n.quarter(e,{width:"narrow",context:"standalone"});case"qqqqq":return n.quarter(e,{width:"narrow",context:"standalone"});case"qqqq":default:return n.quarter(e,{width:"wide",context:"standalone"})||n.quarter(e,{width:"abbreviated",context:"standalone"})||n.quarter(e,{width:"narrow",context:"standalone"})}}validate(e,t){return t>=1&&t<=4}set(e,t,n){return e.setMonth((n-1)*3,1),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","Q","M","L","w","I","d","D","i","e","c","t","T"]}class Rn extends h{incompatibleTokens=["Y","R","q","Q","L","w","I","D","i","e","c","t","T"];priority=110;parse(e,t,n){const a=s=>s-1;switch(t){case"M":return M(g(x.month,e),a);case"MM":return M(b(2,e),a);case"Mo":return M(n.ordinalNumber(e,{unit:"month"}),a);case"MMM":return n.month(e,{width:"abbreviated",context:"formatting"})||n.month(e,{width:"narrow",context:"formatting"});case"MMMMM":return n.month(e,{width:"narrow",context:"formatting"});case"MMMM":default:return n.month(e,{width:"wide",context:"formatting"})||n.month(e,{width:"abbreviated",context:"formatting"})||n.month(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=0&&t<=11}set(e,t,n){return e.setMonth(n,1),e.setHours(0,0,0,0),e}}class Bn extends h{priority=110;parse(e,t,n){const a=s=>s-1;switch(t){case"L":return M(g(x.month,e),a);case"LL":return M(b(2,e),a);case"Lo":return M(n.ordinalNumber(e,{unit:"month"}),a);case"LLL":return n.month(e,{width:"abbreviated",context:"standalone"})||n.month(e,{width:"narrow",context:"standalone"});case"LLLLL":return n.month(e,{width:"narrow",context:"standalone"});case"LLLL":default:return n.month(e,{width:"wide",context:"standalone"})||n.month(e,{width:"abbreviated",context:"standalone"})||n.month(e,{width:"narrow",context:"standalone"})}}validate(e,t){return t>=0&&t<=11}set(e,t,n){return e.setMonth(n,1),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","Q","M","w","I","D","i","e","c","t","T"]}function Xn(r,e,t){const n=u(r,t?.in),a=Fe(n,t)-e;return n.setDate(n.getDate()-a*7),u(n,t?.in)}class $n extends h{priority=100;parse(e,t,n){switch(t){case"w":return g(x.week,e);case"wo":return n.ordinalNumber(e,{unit:"week"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=53}set(e,t,n,a){return W(Xn(e,n,a),a)}incompatibleTokens=["y","R","u","q","Q","M","L","I","d","D","i","t","T"]}function An(r,e,t){const n=u(r,t?.in),a=qe(n,t)-e;return n.setDate(n.getDate()-a*7),n}class Gn extends h{priority=100;parse(e,t,n){switch(t){case"I":return g(x.week,e);case"Io":return n.ordinalNumber(e,{unit:"week"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=53}set(e,t,n){return Q(An(e,n))}incompatibleTokens=["y","Y","u","q","Q","M","L","w","d","D","e","c","t","T"]}const Vn=[31,28,31,30,31,30,31,31,30,31,30,31],zn=[31,29,31,30,31,30,31,31,30,31,30,31];class jn extends h{priority=90;subPriority=1;parse(e,t,n){switch(t){case"d":return g(x.date,e);case"do":return n.ordinalNumber(e,{unit:"date"});default:return b(t.length,e)}}validate(e,t){const n=e.getFullYear(),a=Xe(n),s=e.getMonth();return a?t>=1&&t<=zn[s]:t>=1&&t<=Vn[s]}set(e,t,n){return e.setDate(n),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","Q","w","I","D","i","e","c","t","T"]}class Un extends h{priority=90;subpriority=1;parse(e,t,n){switch(t){case"D":case"DD":return g(x.dayOfYear,e);case"Do":return n.ordinalNumber(e,{unit:"date"});default:return b(t.length,e)}}validate(e,t){const n=e.getFullYear();return Xe(n)?t>=1&&t<=366:t>=1&&t<=365}set(e,t,n){return e.setMonth(0,n),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","Q","M","L","w","I","d","E","i","e","c","t","T"]}function he(r,e,t){const n=F(),a=t?.weekStartsOn??t?.locale?.options?.weekStartsOn??n.weekStartsOn??n.locale?.options?.weekStartsOn??0,s=u(r,t?.in),o=s.getDay(),i=(e%7+7)%7,d=7-a,f=e<0||e>6?e-(o+d)%7:(i+d)%7-(o+d)%7;return ne(s,f,t)}class Zn extends h{priority=90;parse(e,t,n){switch(t){case"E":case"EE":case"EEE":return n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"EEEEE":return n.day(e,{width:"narrow",context:"formatting"});case"EEEEEE":return n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"EEEE":default:return n.day(e,{width:"wide",context:"formatting"})||n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=0&&t<=6}set(e,t,n,a){return e=he(e,n,a),e.setHours(0,0,0,0),e}incompatibleTokens=["D","i","e","c","t","T"]}class Jn extends h{priority=90;parse(e,t,n,a){const s=o=>{const c=Math.floor((o-1)/7)*7;return(o+a.weekStartsOn+6)%7+c};switch(t){case"e":case"ee":return M(b(t.length,e),s);case"eo":return M(n.ordinalNumber(e,{unit:"day"}),s);case"eee":return n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"eeeee":return n.day(e,{width:"narrow",context:"formatting"});case"eeeeee":return n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"eeee":default:return n.day(e,{width:"wide",context:"formatting"})||n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=0&&t<=6}set(e,t,n,a){return e=he(e,n,a),e.setHours(0,0,0,0),e}incompatibleTokens=["y","R","u","q","Q","M","L","I","d","D","E","i","c","t","T"]}class Kn extends h{priority=90;parse(e,t,n,a){const s=o=>{const c=Math.floor((o-1)/7)*7;return(o+a.weekStartsOn+6)%7+c};switch(t){case"c":case"cc":return M(b(t.length,e),s);case"co":return M(n.ordinalNumber(e,{unit:"day"}),s);case"ccc":return n.day(e,{width:"abbreviated",context:"standalone"})||n.day(e,{width:"short",context:"standalone"})||n.day(e,{width:"narrow",context:"standalone"});case"ccccc":return n.day(e,{width:"narrow",context:"standalone"});case"cccccc":return n.day(e,{width:"short",context:"standalone"})||n.day(e,{width:"narrow",context:"standalone"});case"cccc":default:return n.day(e,{width:"wide",context:"standalone"})||n.day(e,{width:"abbreviated",context:"standalone"})||n.day(e,{width:"short",context:"standalone"})||n.day(e,{width:"narrow",context:"standalone"})}}validate(e,t){return t>=0&&t<=6}set(e,t,n,a){return e=he(e,n,a),e.setHours(0,0,0,0),e}incompatibleTokens=["y","R","u","q","Q","M","L","I","d","D","E","i","e","t","T"]}function Sn(r,e,t){const n=u(r,t?.in),a=Yn(n,t),s=e-a;return ne(n,s,t)}class er extends h{priority=90;parse(e,t,n){const a=s=>s===0?7:s;switch(t){case"i":case"ii":return b(t.length,e);case"io":return n.ordinalNumber(e,{unit:"day"});case"iii":return M(n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"}),a);case"iiiii":return M(n.day(e,{width:"narrow",context:"formatting"}),a);case"iiiiii":return M(n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"}),a);case"iiii":default:return M(n.day(e,{width:"wide",context:"formatting"})||n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"}),a)}}validate(e,t){return t>=1&&t<=7}set(e,t,n){return e=Sn(e,n),e.setHours(0,0,0,0),e}incompatibleTokens=["y","Y","u","q","Q","M","L","w","d","D","E","e","c","t","T"]}class tr extends h{priority=80;parse(e,t,n){switch(t){case"a":case"aa":case"aaa":return n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"});case"aaaaa":return n.dayPeriod(e,{width:"narrow",context:"formatting"});case"aaaa":default:return n.dayPeriod(e,{width:"wide",context:"formatting"})||n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"})}}set(e,t,n){return e.setHours(fe(n),0,0,0),e}incompatibleTokens=["b","B","H","k","t","T"]}class nr extends h{priority=80;parse(e,t,n){switch(t){case"b":case"bb":case"bbb":return n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"});case"bbbbb":return n.dayPeriod(e,{width:"narrow",context:"formatting"});case"bbbb":default:return n.dayPeriod(e,{width:"wide",context:"formatting"})||n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"})}}set(e,t,n){return e.setHours(fe(n),0,0,0),e}incompatibleTokens=["a","B","H","k","t","T"]}class rr extends h{priority=80;parse(e,t,n){switch(t){case"B":case"BB":case"BBB":return n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"});case"BBBBB":return n.dayPeriod(e,{width:"narrow",context:"formatting"});case"BBBB":default:return n.dayPeriod(e,{width:"wide",context:"formatting"})||n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"})}}set(e,t,n){return e.setHours(fe(n),0,0,0),e}incompatibleTokens=["a","b","t","T"]}class ar extends h{priority=70;parse(e,t,n){switch(t){case"h":return g(x.hour12h,e);case"ho":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=12}set(e,t,n){const a=e.getHours()>=12;return a&&n<12?e.setHours(n+12,0,0,0):!a&&n===12?e.setHours(0,0,0,0):e.setHours(n,0,0,0),e}incompatibleTokens=["H","K","k","t","T"]}class sr extends h{priority=70;parse(e,t,n){switch(t){case"H":return g(x.hour23h,e);case"Ho":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=23}set(e,t,n){return e.setHours(n,0,0,0),e}incompatibleTokens=["a","b","h","K","k","t","T"]}class or extends h{priority=70;parse(e,t,n){switch(t){case"K":return g(x.hour11h,e);case"Ko":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=11}set(e,t,n){return e.getHours()>=12&&n<12?e.setHours(n+12,0,0,0):e.setHours(n,0,0,0),e}incompatibleTokens=["h","H","k","t","T"]}class ir extends h{priority=70;parse(e,t,n){switch(t){case"k":return g(x.hour24h,e);case"ko":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=24}set(e,t,n){const a=n<=24?n%24:n;return e.setHours(a,0,0,0),e}incompatibleTokens=["a","b","h","H","K","t","T"]}class cr extends h{priority=60;parse(e,t,n){switch(t){case"m":return g(x.minute,e);case"mo":return n.ordinalNumber(e,{unit:"minute"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=59}set(e,t,n){return e.setMinutes(n,0,0),e}incompatibleTokens=["t","T"]}class ur extends h{priority=50;parse(e,t,n){switch(t){case"s":return g(x.second,e);case"so":return n.ordinalNumber(e,{unit:"second"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=59}set(e,t,n){return e.setSeconds(n,0),e}incompatibleTokens=["t","T"]}class dr extends h{priority=30;parse(e,t){const n=a=>Math.trunc(a*Math.pow(10,-t.length+3));return M(b(t.length,e),n)}set(e,t,n){return e.setMilliseconds(n),e}incompatibleTokens=["t","T"]}class lr extends h{priority=10;parse(e,t){switch(t){case"X":return _(v.basicOptionalMinutes,e);case"XX":return _(v.basic,e);case"XXXX":return _(v.basicOptionalSeconds,e);case"XXXXX":return _(v.extendedOptionalSeconds,e);case"XXX":default:return _(v.extended,e)}}set(e,t,n){return t.timestampIsSet?e:p(e,e.getTime()-ee(e)-n)}incompatibleTokens=["t","T","x"]}class fr extends h{priority=10;parse(e,t){switch(t){case"x":return _(v.basicOptionalMinutes,e);case"xx":return _(v.basic,e);case"xxxx":return _(v.basicOptionalSeconds,e);case"xxxxx":return _(v.extendedOptionalSeconds,e);case"xxx":default:return _(v.extended,e)}}set(e,t,n){return t.timestampIsSet?e:p(e,e.getTime()-ee(e)-n)}incompatibleTokens=["t","T","X"]}class hr extends h{priority=40;parse(e){return Re(e)}set(e,t,n){return[p(e,n*1e3),{timestampIsSet:!0}]}incompatibleTokens="*"}class mr extends h{priority=20;parse(e){return Re(e)}set(e,t,n){return[p(e,n),{timestampIsSet:!0}]}incompatibleTokens="*"}const wr={G:new Hn,y:new qn,Y:new Fn,R:new Cn,u:new In,Q:new Ln,q:new Qn,M:new Rn,L:new Bn,w:new $n,I:new Gn,d:new jn,D:new Un,E:new Zn,e:new Jn,c:new Kn,i:new er,a:new tr,b:new nr,B:new rr,h:new ar,H:new sr,K:new or,k:new ir,m:new cr,s:new ur,S:new dr,X:new lr,x:new fr,t:new hr,T:new mr},yr=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,gr=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,pr=/^'([^]*?)'?$/,br=/''/g,xr=/\S/,Mr=/[a-zA-Z]/;function Dr(r,e,t,n){const a=()=>p(n?.in||t,NaN),s=On(),o=n?.locale??s.locale??He,c=n?.firstWeekContainsDate??n?.locale?.options?.firstWeekContainsDate??s.firstWeekContainsDate??s.locale?.options?.firstWeekContainsDate??1,i=n?.weekStartsOn??n?.locale?.options?.weekStartsOn??s.weekStartsOn??s.locale?.options?.weekStartsOn??0;if(!e)return r?a():u(t,n?.in);const d={firstWeekContainsDate:c,weekStartsOn:i,locale:o},f=[new En(n?.in,t)],y=e.match(gr).map(l=>{const w=l[0];if(w in oe){const k=oe[w];return k(l,o.formatLong)}return l}).join("").match(yr),T=[];for(let l of y){!n?.useAdditionalWeekYearTokens&&Le(l)&&ie(l,e,r),!n?.useAdditionalDayOfYearTokens&&Ie(l)&&ie(l,e,r);const w=l[0],k=wr[w];if(k){const{incompatibleTokens:R}=k;if(Array.isArray(R)){const me=T.find(we=>R.includes(we.token)||we.token===w);if(me)throw new RangeError(`The format string mustn't contain \`${me.fullToken}\` and \`${l}\` at the same time`)}else if(k.incompatibleTokens==="*"&&T.length>0)throw new RangeError(`The format string mustn't contain \`${l}\` and any other token at the same time`);T.push({token:w,fullToken:l});const H=k.run(r,l,o.match,d);if(!H)return a();f.push(H.setter),r=H.rest}else{if(w.match(Mr))throw new RangeError("Format string contains an unescaped latin alphabet character `"+w+"`");if(l==="''"?l="'":w==="'"&&(l=kr(l)),r.indexOf(l)===0)r=r.slice(l.length);else return a()}}if(r.length>0&&xr.test(r))return a();const N=f.map(l=>l.priority).sort((l,w)=>w-l).filter((l,w,k)=>k.indexOf(l)===w).map(l=>f.filter(w=>w.priority===l).sort((w,k)=>k.subPriority-w.subPriority)).map(l=>l[0]);let D=u(t,n?.in);if(isNaN(+D))return a();const P={};for(const l of N){if(!l.validate(D,d))return a();const w=l.set(D,P,d);Array.isArray(w)?(D=w[0],Object.assign(P,w[1])):D=w}return D}function kr(r){return r.match(pr)[1].replace(br,"'")}function Tr(r,e){const t=u(r,e?.in);return t.setMinutes(0,0,0),t}function Pr(r,e){const t=u(r,e?.in);return t.setSeconds(0,0),t}function Or(r,e){const t=u(r,e?.in);return t.setMilliseconds(0),t}function Yr(r,e){const t=()=>p(e?.in,NaN),n=e?.additionalDigits??2,a=Nr(r);let s;if(a.date){const d=Er(a.date,n);s=Hr(d.restDateString,d.year)}if(!s||isNaN(+s))return t();const o=+s;let c=0,i;if(a.time&&(c=qr(a.time),isNaN(c)))return t();if(a.timezone){if(i=Fr(a.timezone),isNaN(i))return t()}else{const d=new Date(o+c),f=u(0,e?.in);return f.setFullYear(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate()),f.setHours(d.getUTCHours(),d.getUTCMinutes(),d.getUTCSeconds(),d.getUTCMilliseconds()),f}return u(o+c+i,e?.in)}const S={dateTimeDelimiter:/[T ]/,timeZoneDelimiter:/[Z ]/i,timezone:/([Z+-].*)$/},vr=/^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/,_r=/^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/,Wr=/^([+-])(\d{2})(?::?(\d{2}))?$/;function Nr(r){const e={},t=r.split(S.dateTimeDelimiter);let n;if(t.length>2)return e;if(/:/.test(t[0])?n=t[0]:(e.date=t[0],n=t[1],S.timeZoneDelimiter.test(e.date)&&(e.date=r.split(S.timeZoneDelimiter)[0],n=r.substr(e.date.length,r.length))),n){const a=S.timezone.exec(n);a?(e.time=n.replace(a[1],""),e.timezone=a[1]):e.time=n}return e}function Er(r,e){const t=new RegExp("^(?:(\\d{4}|[+-]\\d{"+(4+e)+"})|(\\d{2}|[+-]\\d{"+(2+e)+"})$)"),n=r.match(t);if(!n)return{year:NaN,restDateString:""};const a=n[1]?parseInt(n[1]):null,s=n[2]?parseInt(n[2]):null;return{year:s===null?a:s*100,restDateString:r.slice((n[1]||n[2]).length)}}function Hr(r,e){if(e===null)return new Date(NaN);const t=r.match(vr);if(!t)return new Date(NaN);const n=!!t[4],a=$(t[1]),s=$(t[2])-1,o=$(t[3]),c=$(t[4]),i=$(t[5])-1;if(n)return Rr(e,c,i)?Cr(e,c,i):new Date(NaN);{const d=new Date(0);return!Lr(e,s,o)||!Qr(e,a)?new Date(NaN):(d.setUTCFullYear(e,s,Math.max(a,o)),d)}}function $(r){return r?parseInt(r):1}function qr(r){const e=r.match(_r);if(!e)return NaN;const t=ae(e[1]),n=ae(e[2]),a=ae(e[3]);return Br(t,n,a)?t*V+n*G+a*1e3:NaN}function ae(r){return r&&parseFloat(r.replace(",","."))||0}function Fr(r){if(r==="Z")return 0;const e=r.match(Wr);if(!e)return 0;const t=e[1]==="+"?-1:1,n=parseInt(e[2]),a=e[3]&&parseInt(e[3])||0;return Xr(n,a)?t*(n*V+a*G):NaN}function Cr(r,e,t){const n=new Date(0);n.setUTCFullYear(r,0,4);const a=n.getUTCDay()||7,s=(e-1)*7+t+1-a;return n.setUTCDate(n.getUTCDate()+s),n}const Ir=[31,null,31,30,31,30,31,31,30,31,30,31];function $e(r){return r%400===0||r%4===0&&r%100!==0}function Lr(r,e,t){return e>=0&&e<=11&&t>=1&&t<=(Ir[e]||($e(r)?29:28))}function Qr(r,e){return e>=1&&e<=($e(r)?366:365)}function Rr(r,e,t){return e>=1&&e<=53&&t>=0&&t<=6}function Br(r,e,t){return r===24?e===0&&t===0:t>=0&&t<60&&e>=0&&e<60&&r>=0&&r<25}function Xr(r,e){return e>=0&&e<=59}/*! +======== +import{a as Ae,c as j,b as O,e as I,g as U,t as Z,n as ye,F as ge,p as Y,x as Ge}from"./index-BfUIlcDy.js";import{g as Ve}from"./chart-B185MtDy.js";const ze={class:"sparkline-card"},je={class:"card-header"},Ue={class:"card-title"},Ze={key:0,class:"card-subtitle"},Je={key:0,class:"card-chart"},Ke={key:0,class:"chart-loader"},Se={key:1,class:"chart-text"},et={class:"percent-value"},tt=["id","viewBox"],nt=["d","fill"],rt=["d","stroke"],J=100,K=40,at=Ae({name:"SparklineChart",__name:"Sparkline",props:{title:{},value:{},color:{},data:{default:()=>[]},showChart:{type:Boolean,default:!0},variant:{default:"smooth"},loading:{type:Boolean,default:!1},centerText:{default:""},subtitle:{default:""}},setup(r){const e=r,t=i=>{if(i.length<3)return i;const d=Math.min(15,Math.max(3,Math.floor(i.length*.2))),f=[];for(let D=0;DR+H,0)/k.length)}const y=Math.min(10,f.length),T=f.length/y,N=[];for(let D=0;D!e.data||e.data.length===0?[]:e.variant==="smooth"?t(e.data):e.data),a=i=>{if(i.length<2)return"";const d=Math.max(...i),f=Math.min(...i),y=d-f||1,T=e.variant==="classic"?4:2;let N="";return i.forEach((D,P)=>{const l=P/(i.length-1)*J,w=(D-f)/y,k=T+(K-T*2)*(1-w);if(P===0)N+=`M ${l.toFixed(2)} ${k.toFixed(2)}`;else{const H=((P-1)/(i.length-1)*J+l)/2;N+=` Q ${H.toFixed(2)} ${k.toFixed(2)} ${l.toFixed(2)} ${k.toFixed(2)}`}}),N},s=j(()=>a(n.value)),o=j(()=>s.value?`${s.value} L ${J} ${K} L 0 ${K} Z`:""),c=j(()=>`sparkline-${e.title.replace(/\s+/g,"-").toLowerCase()}`);return(i,d)=>(Y(),O("div",ze,[I("div",je,[I("div",null,[I("p",Ue,Z(i.title),1),i.subtitle?(Y(),O("p",Ze,Z(i.subtitle),1)):U("",!0)]),I("span",{class:"card-value",style:ye({color:i.color})},Z(typeof i.value=="number"?i.value.toLocaleString():i.value),5)]),i.showChart?(Y(),O("div",Je,[i.loading&&i.variant==="classic"?(Y(),O("div",Ke,[I("div",{class:"loader-spinner",style:ye({borderTopColor:i.color})},null,4)])):i.centerText?(Y(),O("div",Se,[I("span",et,Z(i.centerText),1)])):(Y(),O("svg",{key:2,id:c.value,class:"chart-svg",viewBox:`0 0 ${J} ${K}`,preserveAspectRatio:"none"},[i.variant==="classic"?(Y(),O(ge,{key:0},[n.value.length>1?(Y(),O("path",{key:0,d:o.value,fill:i.color,"fill-opacity":"0.8",class:"sparkline-path"},null,8,nt)):U("",!0)],64)):(Y(),O(ge,{key:1},[n.value.length>1?(Y(),O("path",{key:0,d:s.value,stroke:i.color,"stroke-width":"2.5","stroke-linecap":"round","stroke-linejoin":"round",fill:"none",class:"sparkline-path"},null,8,rt)):U("",!0)],64))],8,tt))])):U("",!0)]))}}),Vr=Ge(at,[["__scopeId","data-v-257cbdca"]]),Te=6048e5,st=864e5,G=6e4,V=36e5,ot=1e3,pe=Symbol.for("constructDateFrom");function p(r,e){return typeof r=="function"?r(e):r&&typeof r=="object"&&pe in r?r[pe](e):r instanceof Date?new r.constructor(e):new Date(e)}function u(r,e){return p(e||r,r)}function ne(r,e,t){const n=u(r,t?.in);return isNaN(e)?p(t?.in||r,NaN):(e&&n.setDate(n.getDate()+e),n)}function ce(r,e,t){const n=u(r,t?.in);if(isNaN(e))return p(r,NaN);if(!e)return n;const a=n.getDate(),s=p(r,n.getTime());s.setMonth(n.getMonth()+e+1,0);const o=s.getDate();return a>=o?s:(n.setFullYear(s.getFullYear(),s.getMonth(),a),n)}function ue(r,e,t){return p(r,+u(r)+e)}function it(r,e,t){return ue(r,e*V)}let ct={};function F(){return ct}function W(r,e){const t=F(),n=e?.weekStartsOn??e?.locale?.options?.weekStartsOn??t.weekStartsOn??t.locale?.options?.weekStartsOn??0,a=u(r,e?.in),s=a.getDay(),o=(s=s.getTime()?n+1:t.getTime()>=c.getTime()?n:n-1}function ee(r){const e=u(r),t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()));return t.setUTCFullYear(e.getFullYear()),+r-+t}function C(r,...e){const t=p.bind(null,e.find(n=>typeof n=="object"));return e.map(t)}function se(r,e){const t=u(r,e?.in);return t.setHours(0,0,0,0),t}function Oe(r,e,t){const[n,a]=C(t?.in,r,e),s=se(n),o=se(a),c=+s-ee(s),i=+o-ee(o);return Math.round((c-i)/st)}function ut(r,e){const t=Pe(r,e),n=p(r,0);return n.setFullYear(t,0,4),n.setHours(0,0,0,0),Q(n)}function dt(r,e,t){const n=u(r,t?.in);return n.setTime(n.getTime()+e*G),n}function lt(r,e,t){return ce(r,e*3,t)}function ft(r,e,t){return ue(r,e*1e3)}function ht(r,e,t){return ne(r,e*7,t)}function mt(r,e,t){return ce(r,e*12,t)}function A(r,e){const t=+u(r)-+u(e);return t<0?-1:t>0?1:t}function wt(r){return r instanceof Date||typeof r=="object"&&Object.prototype.toString.call(r)==="[object Date]"}function Ye(r){return!(!wt(r)&&typeof r!="number"||isNaN(+u(r)))}function yt(r,e,t){const[n,a]=C(t?.in,r,e),s=n.getFullYear()-a.getFullYear(),o=n.getMonth()-a.getMonth();return s*12+o}function gt(r,e,t){const[n,a]=C(t?.in,r,e);return n.getFullYear()-a.getFullYear()}function ve(r,e,t){const[n,a]=C(t?.in,r,e),s=be(n,a),o=Math.abs(Oe(n,a));n.setDate(n.getDate()-s*o);const c=+(be(n,a)===-s),i=s*(o-c);return i===0?0:i}function be(r,e){const t=r.getFullYear()-e.getFullYear()||r.getMonth()-e.getMonth()||r.getDate()-e.getDate()||r.getHours()-e.getHours()||r.getMinutes()-e.getMinutes()||r.getSeconds()-e.getSeconds()||r.getMilliseconds()-e.getMilliseconds();return t<0?-1:t>0?1:t}function z(r){return e=>{const n=(r?Math[r]:Math.trunc)(e);return n===0?0:n}}function pt(r,e,t){const[n,a]=C(t?.in,r,e),s=(+n-+a)/V;return z(t?.roundingMethod)(s)}function de(r,e){return+u(r)-+u(e)}function bt(r,e,t){const n=de(r,e)/G;return z(t?.roundingMethod)(n)}function _e(r,e){const t=u(r,e?.in);return t.setHours(23,59,59,999),t}function We(r,e){const t=u(r,e?.in),n=t.getMonth();return t.setFullYear(t.getFullYear(),n+1,0),t.setHours(23,59,59,999),t}function xt(r,e){const t=u(r,e?.in);return+_e(t,e)==+We(t,e)}function Ne(r,e,t){const[n,a,s]=C(t?.in,r,r,e),o=A(a,s),c=Math.abs(yt(a,s));if(c<1)return 0;a.getMonth()===1&&a.getDate()>27&&a.setDate(30),a.setMonth(a.getMonth()-o*c);let i=A(a,s)===-o;xt(n)&&c===1&&A(n,s)===1&&(i=!1);const d=o*(c-+i);return d===0?0:d}function Mt(r,e,t){const n=Ne(r,e,t)/3;return z(t?.roundingMethod)(n)}function Dt(r,e,t){const n=de(r,e)/1e3;return z(t?.roundingMethod)(n)}function kt(r,e,t){const n=ve(r,e,t)/7;return z(t?.roundingMethod)(n)}function Tt(r,e,t){const[n,a]=C(t?.in,r,e),s=A(n,a),o=Math.abs(gt(n,a));n.setFullYear(1584),a.setFullYear(1584);const c=A(n,a)===-s,i=s*(o-+c);return i===0?0:i}function Pt(r,e){const t=u(r,e?.in),n=t.getMonth(),a=n-n%3;return t.setMonth(a,1),t.setHours(0,0,0,0),t}function Ot(r,e){const t=u(r,e?.in);return t.setDate(1),t.setHours(0,0,0,0),t}function Yt(r,e){const t=u(r,e?.in),n=t.getFullYear();return t.setFullYear(n+1,0,0),t.setHours(23,59,59,999),t}function Ee(r,e){const t=u(r,e?.in);return t.setFullYear(t.getFullYear(),0,1),t.setHours(0,0,0,0),t}function vt(r,e){const t=u(r,e?.in);return t.setMinutes(59,59,999),t}function _t(r,e){const t=F(),n=t.weekStartsOn??t.locale?.options?.weekStartsOn??0,a=u(r,e?.in),s=a.getDay(),o=(s{let n;const a=Ht[r];return typeof a=="string"?n=a:e===1?n=a.one:n=a.other.replace("{{count}}",e.toString()),t?.addSuffix?t.comparison&&t.comparison>0?"in "+n:n+" ago":n};function re(r){return(e={})=>{const t=e.width?String(e.width):r.defaultWidth;return r.formats[t]||r.formats[r.defaultWidth]}}const Ft={full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},Ct={full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},It={full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},Lt={date:re({formats:Ft,defaultWidth:"full"}),time:re({formats:Ct,defaultWidth:"full"}),dateTime:re({formats:It,defaultWidth:"full"})},Qt={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"},Rt=(r,e,t,n)=>Qt[r];function B(r){return(e,t)=>{const n=t?.context?String(t.context):"standalone";let a;if(n==="formatting"&&r.formattingValues){const o=r.defaultFormattingWidth||r.defaultWidth,c=t?.width?String(t.width):o;a=r.formattingValues[c]||r.formattingValues[o]}else{const o=r.defaultWidth,c=t?.width?String(t.width):r.defaultWidth;a=r.values[c]||r.values[o]}const s=r.argumentCallback?r.argumentCallback(e):e;return a[s]}}const Bt={narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},Xt={narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},$t={narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},At={narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},Gt={narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},Vt={narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},zt=(r,e)=>{const t=Number(r),n=t%100;if(n>20||n<10)switch(n%10){case 1:return t+"st";case 2:return t+"nd";case 3:return t+"rd"}return t+"th"},jt={ordinalNumber:zt,era:B({values:Bt,defaultWidth:"wide"}),quarter:B({values:Xt,defaultWidth:"wide",argumentCallback:r=>r-1}),month:B({values:$t,defaultWidth:"wide"}),day:B({values:At,defaultWidth:"wide"}),dayPeriod:B({values:Gt,defaultWidth:"wide",formattingValues:Vt,defaultFormattingWidth:"wide"})};function X(r){return(e,t={})=>{const n=t.width,a=n&&r.matchPatterns[n]||r.matchPatterns[r.defaultMatchWidth],s=e.match(a);if(!s)return null;const o=s[0],c=n&&r.parsePatterns[n]||r.parsePatterns[r.defaultParseWidth],i=Array.isArray(c)?Zt(c,y=>y.test(o)):Ut(c,y=>y.test(o));let d;d=r.valueCallback?r.valueCallback(i):i,d=t.valueCallback?t.valueCallback(d):d;const f=e.slice(o.length);return{value:d,rest:f}}}function Ut(r,e){for(const t in r)if(Object.prototype.hasOwnProperty.call(r,t)&&e(r[t]))return t}function Zt(r,e){for(let t=0;t{const n=e.match(r.matchPattern);if(!n)return null;const a=n[0],s=e.match(r.parsePattern);if(!s)return null;let o=r.valueCallback?r.valueCallback(s[0]):s[0];o=t.valueCallback?t.valueCallback(o):o;const c=e.slice(a.length);return{value:o,rest:c}}}const Kt=/^(\d+)(th|st|nd|rd)?/i,St=/\d+/i,en={narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},tn={any:[/^b/i,/^(a|c)/i]},nn={narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},rn={any:[/1/i,/2/i,/3/i,/4/i]},an={narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},sn={narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},on={narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},cn={narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},un={narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},dn={any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},ln={ordinalNumber:Jt({matchPattern:Kt,parsePattern:St,valueCallback:r=>parseInt(r,10)}),era:X({matchPatterns:en,defaultMatchWidth:"wide",parsePatterns:tn,defaultParseWidth:"any"}),quarter:X({matchPatterns:nn,defaultMatchWidth:"wide",parsePatterns:rn,defaultParseWidth:"any",valueCallback:r=>r+1}),month:X({matchPatterns:an,defaultMatchWidth:"wide",parsePatterns:sn,defaultParseWidth:"any"}),day:X({matchPatterns:on,defaultMatchWidth:"wide",parsePatterns:cn,defaultParseWidth:"any"}),dayPeriod:X({matchPatterns:un,defaultMatchWidth:"any",parsePatterns:dn,defaultParseWidth:"any"})},He={code:"en-US",formatDistance:qt,formatLong:Lt,formatRelative:Rt,localize:jt,match:ln,options:{weekStartsOn:0,firstWeekContainsDate:1}};function fn(r,e){const t=u(r,e?.in);return Oe(t,Ee(t))+1}function qe(r,e){const t=u(r,e?.in),n=+Q(t)-+ut(t);return Math.round(n/Te)+1}function le(r,e){const t=u(r,e?.in),n=t.getFullYear(),a=F(),s=e?.firstWeekContainsDate??e?.locale?.options?.firstWeekContainsDate??a.firstWeekContainsDate??a.locale?.options?.firstWeekContainsDate??1,o=p(e?.in||r,0);o.setFullYear(n+1,0,s),o.setHours(0,0,0,0);const c=W(o,e),i=p(e?.in||r,0);i.setFullYear(n,0,s),i.setHours(0,0,0,0);const d=W(i,e);return+t>=+c?n+1:+t>=+d?n:n-1}function hn(r,e){const t=F(),n=e?.firstWeekContainsDate??e?.locale?.options?.firstWeekContainsDate??t.firstWeekContainsDate??t.locale?.options?.firstWeekContainsDate??1,a=le(r,e),s=p(e?.in||r,0);return s.setFullYear(a,0,n),s.setHours(0,0,0,0),W(s,e)}function Fe(r,e){const t=u(r,e?.in),n=+W(t,e)-+hn(t,e);return Math.round(n/Te)+1}function m(r,e){const t=r<0?"-":"",n=Math.abs(r).toString().padStart(e,"0");return t+n}const E={y(r,e){const t=r.getFullYear(),n=t>0?t:1-t;return m(e==="yy"?n%100:n,e.length)},M(r,e){const t=r.getMonth();return e==="M"?String(t+1):m(t+1,2)},d(r,e){return m(r.getDate(),e.length)},a(r,e){const t=r.getHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return t.toUpperCase();case"aaa":return t;case"aaaaa":return t[0];case"aaaa":default:return t==="am"?"a.m.":"p.m."}},h(r,e){return m(r.getHours()%12||12,e.length)},H(r,e){return m(r.getHours(),e.length)},m(r,e){return m(r.getMinutes(),e.length)},s(r,e){return m(r.getSeconds(),e.length)},S(r,e){const t=e.length,n=r.getMilliseconds(),a=Math.trunc(n*Math.pow(10,t-3));return m(a,e.length)}},L={midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},xe={G:function(r,e,t){const n=r.getFullYear()>0?1:0;switch(e){case"G":case"GG":case"GGG":return t.era(n,{width:"abbreviated"});case"GGGGG":return t.era(n,{width:"narrow"});case"GGGG":default:return t.era(n,{width:"wide"})}},y:function(r,e,t){if(e==="yo"){const n=r.getFullYear(),a=n>0?n:1-n;return t.ordinalNumber(a,{unit:"year"})}return E.y(r,e)},Y:function(r,e,t,n){const a=le(r,n),s=a>0?a:1-a;if(e==="YY"){const o=s%100;return m(o,2)}return e==="Yo"?t.ordinalNumber(s,{unit:"year"}):m(s,e.length)},R:function(r,e){const t=Pe(r);return m(t,e.length)},u:function(r,e){const t=r.getFullYear();return m(t,e.length)},Q:function(r,e,t){const n=Math.ceil((r.getMonth()+1)/3);switch(e){case"Q":return String(n);case"QQ":return m(n,2);case"Qo":return t.ordinalNumber(n,{unit:"quarter"});case"QQQ":return t.quarter(n,{width:"abbreviated",context:"formatting"});case"QQQQQ":return t.quarter(n,{width:"narrow",context:"formatting"});case"QQQQ":default:return t.quarter(n,{width:"wide",context:"formatting"})}},q:function(r,e,t){const n=Math.ceil((r.getMonth()+1)/3);switch(e){case"q":return String(n);case"qq":return m(n,2);case"qo":return t.ordinalNumber(n,{unit:"quarter"});case"qqq":return t.quarter(n,{width:"abbreviated",context:"standalone"});case"qqqqq":return t.quarter(n,{width:"narrow",context:"standalone"});case"qqqq":default:return t.quarter(n,{width:"wide",context:"standalone"})}},M:function(r,e,t){const n=r.getMonth();switch(e){case"M":case"MM":return E.M(r,e);case"Mo":return t.ordinalNumber(n+1,{unit:"month"});case"MMM":return t.month(n,{width:"abbreviated",context:"formatting"});case"MMMMM":return t.month(n,{width:"narrow",context:"formatting"});case"MMMM":default:return t.month(n,{width:"wide",context:"formatting"})}},L:function(r,e,t){const n=r.getMonth();switch(e){case"L":return String(n+1);case"LL":return m(n+1,2);case"Lo":return t.ordinalNumber(n+1,{unit:"month"});case"LLL":return t.month(n,{width:"abbreviated",context:"standalone"});case"LLLLL":return t.month(n,{width:"narrow",context:"standalone"});case"LLLL":default:return t.month(n,{width:"wide",context:"standalone"})}},w:function(r,e,t,n){const a=Fe(r,n);return e==="wo"?t.ordinalNumber(a,{unit:"week"}):m(a,e.length)},I:function(r,e,t){const n=qe(r);return e==="Io"?t.ordinalNumber(n,{unit:"week"}):m(n,e.length)},d:function(r,e,t){return e==="do"?t.ordinalNumber(r.getDate(),{unit:"date"}):E.d(r,e)},D:function(r,e,t){const n=fn(r);return e==="Do"?t.ordinalNumber(n,{unit:"dayOfYear"}):m(n,e.length)},E:function(r,e,t){const n=r.getDay();switch(e){case"E":case"EE":case"EEE":return t.day(n,{width:"abbreviated",context:"formatting"});case"EEEEE":return t.day(n,{width:"narrow",context:"formatting"});case"EEEEEE":return t.day(n,{width:"short",context:"formatting"});case"EEEE":default:return t.day(n,{width:"wide",context:"formatting"})}},e:function(r,e,t,n){const a=r.getDay(),s=(a-n.weekStartsOn+8)%7||7;switch(e){case"e":return String(s);case"ee":return m(s,2);case"eo":return t.ordinalNumber(s,{unit:"day"});case"eee":return t.day(a,{width:"abbreviated",context:"formatting"});case"eeeee":return t.day(a,{width:"narrow",context:"formatting"});case"eeeeee":return t.day(a,{width:"short",context:"formatting"});case"eeee":default:return t.day(a,{width:"wide",context:"formatting"})}},c:function(r,e,t,n){const a=r.getDay(),s=(a-n.weekStartsOn+8)%7||7;switch(e){case"c":return String(s);case"cc":return m(s,e.length);case"co":return t.ordinalNumber(s,{unit:"day"});case"ccc":return t.day(a,{width:"abbreviated",context:"standalone"});case"ccccc":return t.day(a,{width:"narrow",context:"standalone"});case"cccccc":return t.day(a,{width:"short",context:"standalone"});case"cccc":default:return t.day(a,{width:"wide",context:"standalone"})}},i:function(r,e,t){const n=r.getDay(),a=n===0?7:n;switch(e){case"i":return String(a);case"ii":return m(a,e.length);case"io":return t.ordinalNumber(a,{unit:"day"});case"iii":return t.day(n,{width:"abbreviated",context:"formatting"});case"iiiii":return t.day(n,{width:"narrow",context:"formatting"});case"iiiiii":return t.day(n,{width:"short",context:"formatting"});case"iiii":default:return t.day(n,{width:"wide",context:"formatting"})}},a:function(r,e,t){const a=r.getHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"});case"aaa":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"}).toLowerCase();case"aaaaa":return t.dayPeriod(a,{width:"narrow",context:"formatting"});case"aaaa":default:return t.dayPeriod(a,{width:"wide",context:"formatting"})}},b:function(r,e,t){const n=r.getHours();let a;switch(n===12?a=L.noon:n===0?a=L.midnight:a=n/12>=1?"pm":"am",e){case"b":case"bb":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"});case"bbb":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"}).toLowerCase();case"bbbbb":return t.dayPeriod(a,{width:"narrow",context:"formatting"});case"bbbb":default:return t.dayPeriod(a,{width:"wide",context:"formatting"})}},B:function(r,e,t){const n=r.getHours();let a;switch(n>=17?a=L.evening:n>=12?a=L.afternoon:n>=4?a=L.morning:a=L.night,e){case"B":case"BB":case"BBB":return t.dayPeriod(a,{width:"abbreviated",context:"formatting"});case"BBBBB":return t.dayPeriod(a,{width:"narrow",context:"formatting"});case"BBBB":default:return t.dayPeriod(a,{width:"wide",context:"formatting"})}},h:function(r,e,t){if(e==="ho"){let n=r.getHours()%12;return n===0&&(n=12),t.ordinalNumber(n,{unit:"hour"})}return E.h(r,e)},H:function(r,e,t){return e==="Ho"?t.ordinalNumber(r.getHours(),{unit:"hour"}):E.H(r,e)},K:function(r,e,t){const n=r.getHours()%12;return e==="Ko"?t.ordinalNumber(n,{unit:"hour"}):m(n,e.length)},k:function(r,e,t){let n=r.getHours();return n===0&&(n=24),e==="ko"?t.ordinalNumber(n,{unit:"hour"}):m(n,e.length)},m:function(r,e,t){return e==="mo"?t.ordinalNumber(r.getMinutes(),{unit:"minute"}):E.m(r,e)},s:function(r,e,t){return e==="so"?t.ordinalNumber(r.getSeconds(),{unit:"second"}):E.s(r,e)},S:function(r,e){return E.S(r,e)},X:function(r,e,t){const n=r.getTimezoneOffset();if(n===0)return"Z";switch(e){case"X":return De(n);case"XXXX":case"XX":return q(n);case"XXXXX":case"XXX":default:return q(n,":")}},x:function(r,e,t){const n=r.getTimezoneOffset();switch(e){case"x":return De(n);case"xxxx":case"xx":return q(n);case"xxxxx":case"xxx":default:return q(n,":")}},O:function(r,e,t){const n=r.getTimezoneOffset();switch(e){case"O":case"OO":case"OOO":return"GMT"+Me(n,":");case"OOOO":default:return"GMT"+q(n,":")}},z:function(r,e,t){const n=r.getTimezoneOffset();switch(e){case"z":case"zz":case"zzz":return"GMT"+Me(n,":");case"zzzz":default:return"GMT"+q(n,":")}},t:function(r,e,t){const n=Math.trunc(+r/1e3);return m(n,e.length)},T:function(r,e,t){return m(+r,e.length)}};function Me(r,e=""){const t=r>0?"-":"+",n=Math.abs(r),a=Math.trunc(n/60),s=n%60;return s===0?t+String(a):t+String(a)+e+m(s,2)}function De(r,e){return r%60===0?(r>0?"-":"+")+m(Math.abs(r)/60,2):q(r,e)}function q(r,e=""){const t=r>0?"-":"+",n=Math.abs(r),a=m(Math.trunc(n/60),2),s=m(n%60,2);return t+a+e+s}const ke=(r,e)=>{switch(r){case"P":return e.date({width:"short"});case"PP":return e.date({width:"medium"});case"PPP":return e.date({width:"long"});case"PPPP":default:return e.date({width:"full"})}},Ce=(r,e)=>{switch(r){case"p":return e.time({width:"short"});case"pp":return e.time({width:"medium"});case"ppp":return e.time({width:"long"});case"pppp":default:return e.time({width:"full"})}},mn=(r,e)=>{const t=r.match(/(P+)(p+)?/)||[],n=t[1],a=t[2];if(!a)return ke(r,e);let s;switch(n){case"P":s=e.dateTime({width:"short"});break;case"PP":s=e.dateTime({width:"medium"});break;case"PPP":s=e.dateTime({width:"long"});break;case"PPPP":default:s=e.dateTime({width:"full"});break}return s.replace("{{date}}",ke(n,e)).replace("{{time}}",Ce(a,e))},oe={p:Ce,P:mn},wn=/^D+$/,yn=/^Y+$/,gn=["D","DD","YY","YYYY"];function Ie(r){return wn.test(r)}function Le(r){return yn.test(r)}function ie(r,e,t){const n=pn(r,e,t);if(console.warn(n),gn.includes(r))throw new RangeError(n)}function pn(r,e,t){const n=r[0]==="Y"?"years":"days of the month";return`Use \`${r.toLowerCase()}\` instead of \`${r}\` (in \`${e}\`) for formatting ${n} to the input \`${t}\`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md`}const bn=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,xn=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,Mn=/^'([^]*?)'?$/,Dn=/''/g,kn=/[a-zA-Z]/;function Tn(r,e,t){const n=F(),a=t?.locale??n.locale??He,s=t?.firstWeekContainsDate??t?.locale?.options?.firstWeekContainsDate??n.firstWeekContainsDate??n.locale?.options?.firstWeekContainsDate??1,o=t?.weekStartsOn??t?.locale?.options?.weekStartsOn??n.weekStartsOn??n.locale?.options?.weekStartsOn??0,c=u(r,t?.in);if(!Ye(c))throw new RangeError("Invalid time value");let i=e.match(xn).map(f=>{const y=f[0];if(y==="p"||y==="P"){const T=oe[y];return T(f,a.formatLong)}return f}).join("").match(bn).map(f=>{if(f==="''")return{isToken:!1,value:"'"};const y=f[0];if(y==="'")return{isToken:!1,value:Pn(f)};if(xe[y])return{isToken:!0,value:f};if(y.match(kn))throw new RangeError("Format string contains an unescaped latin alphabet character `"+y+"`");return{isToken:!1,value:f}});a.localize.preprocessor&&(i=a.localize.preprocessor(c,i));const d={firstWeekContainsDate:s,weekStartsOn:o,locale:a};return i.map(f=>{if(!f.isToken)return f.value;const y=f.value;(!t?.useAdditionalWeekYearTokens&&Le(y)||!t?.useAdditionalDayOfYearTokens&&Ie(y))&&ie(y,e,String(r));const T=xe[y[0]];return T(c,y,a.localize,d)}).join("")}function Pn(r){const e=r.match(Mn);return e?e[1].replace(Dn,"'"):r}function On(){return Object.assign({},F())}function Yn(r,e){const t=u(r,e?.in).getDay();return t===0?7:t}function vn(r,e){const t=_n(e)?new e(0):p(e,0);return t.setFullYear(r.getFullYear(),r.getMonth(),r.getDate()),t.setHours(r.getHours(),r.getMinutes(),r.getSeconds(),r.getMilliseconds()),t}function _n(r){return typeof r=="function"&&r.prototype?.constructor===r}const Wn=10;class Qe{subPriority=0;validate(e,t){return!0}}class Nn extends Qe{constructor(e,t,n,a,s){super(),this.value=e,this.validateValue=t,this.setValue=n,this.priority=a,s&&(this.subPriority=s)}validate(e,t){return this.validateValue(e,this.value,t)}set(e,t,n){return this.setValue(e,t,this.value,n)}}class En extends Qe{priority=Wn;subPriority=-1;constructor(e,t){super(),this.context=e||(n=>p(t,n))}set(e,t){return t.timestampIsSet?e:p(e,vn(e,this.context))}}class h{run(e,t,n,a){const s=this.parse(e,t,n,a);return s?{setter:new Nn(s.value,this.validate,this.set,this.priority,this.subPriority),rest:s.rest}:null}validate(e,t,n){return!0}}class Hn extends h{priority=140;parse(e,t,n){switch(t){case"G":case"GG":case"GGG":return n.era(e,{width:"abbreviated"})||n.era(e,{width:"narrow"});case"GGGGG":return n.era(e,{width:"narrow"});case"GGGG":default:return n.era(e,{width:"wide"})||n.era(e,{width:"abbreviated"})||n.era(e,{width:"narrow"})}}set(e,t,n){return t.era=n,e.setFullYear(n,0,1),e.setHours(0,0,0,0),e}incompatibleTokens=["R","u","t","T"]}const x={month:/^(1[0-2]|0?\d)/,date:/^(3[0-1]|[0-2]?\d)/,dayOfYear:/^(36[0-6]|3[0-5]\d|[0-2]?\d?\d)/,week:/^(5[0-3]|[0-4]?\d)/,hour23h:/^(2[0-3]|[0-1]?\d)/,hour24h:/^(2[0-4]|[0-1]?\d)/,hour11h:/^(1[0-1]|0?\d)/,hour12h:/^(1[0-2]|0?\d)/,minute:/^[0-5]?\d/,second:/^[0-5]?\d/,singleDigit:/^\d/,twoDigits:/^\d{1,2}/,threeDigits:/^\d{1,3}/,fourDigits:/^\d{1,4}/,anyDigitsSigned:/^-?\d+/,singleDigitSigned:/^-?\d/,twoDigitsSigned:/^-?\d{1,2}/,threeDigitsSigned:/^-?\d{1,3}/,fourDigitsSigned:/^-?\d{1,4}/},v={basicOptionalMinutes:/^([+-])(\d{2})(\d{2})?|Z/,basic:/^([+-])(\d{2})(\d{2})|Z/,basicOptionalSeconds:/^([+-])(\d{2})(\d{2})((\d{2}))?|Z/,extended:/^([+-])(\d{2}):(\d{2})|Z/,extendedOptionalSeconds:/^([+-])(\d{2}):(\d{2})(:(\d{2}))?|Z/};function M(r,e){return r&&{value:e(r.value),rest:r.rest}}function g(r,e){const t=e.match(r);return t?{value:parseInt(t[0],10),rest:e.slice(t[0].length)}:null}function _(r,e){const t=e.match(r);if(!t)return null;if(t[0]==="Z")return{value:0,rest:e.slice(1)};const n=t[1]==="+"?1:-1,a=t[2]?parseInt(t[2],10):0,s=t[3]?parseInt(t[3],10):0,o=t[5]?parseInt(t[5],10):0;return{value:n*(a*V+s*G+o*ot),rest:e.slice(t[0].length)}}function Re(r){return g(x.anyDigitsSigned,r)}function b(r,e){switch(r){case 1:return g(x.singleDigit,e);case 2:return g(x.twoDigits,e);case 3:return g(x.threeDigits,e);case 4:return g(x.fourDigits,e);default:return g(new RegExp("^\\d{1,"+r+"}"),e)}}function te(r,e){switch(r){case 1:return g(x.singleDigitSigned,e);case 2:return g(x.twoDigitsSigned,e);case 3:return g(x.threeDigitsSigned,e);case 4:return g(x.fourDigitsSigned,e);default:return g(new RegExp("^-?\\d{1,"+r+"}"),e)}}function fe(r){switch(r){case"morning":return 4;case"evening":return 17;case"pm":case"noon":case"afternoon":return 12;case"am":case"midnight":case"night":default:return 0}}function Be(r,e){const t=e>0,n=t?e:1-e;let a;if(n<=50)a=r||100;else{const s=n+50,o=Math.trunc(s/100)*100,c=r>=s%100;a=r+o-(c?100:0)}return t?a:1-a}function Xe(r){return r%400===0||r%4===0&&r%100!==0}class qn extends h{priority=130;incompatibleTokens=["Y","R","u","w","I","i","e","c","t","T"];parse(e,t,n){const a=s=>({year:s,isTwoDigitYear:t==="yy"});switch(t){case"y":return M(b(4,e),a);case"yo":return M(n.ordinalNumber(e,{unit:"year"}),a);default:return M(b(t.length,e),a)}}validate(e,t){return t.isTwoDigitYear||t.year>0}set(e,t,n){const a=e.getFullYear();if(n.isTwoDigitYear){const o=Be(n.year,a);return e.setFullYear(o,0,1),e.setHours(0,0,0,0),e}const s=!("era"in t)||t.era===1?n.year:1-n.year;return e.setFullYear(s,0,1),e.setHours(0,0,0,0),e}}class Fn extends h{priority=130;parse(e,t,n){const a=s=>({year:s,isTwoDigitYear:t==="YY"});switch(t){case"Y":return M(b(4,e),a);case"Yo":return M(n.ordinalNumber(e,{unit:"year"}),a);default:return M(b(t.length,e),a)}}validate(e,t){return t.isTwoDigitYear||t.year>0}set(e,t,n,a){const s=le(e,a);if(n.isTwoDigitYear){const c=Be(n.year,s);return e.setFullYear(c,0,a.firstWeekContainsDate),e.setHours(0,0,0,0),W(e,a)}const o=!("era"in t)||t.era===1?n.year:1-n.year;return e.setFullYear(o,0,a.firstWeekContainsDate),e.setHours(0,0,0,0),W(e,a)}incompatibleTokens=["y","R","u","Q","q","M","L","I","d","D","i","t","T"]}class Cn extends h{priority=130;parse(e,t){return te(t==="R"?4:t.length,e)}set(e,t,n){const a=p(e,0);return a.setFullYear(n,0,4),a.setHours(0,0,0,0),Q(a)}incompatibleTokens=["G","y","Y","u","Q","q","M","L","w","d","D","e","c","t","T"]}class In extends h{priority=130;parse(e,t){return te(t==="u"?4:t.length,e)}set(e,t,n){return e.setFullYear(n,0,1),e.setHours(0,0,0,0),e}incompatibleTokens=["G","y","Y","R","w","I","i","e","c","t","T"]}class Ln extends h{priority=120;parse(e,t,n){switch(t){case"Q":case"QQ":return b(t.length,e);case"Qo":return n.ordinalNumber(e,{unit:"quarter"});case"QQQ":return n.quarter(e,{width:"abbreviated",context:"formatting"})||n.quarter(e,{width:"narrow",context:"formatting"});case"QQQQQ":return n.quarter(e,{width:"narrow",context:"formatting"});case"QQQQ":default:return n.quarter(e,{width:"wide",context:"formatting"})||n.quarter(e,{width:"abbreviated",context:"formatting"})||n.quarter(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=1&&t<=4}set(e,t,n){return e.setMonth((n-1)*3,1),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","M","L","w","I","d","D","i","e","c","t","T"]}class Qn extends h{priority=120;parse(e,t,n){switch(t){case"q":case"qq":return b(t.length,e);case"qo":return n.ordinalNumber(e,{unit:"quarter"});case"qqq":return n.quarter(e,{width:"abbreviated",context:"standalone"})||n.quarter(e,{width:"narrow",context:"standalone"});case"qqqqq":return n.quarter(e,{width:"narrow",context:"standalone"});case"qqqq":default:return n.quarter(e,{width:"wide",context:"standalone"})||n.quarter(e,{width:"abbreviated",context:"standalone"})||n.quarter(e,{width:"narrow",context:"standalone"})}}validate(e,t){return t>=1&&t<=4}set(e,t,n){return e.setMonth((n-1)*3,1),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","Q","M","L","w","I","d","D","i","e","c","t","T"]}class Rn extends h{incompatibleTokens=["Y","R","q","Q","L","w","I","D","i","e","c","t","T"];priority=110;parse(e,t,n){const a=s=>s-1;switch(t){case"M":return M(g(x.month,e),a);case"MM":return M(b(2,e),a);case"Mo":return M(n.ordinalNumber(e,{unit:"month"}),a);case"MMM":return n.month(e,{width:"abbreviated",context:"formatting"})||n.month(e,{width:"narrow",context:"formatting"});case"MMMMM":return n.month(e,{width:"narrow",context:"formatting"});case"MMMM":default:return n.month(e,{width:"wide",context:"formatting"})||n.month(e,{width:"abbreviated",context:"formatting"})||n.month(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=0&&t<=11}set(e,t,n){return e.setMonth(n,1),e.setHours(0,0,0,0),e}}class Bn extends h{priority=110;parse(e,t,n){const a=s=>s-1;switch(t){case"L":return M(g(x.month,e),a);case"LL":return M(b(2,e),a);case"Lo":return M(n.ordinalNumber(e,{unit:"month"}),a);case"LLL":return n.month(e,{width:"abbreviated",context:"standalone"})||n.month(e,{width:"narrow",context:"standalone"});case"LLLLL":return n.month(e,{width:"narrow",context:"standalone"});case"LLLL":default:return n.month(e,{width:"wide",context:"standalone"})||n.month(e,{width:"abbreviated",context:"standalone"})||n.month(e,{width:"narrow",context:"standalone"})}}validate(e,t){return t>=0&&t<=11}set(e,t,n){return e.setMonth(n,1),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","Q","M","w","I","D","i","e","c","t","T"]}function Xn(r,e,t){const n=u(r,t?.in),a=Fe(n,t)-e;return n.setDate(n.getDate()-a*7),u(n,t?.in)}class $n extends h{priority=100;parse(e,t,n){switch(t){case"w":return g(x.week,e);case"wo":return n.ordinalNumber(e,{unit:"week"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=53}set(e,t,n,a){return W(Xn(e,n,a),a)}incompatibleTokens=["y","R","u","q","Q","M","L","I","d","D","i","t","T"]}function An(r,e,t){const n=u(r,t?.in),a=qe(n,t)-e;return n.setDate(n.getDate()-a*7),n}class Gn extends h{priority=100;parse(e,t,n){switch(t){case"I":return g(x.week,e);case"Io":return n.ordinalNumber(e,{unit:"week"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=53}set(e,t,n){return Q(An(e,n))}incompatibleTokens=["y","Y","u","q","Q","M","L","w","d","D","e","c","t","T"]}const Vn=[31,28,31,30,31,30,31,31,30,31,30,31],zn=[31,29,31,30,31,30,31,31,30,31,30,31];class jn extends h{priority=90;subPriority=1;parse(e,t,n){switch(t){case"d":return g(x.date,e);case"do":return n.ordinalNumber(e,{unit:"date"});default:return b(t.length,e)}}validate(e,t){const n=e.getFullYear(),a=Xe(n),s=e.getMonth();return a?t>=1&&t<=zn[s]:t>=1&&t<=Vn[s]}set(e,t,n){return e.setDate(n),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","Q","w","I","D","i","e","c","t","T"]}class Un extends h{priority=90;subpriority=1;parse(e,t,n){switch(t){case"D":case"DD":return g(x.dayOfYear,e);case"Do":return n.ordinalNumber(e,{unit:"date"});default:return b(t.length,e)}}validate(e,t){const n=e.getFullYear();return Xe(n)?t>=1&&t<=366:t>=1&&t<=365}set(e,t,n){return e.setMonth(0,n),e.setHours(0,0,0,0),e}incompatibleTokens=["Y","R","q","Q","M","L","w","I","d","E","i","e","c","t","T"]}function he(r,e,t){const n=F(),a=t?.weekStartsOn??t?.locale?.options?.weekStartsOn??n.weekStartsOn??n.locale?.options?.weekStartsOn??0,s=u(r,t?.in),o=s.getDay(),i=(e%7+7)%7,d=7-a,f=e<0||e>6?e-(o+d)%7:(i+d)%7-(o+d)%7;return ne(s,f,t)}class Zn extends h{priority=90;parse(e,t,n){switch(t){case"E":case"EE":case"EEE":return n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"EEEEE":return n.day(e,{width:"narrow",context:"formatting"});case"EEEEEE":return n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"EEEE":default:return n.day(e,{width:"wide",context:"formatting"})||n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=0&&t<=6}set(e,t,n,a){return e=he(e,n,a),e.setHours(0,0,0,0),e}incompatibleTokens=["D","i","e","c","t","T"]}class Jn extends h{priority=90;parse(e,t,n,a){const s=o=>{const c=Math.floor((o-1)/7)*7;return(o+a.weekStartsOn+6)%7+c};switch(t){case"e":case"ee":return M(b(t.length,e),s);case"eo":return M(n.ordinalNumber(e,{unit:"day"}),s);case"eee":return n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"eeeee":return n.day(e,{width:"narrow",context:"formatting"});case"eeeeee":return n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"});case"eeee":default:return n.day(e,{width:"wide",context:"formatting"})||n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"})}}validate(e,t){return t>=0&&t<=6}set(e,t,n,a){return e=he(e,n,a),e.setHours(0,0,0,0),e}incompatibleTokens=["y","R","u","q","Q","M","L","I","d","D","E","i","c","t","T"]}class Kn extends h{priority=90;parse(e,t,n,a){const s=o=>{const c=Math.floor((o-1)/7)*7;return(o+a.weekStartsOn+6)%7+c};switch(t){case"c":case"cc":return M(b(t.length,e),s);case"co":return M(n.ordinalNumber(e,{unit:"day"}),s);case"ccc":return n.day(e,{width:"abbreviated",context:"standalone"})||n.day(e,{width:"short",context:"standalone"})||n.day(e,{width:"narrow",context:"standalone"});case"ccccc":return n.day(e,{width:"narrow",context:"standalone"});case"cccccc":return n.day(e,{width:"short",context:"standalone"})||n.day(e,{width:"narrow",context:"standalone"});case"cccc":default:return n.day(e,{width:"wide",context:"standalone"})||n.day(e,{width:"abbreviated",context:"standalone"})||n.day(e,{width:"short",context:"standalone"})||n.day(e,{width:"narrow",context:"standalone"})}}validate(e,t){return t>=0&&t<=6}set(e,t,n,a){return e=he(e,n,a),e.setHours(0,0,0,0),e}incompatibleTokens=["y","R","u","q","Q","M","L","I","d","D","E","i","e","t","T"]}function Sn(r,e,t){const n=u(r,t?.in),a=Yn(n,t),s=e-a;return ne(n,s,t)}class er extends h{priority=90;parse(e,t,n){const a=s=>s===0?7:s;switch(t){case"i":case"ii":return b(t.length,e);case"io":return n.ordinalNumber(e,{unit:"day"});case"iii":return M(n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"}),a);case"iiiii":return M(n.day(e,{width:"narrow",context:"formatting"}),a);case"iiiiii":return M(n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"}),a);case"iiii":default:return M(n.day(e,{width:"wide",context:"formatting"})||n.day(e,{width:"abbreviated",context:"formatting"})||n.day(e,{width:"short",context:"formatting"})||n.day(e,{width:"narrow",context:"formatting"}),a)}}validate(e,t){return t>=1&&t<=7}set(e,t,n){return e=Sn(e,n),e.setHours(0,0,0,0),e}incompatibleTokens=["y","Y","u","q","Q","M","L","w","d","D","E","e","c","t","T"]}class tr extends h{priority=80;parse(e,t,n){switch(t){case"a":case"aa":case"aaa":return n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"});case"aaaaa":return n.dayPeriod(e,{width:"narrow",context:"formatting"});case"aaaa":default:return n.dayPeriod(e,{width:"wide",context:"formatting"})||n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"})}}set(e,t,n){return e.setHours(fe(n),0,0,0),e}incompatibleTokens=["b","B","H","k","t","T"]}class nr extends h{priority=80;parse(e,t,n){switch(t){case"b":case"bb":case"bbb":return n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"});case"bbbbb":return n.dayPeriod(e,{width:"narrow",context:"formatting"});case"bbbb":default:return n.dayPeriod(e,{width:"wide",context:"formatting"})||n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"})}}set(e,t,n){return e.setHours(fe(n),0,0,0),e}incompatibleTokens=["a","B","H","k","t","T"]}class rr extends h{priority=80;parse(e,t,n){switch(t){case"B":case"BB":case"BBB":return n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"});case"BBBBB":return n.dayPeriod(e,{width:"narrow",context:"formatting"});case"BBBB":default:return n.dayPeriod(e,{width:"wide",context:"formatting"})||n.dayPeriod(e,{width:"abbreviated",context:"formatting"})||n.dayPeriod(e,{width:"narrow",context:"formatting"})}}set(e,t,n){return e.setHours(fe(n),0,0,0),e}incompatibleTokens=["a","b","t","T"]}class ar extends h{priority=70;parse(e,t,n){switch(t){case"h":return g(x.hour12h,e);case"ho":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=12}set(e,t,n){const a=e.getHours()>=12;return a&&n<12?e.setHours(n+12,0,0,0):!a&&n===12?e.setHours(0,0,0,0):e.setHours(n,0,0,0),e}incompatibleTokens=["H","K","k","t","T"]}class sr extends h{priority=70;parse(e,t,n){switch(t){case"H":return g(x.hour23h,e);case"Ho":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=23}set(e,t,n){return e.setHours(n,0,0,0),e}incompatibleTokens=["a","b","h","K","k","t","T"]}class or extends h{priority=70;parse(e,t,n){switch(t){case"K":return g(x.hour11h,e);case"Ko":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=11}set(e,t,n){return e.getHours()>=12&&n<12?e.setHours(n+12,0,0,0):e.setHours(n,0,0,0),e}incompatibleTokens=["h","H","k","t","T"]}class ir extends h{priority=70;parse(e,t,n){switch(t){case"k":return g(x.hour24h,e);case"ko":return n.ordinalNumber(e,{unit:"hour"});default:return b(t.length,e)}}validate(e,t){return t>=1&&t<=24}set(e,t,n){const a=n<=24?n%24:n;return e.setHours(a,0,0,0),e}incompatibleTokens=["a","b","h","H","K","t","T"]}class cr extends h{priority=60;parse(e,t,n){switch(t){case"m":return g(x.minute,e);case"mo":return n.ordinalNumber(e,{unit:"minute"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=59}set(e,t,n){return e.setMinutes(n,0,0),e}incompatibleTokens=["t","T"]}class ur extends h{priority=50;parse(e,t,n){switch(t){case"s":return g(x.second,e);case"so":return n.ordinalNumber(e,{unit:"second"});default:return b(t.length,e)}}validate(e,t){return t>=0&&t<=59}set(e,t,n){return e.setSeconds(n,0),e}incompatibleTokens=["t","T"]}class dr extends h{priority=30;parse(e,t){const n=a=>Math.trunc(a*Math.pow(10,-t.length+3));return M(b(t.length,e),n)}set(e,t,n){return e.setMilliseconds(n),e}incompatibleTokens=["t","T"]}class lr extends h{priority=10;parse(e,t){switch(t){case"X":return _(v.basicOptionalMinutes,e);case"XX":return _(v.basic,e);case"XXXX":return _(v.basicOptionalSeconds,e);case"XXXXX":return _(v.extendedOptionalSeconds,e);case"XXX":default:return _(v.extended,e)}}set(e,t,n){return t.timestampIsSet?e:p(e,e.getTime()-ee(e)-n)}incompatibleTokens=["t","T","x"]}class fr extends h{priority=10;parse(e,t){switch(t){case"x":return _(v.basicOptionalMinutes,e);case"xx":return _(v.basic,e);case"xxxx":return _(v.basicOptionalSeconds,e);case"xxxxx":return _(v.extendedOptionalSeconds,e);case"xxx":default:return _(v.extended,e)}}set(e,t,n){return t.timestampIsSet?e:p(e,e.getTime()-ee(e)-n)}incompatibleTokens=["t","T","X"]}class hr extends h{priority=40;parse(e){return Re(e)}set(e,t,n){return[p(e,n*1e3),{timestampIsSet:!0}]}incompatibleTokens="*"}class mr extends h{priority=20;parse(e){return Re(e)}set(e,t,n){return[p(e,n),{timestampIsSet:!0}]}incompatibleTokens="*"}const wr={G:new Hn,y:new qn,Y:new Fn,R:new Cn,u:new In,Q:new Ln,q:new Qn,M:new Rn,L:new Bn,w:new $n,I:new Gn,d:new jn,D:new Un,E:new Zn,e:new Jn,c:new Kn,i:new er,a:new tr,b:new nr,B:new rr,h:new ar,H:new sr,K:new or,k:new ir,m:new cr,s:new ur,S:new dr,X:new lr,x:new fr,t:new hr,T:new mr},yr=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,gr=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,pr=/^'([^]*?)'?$/,br=/''/g,xr=/\S/,Mr=/[a-zA-Z]/;function Dr(r,e,t,n){const a=()=>p(n?.in||t,NaN),s=On(),o=n?.locale??s.locale??He,c=n?.firstWeekContainsDate??n?.locale?.options?.firstWeekContainsDate??s.firstWeekContainsDate??s.locale?.options?.firstWeekContainsDate??1,i=n?.weekStartsOn??n?.locale?.options?.weekStartsOn??s.weekStartsOn??s.locale?.options?.weekStartsOn??0;if(!e)return r?a():u(t,n?.in);const d={firstWeekContainsDate:c,weekStartsOn:i,locale:o},f=[new En(n?.in,t)],y=e.match(gr).map(l=>{const w=l[0];if(w in oe){const k=oe[w];return k(l,o.formatLong)}return l}).join("").match(yr),T=[];for(let l of y){!n?.useAdditionalWeekYearTokens&&Le(l)&&ie(l,e,r),!n?.useAdditionalDayOfYearTokens&&Ie(l)&&ie(l,e,r);const w=l[0],k=wr[w];if(k){const{incompatibleTokens:R}=k;if(Array.isArray(R)){const me=T.find(we=>R.includes(we.token)||we.token===w);if(me)throw new RangeError(`The format string mustn't contain \`${me.fullToken}\` and \`${l}\` at the same time`)}else if(k.incompatibleTokens==="*"&&T.length>0)throw new RangeError(`The format string mustn't contain \`${l}\` and any other token at the same time`);T.push({token:w,fullToken:l});const H=k.run(r,l,o.match,d);if(!H)return a();f.push(H.setter),r=H.rest}else{if(w.match(Mr))throw new RangeError("Format string contains an unescaped latin alphabet character `"+w+"`");if(l==="''"?l="'":w==="'"&&(l=kr(l)),r.indexOf(l)===0)r=r.slice(l.length);else return a()}}if(r.length>0&&xr.test(r))return a();const N=f.map(l=>l.priority).sort((l,w)=>w-l).filter((l,w,k)=>k.indexOf(l)===w).map(l=>f.filter(w=>w.priority===l).sort((w,k)=>k.subPriority-w.subPriority)).map(l=>l[0]);let D=u(t,n?.in);if(isNaN(+D))return a();const P={};for(const l of N){if(!l.validate(D,d))return a();const w=l.set(D,P,d);Array.isArray(w)?(D=w[0],Object.assign(P,w[1])):D=w}return D}function kr(r){return r.match(pr)[1].replace(br,"'")}function Tr(r,e){const t=u(r,e?.in);return t.setMinutes(0,0,0),t}function Pr(r,e){const t=u(r,e?.in);return t.setSeconds(0,0),t}function Or(r,e){const t=u(r,e?.in);return t.setMilliseconds(0),t}function Yr(r,e){const t=()=>p(e?.in,NaN),n=e?.additionalDigits??2,a=Nr(r);let s;if(a.date){const d=Er(a.date,n);s=Hr(d.restDateString,d.year)}if(!s||isNaN(+s))return t();const o=+s;let c=0,i;if(a.time&&(c=qr(a.time),isNaN(c)))return t();if(a.timezone){if(i=Fr(a.timezone),isNaN(i))return t()}else{const d=new Date(o+c),f=u(0,e?.in);return f.setFullYear(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate()),f.setHours(d.getUTCHours(),d.getUTCMinutes(),d.getUTCSeconds(),d.getUTCMilliseconds()),f}return u(o+c+i,e?.in)}const S={dateTimeDelimiter:/[T ]/,timeZoneDelimiter:/[Z ]/i,timezone:/([Z+-].*)$/},vr=/^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/,_r=/^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/,Wr=/^([+-])(\d{2})(?::?(\d{2}))?$/;function Nr(r){const e={},t=r.split(S.dateTimeDelimiter);let n;if(t.length>2)return e;if(/:/.test(t[0])?n=t[0]:(e.date=t[0],n=t[1],S.timeZoneDelimiter.test(e.date)&&(e.date=r.split(S.timeZoneDelimiter)[0],n=r.substr(e.date.length,r.length))),n){const a=S.timezone.exec(n);a?(e.time=n.replace(a[1],""),e.timezone=a[1]):e.time=n}return e}function Er(r,e){const t=new RegExp("^(?:(\\d{4}|[+-]\\d{"+(4+e)+"})|(\\d{2}|[+-]\\d{"+(2+e)+"})$)"),n=r.match(t);if(!n)return{year:NaN,restDateString:""};const a=n[1]?parseInt(n[1]):null,s=n[2]?parseInt(n[2]):null;return{year:s===null?a:s*100,restDateString:r.slice((n[1]||n[2]).length)}}function Hr(r,e){if(e===null)return new Date(NaN);const t=r.match(vr);if(!t)return new Date(NaN);const n=!!t[4],a=$(t[1]),s=$(t[2])-1,o=$(t[3]),c=$(t[4]),i=$(t[5])-1;if(n)return Rr(e,c,i)?Cr(e,c,i):new Date(NaN);{const d=new Date(0);return!Lr(e,s,o)||!Qr(e,a)?new Date(NaN):(d.setUTCFullYear(e,s,Math.max(a,o)),d)}}function $(r){return r?parseInt(r):1}function qr(r){const e=r.match(_r);if(!e)return NaN;const t=ae(e[1]),n=ae(e[2]),a=ae(e[3]);return Br(t,n,a)?t*V+n*G+a*1e3:NaN}function ae(r){return r&&parseFloat(r.replace(",","."))||0}function Fr(r){if(r==="Z")return 0;const e=r.match(Wr);if(!e)return 0;const t=e[1]==="+"?-1:1,n=parseInt(e[2]),a=e[3]&&parseInt(e[3])||0;return Xr(n,a)?t*(n*V+a*G):NaN}function Cr(r,e,t){const n=new Date(0);n.setUTCFullYear(r,0,4);const a=n.getUTCDay()||7,s=(e-1)*7+t+1-a;return n.setUTCDate(n.getUTCDate()+s),n}const Ir=[31,null,31,30,31,30,31,31,30,31,30,31];function $e(r){return r%400===0||r%4===0&&r%100!==0}function Lr(r,e,t){return e>=0&&e<=11&&t>=1&&t<=(Ir[e]||($e(r)?29:28))}function Qr(r,e){return e>=1&&e<=($e(r)?366:365)}function Rr(r,e,t){return e>=1&&e<=53&&t>=0&&t<=6}function Br(r,e,t){return r===24?e===0&&t===0:t>=0&&t<60&&e>=0&&e<60&&r>=0&&r<25}function Xr(r,e){return e>=0&&e<=59}/*! +>>>>>>>> upstream/feat/newRadios:repeater/web/html/assets/chartjs-adapter-date-fns.esm-Do2apMon.js * chartjs-adapter-date-fns v3.0.0 * https://www.chartjs.org * (c) 2022 chartjs-adapter-date-fns Contributors diff --git a/scripts/proxmox-install.sh b/scripts/proxmox-install.sh new file mode 100644 index 0000000..b9f0c2b --- /dev/null +++ b/scripts/proxmox-install.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +# pyMC Repeater - Proxmox LXC Installer +# Creates an LXC container with USB passthrough and installs pyMC Repeater +# +# Usage (run on the Proxmox host): +# bash -c "$(curl -fsSL https://raw.githubusercontent.com/rightup/pyMC_Repeater/main/scripts/proxmox-install.sh)" +# +# License: MIT +# Source: https://github.com/rightup/pyMC_Repeater + +set -euo pipefail + +# ── Defaults ─────────────────────────────────────────────────────────────── +REPO="https://github.com/rightup/pyMC_Repeater.git" +BRANCH="feat/newRadios" +CT_TEMPLATE="debian-12-standard" +CT_RAM=1024 +CT_SWAP=512 +CT_DISK=4 +CT_CORES=2 +CT_HOSTNAME="pymc-repeater" +CT_BRIDGE="vmbr0" +CT_STORAGE="local-lvm" +CT_TEMPLATE_STORAGE="local" +CH341_VID="1a86" +CH341_PID="5512" + +# ── Colors ───────────────────────────────────────────────────────────────── +RD="\033[01;31m" GN="\033[1;92m" YW="\033[33m" BL="\033[36m" BLD="\033[1m" CL="\033[m" + +msg_info() { echo -e " ${BL}ℹ${CL} ${1}"; } +msg_ok() { echo -e " ${GN}✓${CL} ${1}"; } +msg_warn() { echo -e " ${YW}⚠${CL} ${1}"; } +msg_error() { echo -e " ${RD}✗${CL} ${1}"; } + +header() { + clear + echo -e "${BLD}" + echo "═══════════════════════════════════════════════════════════════" + echo " pyMC Repeater - Proxmox LXC Installer" + echo "═══════════════════════════════════════════════════════════════" + echo -e "${CL}" +} + +cleanup() { + local exit_code=$? + if [ $exit_code -ne 0 ] && [ -n "${CTID:-}" ] && pct status "$CTID" &>/dev/null; then + echo "" + read -p " Delete the failed container ${CTID}? [y/N]: " -r + if [[ "$REPLY" =~ ^[Yy]$ ]]; then + pct stop "$CTID" 2>/dev/null || true + pct destroy "$CTID" 2>/dev/null || true + msg_ok "Container ${CTID} removed" + fi + fi +} +trap cleanup EXIT + +# ── Preflight checks ────────────────────────────────────────────────────── +header + +if ! command -v pct &>/dev/null; then + msg_error "This script must be run on a Proxmox VE host." + exit 1 +fi + +if [ "$EUID" -ne 0 ]; then + msg_error "Please run as root" + exit 1 +fi + +msg_ok "Running on Proxmox host as root" + +# Check for CH341 +echo "" +if lsusb -d "${CH341_VID}:${CH341_PID}" &>/dev/null; then + msg_ok "CH341 USB device detected" +else + msg_warn "CH341 USB device not found — plug it in before starting the repeater" +fi + +# ── Interactive settings ────────────────────────────────────────────────── +echo "" +echo -e "${BLD}Container Settings${CL} (press Enter for defaults):" +echo "" + +read -p " Hostname [${CT_HOSTNAME}]: " -r input; CT_HOSTNAME="${input:-$CT_HOSTNAME}" +read -p " RAM in MB [${CT_RAM}]: " -r input; CT_RAM="${input:-$CT_RAM}" +read -p " Disk in GB [${CT_DISK}]: " -r input; CT_DISK="${input:-$CT_DISK}" +read -p " CPU cores [${CT_CORES}]: " -r input; CT_CORES="${input:-$CT_CORES}" +read -p " Bridge [${CT_BRIDGE}]: " -r input; CT_BRIDGE="${input:-$CT_BRIDGE}" + +AVAILABLE_STORAGES=$(pvesm status -content rootdir 2>/dev/null | awk 'NR>1 {print $1}' || echo "local-lvm") +echo " Available storages: ${AVAILABLE_STORAGES}" +read -p " Storage [${CT_STORAGE}]: " -r input; CT_STORAGE="${input:-$CT_STORAGE}" +read -p " Git branch [${BRANCH}]: " -r input; BRANCH="${input:-$BRANCH}" +read -sp " Root password [pymc]: " CT_PASSWORD; echo +CT_PASSWORD="${CT_PASSWORD:-pymc}" + +# ── Get next CTID ───────────────────────────────────────────────────────── +CTID=$(pvesh get /cluster/nextid) + +# ── Confirmation ────────────────────────────────────────────────────────── +echo "" +echo -e "${BLD}Summary:${CL}" +echo " CTID: ${CTID} Host: ${CT_HOSTNAME} RAM: ${CT_RAM}MB Disk: ${CT_DISK}GB" +echo " Cores: ${CT_CORES} Storage: ${CT_STORAGE} Bridge: ${CT_BRIDGE} Branch: ${BRANCH}" +echo " Mode: privileged (required for USB passthrough)" +echo "" +read -p " Proceed? [Y/n]: " -r +[[ "${REPLY:-Y}" =~ ^[Nn]$ ]] && { msg_warn "Aborted"; exit 0; } + +# ── Download template ───────────────────────────────────────────────────── +echo "" +msg_info "Downloading Debian 12 template..." +TEMPLATE_FILE=$(pveam available -section system 2>/dev/null | grep "${CT_TEMPLATE}" | sort -t- -k4 -V | tail -1 | awk '{print $2}') +[ -z "$TEMPLATE_FILE" ] && { msg_error "Template not found. Run: pveam update"; exit 1; } + +pveam list "$CT_TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE_FILE" || \ + pveam download "$CT_TEMPLATE_STORAGE" "$TEMPLATE_FILE" +msg_ok "Template ready" + +# ── Create container ────────────────────────────────────────────────────── +msg_info "Creating LXC container ${CTID}..." +pct create "$CTID" "${CT_TEMPLATE_STORAGE}:vztmpl/${TEMPLATE_FILE}" \ + --hostname "$CT_HOSTNAME" \ + --memory "$CT_RAM" \ + --swap "$CT_SWAP" \ + --cores "$CT_CORES" \ + --rootfs "${CT_STORAGE}:${CT_DISK}" \ + --net0 "name=eth0,bridge=${CT_BRIDGE},ip=dhcp" \ + --unprivileged 0 \ + --features nesting=1 \ + --onboot 1 \ + --start 0 \ + --password "$CT_PASSWORD" \ + --ostype debian +msg_ok "Container created" + +# ── USB passthrough ─────────────────────────────────────────────────────── +msg_info "Configuring USB passthrough..." +cat >> "/etc/pve/lxc/${CTID}.conf" <<'EOF' + +# CH341 USB passthrough for pyMC Repeater +lxc.cgroup2.devices.allow: c 189:* rwm +lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir 0 0 +EOF +msg_ok "USB passthrough configured" + +# ── Host udev rule ──────────────────────────────────────────────────────── +msg_info "Installing CH341 udev rule on host..." +echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="5512", MODE="0666"' \ + > /etc/udev/rules.d/99-ch341.rules +udevadm control --reload-rules +udevadm trigger --subsystem-match=usb --action=change +msg_ok "Host udev rule installed" + +# ── Start container & wait for network ──────────────────────────────────── +msg_info "Starting container..." +pct start "$CTID" +sleep 3 +for _ in $(seq 1 30); do + pct exec "$CTID" -- ping -c1 -W1 8.8.8.8 &>/dev/null && break + sleep 1 +done +msg_ok "Container running with network" + +# ── Bootstrap: install git, clone repo ──────────────────────────────────── +msg_info "Installing git inside container..." +pct exec "$CTID" -- bash -c " + export DEBIAN_FRONTEND=noninteractive + + # Fix locale warnings + apt-get update -qq + apt-get install -y locales >/dev/null 2>&1 + sed -i 's/# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen + locale-gen >/dev/null 2>&1 + echo 'LANG=en_US.UTF-8' > /etc/default/locale + + apt-get install -y git whiptail >/dev/null 2>&1 + + # Enable auto-login on console (no password prompt in Proxmox web console) + mkdir -p /etc/systemd/system/container-getty@1.service.d + cat > /etc/systemd/system/container-getty@1.service.d/override.conf <<'AUTOLOGIN' +[Service] +ExecStart= +ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM +AUTOLOGIN + systemctl daemon-reload + + # Login banner with system info + cat > /etc/profile.d/pymc-motd.sh <<'MOTD' +#!/bin/sh +HOSTNAME=\$(hostname) +IP=\$(hostname -I | awk '{print \$1}') +OS=\$(. /etc/os-release && echo \"\$NAME\") +VER=\$(. /etc/os-release && echo \"\$VERSION_ID\") +echo \"\" +echo \" pyMC Repeater LXC Container\" +echo \" 🌐 GitHub: https://github.com/rightup/pyMC_Repeater\" +echo \"\" +echo \" 🖥️ OS: \$OS - Version: \$VER\" +echo \" 🏠 Hostname: \$HOSTNAME\" +echo \" 💡 IP Address: \$IP\" +echo \" 📡 Dashboard: http://\$IP:8000\" +echo \"\" +echo \" Management: cd /opt/pymc_repeater && bash manage.sh\" +echo \"\" +MOTD + chmod +x /etc/profile.d/pymc-motd.sh +" +msg_ok "Git installed, locale fixed, console auto-login enabled" + +msg_info "Cloning pyMC_Repeater (branch: ${BRANCH})..." +pct exec "$CTID" -- bash -c "git clone --branch ${BRANCH} ${REPO} /root/pyMC_Repeater" +msg_ok "Repository cloned" + +# Pre-seed config with CH341 radio type and correct GPIO pins +pct exec "$CTID" -- bash -c " + mkdir -p /etc/pymc_repeater + if [ -f /root/pyMC_Repeater/config.yaml.example ]; then + cp /root/pyMC_Repeater/config.yaml.example /etc/pymc_repeater/config.yaml + # Set radio type to CH341 + sed -i 's/^radio_type: sx1262$/radio_type: sx1262_ch341/' /etc/pymc_repeater/config.yaml + # Replace Pi BCM GPIO pins with CH341 GPIO pin numbers (0-7) + sed -i 's/cs_pin: 21/cs_pin: 0/' /etc/pymc_repeater/config.yaml + sed -i 's/reset_pin: 18/reset_pin: 2/' /etc/pymc_repeater/config.yaml + sed -i 's/busy_pin: 20/busy_pin: 4/' /etc/pymc_repeater/config.yaml + sed -i 's/irq_pin: 16/irq_pin: 6/' /etc/pymc_repeater/config.yaml + sed -i 's/rxen_pin: -1/rxen_pin: 1/' /etc/pymc_repeater/config.yaml + # Enable TCXO and DIO2 RF switch for E22 module + sed -i 's/use_dio3_tcxo: false/use_dio3_tcxo: true/' /etc/pymc_repeater/config.yaml + sed -i 's/use_dio2_rf: false/use_dio2_rf: true/' /etc/pymc_repeater/config.yaml + fi +" + +# ── Run manage.sh install ───────────────────────────────────────────────── +msg_info "Running manage.sh install (this will take several minutes)..." +echo "" +# Use lxc-attach with a pty so manage.sh gets an interactive terminal +lxc-attach -n "$CTID" -- bash -c "cd /root/pyMC_Repeater && TERM=xterm bash manage.sh install" +echo "" +msg_ok "manage.sh install completed" + +# ── Get container IP ────────────────────────────────────────────────────── +sleep 2 +CT_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}') + +# ── Done ────────────────────────────────────────────────────────────────── +echo "" +echo -e "${BLD}" +echo "═══════════════════════════════════════════════════════════════" +echo " ✓ pyMC Repeater Installation Complete!" +echo "═══════════════════════════════════════════════════════════════" +echo -e "${CL}" +echo -e " Container: ${GN}${CTID}${CL} (${CT_HOSTNAME})" +echo -e " IP Address: ${GN}${CT_IP:-unknown}${CL}" +echo -e " Dashboard: ${GN}http://${CT_IP:-}:8000${CL}" +echo "" +echo " Next: open the dashboard and complete the setup wizard" +echo " Management: pct enter ${CTID}, then: cd /opt/pymc_repeater && bash manage.sh" +echo "" +echo "═══════════════════════════════════════════════════════════════" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_engine.py b/tests/test_engine.py new file mode 100644 index 0000000..5de8484 --- /dev/null +++ b/tests/test_engine.py @@ -0,0 +1,1305 @@ +""" +Comprehensive tests for pyMC_Repeater engine.py — RepeaterHandler. + +Covers: flood_forward, direct_forward, process_packet, duplicate detection, +mark_seen, validate_packet, packet scoring, TX delay, cache management, +airtime duty-cycle, and config reloading. +""" +import copy +import math +import time +from collections import OrderedDict +from typing import Optional +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from pymc_core.protocol import Packet +from pymc_core.protocol.constants import ( + MAX_PATH_SIZE, + PH_ROUTE_MASK, + PH_TYPE_SHIFT, + ROUTE_TYPE_DIRECT, + ROUTE_TYPE_FLOOD, + ROUTE_TYPE_TRANSPORT_DIRECT, + ROUTE_TYPE_TRANSPORT_FLOOD, +) + + +# --------------------------------------------------------------------------- +# Helpers — build minimal config / mocks needed by RepeaterHandler.__init__ +# --------------------------------------------------------------------------- + +LOCAL_HASH = 0xAB # repeater's own 1-byte path hash + + +def _make_config(**overrides) -> dict: + """Return a minimal valid config dict for RepeaterHandler.""" + cfg = { + "repeater": { + "mode": "forward", + "cache_ttl": 3600, + "use_score_for_tx": False, + "score_threshold": 0.3, + "send_advert_interval_hours": 0, # off in tests + "node_name": "test-node", + }, + "mesh": { + "global_flood_allow": True, + "loop_detect": "off", + }, + "delays": { + "tx_delay_factor": 1.0, + "direct_tx_delay_factor": 0.5, + }, + "duty_cycle": { + "max_airtime_per_minute": 3600, + "enforcement_enabled": True, + }, + "radio": { + "spreading_factor": 8, + "bandwidth": 125000, + "coding_rate": 8, + "preamble_length": 17, + }, + } + # Merge overrides + for key, val in overrides.items(): + if isinstance(val, dict) and key in cfg: + cfg[key].update(val) + else: + cfg[key] = val + return cfg + + +def _make_radio(): + """Return a mock radio with sensible defaults.""" + radio = MagicMock() + radio.spreading_factor = 8 + radio.bandwidth = 125000 + radio.coding_rate = 8 + radio.preamble_length = 17 + radio.frequency = 915000000 + radio.tx_power = 14 + return radio + + +def _make_dispatcher(radio=None): + """Return a mock dispatcher with a radio and local_identity.""" + dispatcher = MagicMock() + dispatcher.radio = radio or _make_radio() + dispatcher.local_identity = MagicMock() + dispatcher.send_packet = AsyncMock() + return dispatcher + + +@pytest.fixture() +def handler(): + """Create a RepeaterHandler with mocked external dependencies.""" + config = _make_config() + dispatcher = _make_dispatcher() + with ( + patch("repeater.engine.StorageCollector"), + patch("repeater.engine.RepeaterHandler._start_background_tasks"), + ): + from repeater.engine import RepeaterHandler + h = RepeaterHandler(config, dispatcher, LOCAL_HASH) + return h + + +def _make_flood_packet(payload: bytes = b"\x01\x02\x03\x04", + path: bytes = b"", + payload_type: int = 0x01) -> Packet: + """Build a FLOOD-routed packet.""" + pkt = Packet() + # header: route=FLOOD(0x01), payload_type shifted, version=0 + pkt.header = ROUTE_TYPE_FLOOD | (payload_type << PH_TYPE_SHIFT) + pkt.payload = bytearray(payload) + pkt.payload_len = len(payload) + pkt.path = bytearray(path) + pkt.path_len = len(path) + return pkt + + +def _make_direct_packet(payload: bytes = b"\x01\x02\x03\x04", + path: bytes = None, + payload_type: int = 0x01) -> Packet: + """Build a DIRECT-routed packet with path[0] == LOCAL_HASH by default.""" + if path is None: + path = bytes([LOCAL_HASH, 0xCC, 0xDD]) + pkt = Packet() + pkt.header = ROUTE_TYPE_DIRECT | (payload_type << PH_TYPE_SHIFT) + pkt.payload = bytearray(payload) + pkt.payload_len = len(payload) + pkt.path = bytearray(path) + pkt.path_len = len(path) + return pkt + + +def _make_transport_flood_packet(payload: bytes = b"\x01\x02\x03\x04", + path: bytes = b"", + transport_codes=(0x1234, 0x5678)) -> Packet: + """Build a TRANSPORT_FLOOD-routed packet.""" + pkt = Packet() + pkt.header = ROUTE_TYPE_TRANSPORT_FLOOD | (0x01 << PH_TYPE_SHIFT) + pkt.payload = bytearray(payload) + pkt.payload_len = len(payload) + pkt.path = bytearray(path) + pkt.path_len = len(path) + pkt.transport_codes = list(transport_codes) + return pkt + + +def _make_transport_direct_packet(payload: bytes = b"\x01\x02\x03\x04", + path: bytes = None, + transport_codes=(0x1234, 0x5678)) -> Packet: + """Build a TRANSPORT_DIRECT-routed packet with path[0] == LOCAL_HASH.""" + if path is None: + path = bytes([LOCAL_HASH, 0xCC]) + pkt = Packet() + pkt.header = ROUTE_TYPE_TRANSPORT_DIRECT | (0x01 << PH_TYPE_SHIFT) + pkt.payload = bytearray(payload) + pkt.payload_len = len(payload) + pkt.path = bytearray(path) + pkt.path_len = len(path) + pkt.transport_codes = list(transport_codes) + return pkt + + +# =================================================================== +# 1. flood_forward +# =================================================================== + +class TestFloodForward: + """flood_forward: validation, duplicate suppression, path append.""" + + def test_valid_flood_returns_packet(self, handler): + pkt = _make_flood_packet() + result = handler.flood_forward(pkt) + assert result is not None + assert result is pkt # mutated in-place + + def test_local_hash_appended_to_path(self, handler): + pkt = _make_flood_packet(path=b"\x11\x22") + result = handler.flood_forward(pkt) + assert result.path[-1] == LOCAL_HASH + assert list(result.path) == [0x11, 0x22, LOCAL_HASH] + assert result.path_len == 3 + + def test_empty_path_gets_local_hash(self, handler): + pkt = _make_flood_packet(path=b"") + result = handler.flood_forward(pkt) + assert list(result.path) == [LOCAL_HASH] + assert result.path_len == 1 + + def test_duplicate_flood_dropped(self, handler): + pkt = _make_flood_packet() + handler.flood_forward(pkt) + + pkt2 = _make_flood_packet() # same payload → same hash + result = handler.flood_forward(pkt2) + assert result is None + assert pkt2.drop_reason == "Duplicate" + + def test_empty_payload_rejected(self, handler): + pkt = _make_flood_packet(payload=b"") + pkt.payload_len = 0 + result = handler.flood_forward(pkt) + assert result is None + assert "Empty payload" in pkt.drop_reason + + def test_none_payload_rejected(self, handler): + pkt = _make_flood_packet() + pkt.payload = None + result = handler.flood_forward(pkt) + assert result is None + + def test_path_at_max_rejected(self, handler): + pkt = _make_flood_packet(path=bytes(range(MAX_PATH_SIZE))) + result = handler.flood_forward(pkt) + assert result is None + assert "Path length" in pkt.drop_reason + + def test_do_not_retransmit_dropped(self, handler): + pkt = _make_flood_packet() + pkt.mark_do_not_retransmit() + result = handler.flood_forward(pkt) + assert result is None + assert "do not retransmit" in pkt.drop_reason.lower() + + def test_global_flood_deny_plain_flood(self, handler): + handler.config["mesh"]["global_flood_allow"] = False + pkt = _make_flood_packet() + # When global_flood_allow=False, flood_forward calls _check_transport_codes + # which will fail because there are no transport codes on a plain flood + result = handler.flood_forward(pkt) + assert result is None + + def test_hash_computed_before_path_append(self, handler): + """mark_seen must use the pre-append hash so duplicate detection works + when another node sends the same packet with or without our hash.""" + pkt1 = _make_flood_packet(payload=b"\xAA\xBB") + hash_before = pkt1.calculate_packet_hash().hex().upper() + + handler.flood_forward(pkt1) + + # The hash stored in seen_packets should be the PRE-append hash + assert hash_before in handler.seen_packets + + def test_path_len_updated_after_append(self, handler): + pkt = _make_flood_packet(path=b"\x11") + handler.flood_forward(pkt) + assert pkt.path_len == len(pkt.path) == 2 + + def test_different_payloads_not_duplicate(self, handler): + pkt1 = _make_flood_packet(payload=b"\x01") + pkt2 = _make_flood_packet(payload=b"\x02") + r1 = handler.flood_forward(pkt1) + r2 = handler.flood_forward(pkt2) + assert r1 is not None + assert r2 is not None + + def test_path_none_is_handled(self, handler): + pkt = _make_flood_packet() + pkt.path = None + pkt.path_len = 0 + result = handler.flood_forward(pkt) + assert result is not None + assert list(result.path) == [LOCAL_HASH] + + +# =================================================================== +# 2. direct_forward +# =================================================================== + +class TestDirectForward: + """direct_forward: next-hop check, path consumption, duplicate suppression.""" + + def test_valid_direct_returns_packet(self, handler): + pkt = _make_direct_packet() + result = handler.direct_forward(pkt) + assert result is not None + + def test_path_consumed(self, handler): + pkt = _make_direct_packet(path=bytes([LOCAL_HASH, 0xCC, 0xDD])) + result = handler.direct_forward(pkt) + assert list(result.path) == [0xCC, 0xDD] + assert result.path_len == 2 + + def test_single_hop_path_consumed(self, handler): + """After consuming our hash the path becomes empty — packet delivered.""" + pkt = _make_direct_packet(path=bytes([LOCAL_HASH])) + result = handler.direct_forward(pkt) + assert list(result.path) == [] + assert result.path_len == 0 + + def test_wrong_next_hop_dropped(self, handler): + pkt = _make_direct_packet(path=bytes([0xFF, 0xCC])) + result = handler.direct_forward(pkt) + assert result is None + assert "not for us" in pkt.drop_reason + + def test_empty_path_dropped(self, handler): + pkt = _make_direct_packet(path=b"") + pkt.path_len = 0 + result = handler.direct_forward(pkt) + assert result is None + assert "no path" in pkt.drop_reason + + def test_none_path_dropped(self, handler): + pkt = Packet() + pkt.header = ROUTE_TYPE_DIRECT | (0x01 << PH_TYPE_SHIFT) + pkt.payload = bytearray(b"\x01\x02") + pkt.payload_len = 2 + pkt.path = None + pkt.path_len = 0 + result = handler.direct_forward(pkt) + assert result is None + + def test_duplicate_direct_dropped(self, handler): + pkt = _make_direct_packet() + handler.direct_forward(pkt) + + pkt2 = _make_direct_packet() # same payload → same hash + result = handler.direct_forward(pkt2) + assert result is None + assert pkt2.drop_reason == "Duplicate" + + def test_hash_computed_before_path_consume(self, handler): + """mark_seen hash must match the packet as received, before path[0] removal.""" + pkt = _make_direct_packet(path=bytes([LOCAL_HASH, 0xCC])) + hash_before = pkt.calculate_packet_hash().hex().upper() + + handler.direct_forward(pkt) + assert hash_before in handler.seen_packets + + def test_path_len_updated_after_consume(self, handler): + pkt = _make_direct_packet(path=bytes([LOCAL_HASH, 0xCC, 0xDD])) + handler.direct_forward(pkt) + assert pkt.path_len == len(pkt.path) == 2 + + +# =================================================================== +# 3. process_packet — route dispatch +# =================================================================== + +class TestProcessPacket: + """process_packet routes to flood_forward or direct_forward.""" + + def test_flood_route_dispatched(self, handler): + pkt = _make_flood_packet() + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + fwd_pkt, delay = result + # Flood appends local hash + assert fwd_pkt.path[-1] == LOCAL_HASH + + def test_direct_route_dispatched(self, handler): + pkt = _make_direct_packet() + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + fwd_pkt, delay = result + # Direct consumed first hop + assert LOCAL_HASH not in fwd_pkt.path + + def test_transport_flood_dispatched(self, handler): + pkt = _make_transport_flood_packet() + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + fwd_pkt, _ = result + assert fwd_pkt.path[-1] == LOCAL_HASH + + def test_transport_direct_dispatched(self, handler): + pkt = _make_transport_direct_packet() + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + + def test_unknown_route_type_dropped(self, handler): + """A header with route bits = 0x03 is TRANSPORT_DIRECT which IS handled. + We hackily create a truly unknown route by patching both masks.""" + pkt = _make_flood_packet() + # All 4 route types are handled. We can verify the fallback by + # ensuring flood forward fails, e.g. empty payload → returns None. + pkt.payload = None + result = handler.process_packet(pkt, snr=0.0) + assert result is None + + def test_returns_tuple_with_delay(self, handler): + pkt = _make_flood_packet() + result = handler.process_packet(pkt, snr=5.0) + assert isinstance(result, tuple) + assert len(result) == 2 + fwd_pkt, delay = result + assert isinstance(delay, float) + assert delay >= 0.0 + + def test_flood_forward_failure_returns_none(self, handler): + pkt = _make_flood_packet(payload=b"") + pkt.payload_len = 0 + result = handler.process_packet(pkt, snr=0.0) + assert result is None + + def test_direct_forward_failure_returns_none(self, handler): + pkt = _make_direct_packet(path=bytes([0xFF])) # wrong hop + result = handler.process_packet(pkt, snr=0.0) + assert result is None + + +# =================================================================== +# 4. is_duplicate / mark_seen / cache management +# =================================================================== + +class TestDuplicateDetection: + """Duplicate tracking, TTL clean-up, and cache eviction.""" + + def test_unseen_packet_not_duplicate(self, handler): + pkt = _make_flood_packet() + assert handler.is_duplicate(pkt) is False + + def test_after_mark_seen_is_duplicate(self, handler): + pkt = _make_flood_packet() + handler.mark_seen(pkt) + assert handler.is_duplicate(pkt) is True + + def test_different_packets_independent(self, handler): + pkt1 = _make_flood_packet(payload=b"\x01") + pkt2 = _make_flood_packet(payload=b"\x02") + handler.mark_seen(pkt1) + assert handler.is_duplicate(pkt1) is True + assert handler.is_duplicate(pkt2) is False + + def test_cache_eviction_at_max_size(self, handler): + handler.max_cache_size = 5 + packets = [_make_flood_packet(payload=bytes([i])) for i in range(6)] + for p in packets: + handler.mark_seen(p) + # Oldest (packets[0]) should have been evicted + assert handler.is_duplicate(packets[0]) is False + assert handler.is_duplicate(packets[5]) is True + + def test_cleanup_cache_removes_expired(self, handler): + pkt = _make_flood_packet() + handler.mark_seen(pkt) + pkt_hash = pkt.calculate_packet_hash().hex().upper() + # Manually expire it + handler.seen_packets[pkt_hash] = time.time() - handler.cache_ttl - 1 + handler.cleanup_cache() + assert handler.is_duplicate(pkt) is False + + def test_cleanup_cache_keeps_fresh(self, handler): + pkt = _make_flood_packet() + handler.mark_seen(pkt) + handler.cleanup_cache() + assert handler.is_duplicate(pkt) is True + + def test_mark_seen_stores_hex_upper_key(self, handler): + pkt = _make_flood_packet() + handler.mark_seen(pkt) + for key in handler.seen_packets: + assert key == key.upper() + + +# =================================================================== +# 5. validate_packet +# =================================================================== + +class TestValidatePacket: + """validate_packet: empty payload, oversized path.""" + + def test_valid_packet(self, handler): + pkt = _make_flood_packet() + valid, reason = handler.validate_packet(pkt) + assert valid is True + assert reason == "" + + def test_empty_payload_fails(self, handler): + pkt = _make_flood_packet(payload=b"") + pkt.payload = None + valid, reason = handler.validate_packet(pkt) + assert valid is False + assert "Empty" in reason + + def test_path_at_max_fails(self, handler): + pkt = _make_flood_packet(path=bytes(range(MAX_PATH_SIZE))) + valid, reason = handler.validate_packet(pkt) + assert valid is False + assert "MAX_PATH_SIZE" in reason + + def test_path_one_below_max_passes(self, handler): + pkt = _make_flood_packet(path=bytes(range(MAX_PATH_SIZE - 1))) + valid, reason = handler.validate_packet(pkt) + assert valid is True + + def test_none_packet(self, handler): + valid, reason = handler.validate_packet(None) + assert valid is False + + +# =================================================================== +# 6. calculate_packet_score — static method +# =================================================================== + +class TestPacketScore: + """Score: SNR thresholds, collision penalty, clamping.""" + + def test_below_threshold_returns_zero(self): + from repeater.engine import RepeaterHandler + # SF8 threshold is -10.0 + score = RepeaterHandler.calculate_packet_score(snr=-15.0, packet_len=50, spreading_factor=8) + assert score == 0.0 + + def test_at_threshold_returns_zero(self): + from repeater.engine import RepeaterHandler + score = RepeaterHandler.calculate_packet_score(snr=-10.0, packet_len=50, spreading_factor=8) + assert score == 0.0 + + def test_above_threshold_positive(self): + from repeater.engine import RepeaterHandler + score = RepeaterHandler.calculate_packet_score(snr=0.0, packet_len=50, spreading_factor=8) + assert score > 0.0 + + def test_high_snr_high_score(self): + from repeater.engine import RepeaterHandler + score = RepeaterHandler.calculate_packet_score(snr=10.0, packet_len=10, spreading_factor=8) + assert score > 0.5 + + def test_long_packet_collision_penalty(self): + from repeater.engine import RepeaterHandler + short = RepeaterHandler.calculate_packet_score(snr=5.0, packet_len=10, spreading_factor=8) + long_ = RepeaterHandler.calculate_packet_score(snr=5.0, packet_len=250, spreading_factor=8) + assert short > long_ + + def test_score_clamped_to_0_1(self): + from repeater.engine import RepeaterHandler + score = RepeaterHandler.calculate_packet_score(snr=50.0, packet_len=1, spreading_factor=8) + assert 0.0 <= score <= 1.0 + + def test_sf_below_7_returns_zero(self): + from repeater.engine import RepeaterHandler + score = RepeaterHandler.calculate_packet_score(snr=10.0, packet_len=50, spreading_factor=6) + assert score == 0.0 + + def test_each_sf_has_different_threshold(self): + from repeater.engine import RepeaterHandler + scores = {} + for sf in (7, 8, 9, 10, 11, 12): + scores[sf] = RepeaterHandler.calculate_packet_score(snr=-5.0, packet_len=50, spreading_factor=sf) + # Higher SF → lower threshold → better reception at same SNR + # At SNR=-5, SF7 (threshold -7.5) should be worse than SF12 (threshold -20) + assert scores[12] > scores[7] + + +# =================================================================== +# 7. _calculate_tx_delay +# =================================================================== + +class TestTxDelay: + """TX delay: flood random, direct fixed, score adjustment, cap.""" + + def test_flood_delay_non_negative(self, handler): + pkt = _make_flood_packet() + delay = handler._calculate_tx_delay(pkt, snr=0.0) + assert delay >= 0.0 + + def test_flood_delay_capped_at_5s(self, handler): + handler.tx_delay_factor = 1000.0 # extreme multiplier + pkt = _make_flood_packet() + delay = handler._calculate_tx_delay(pkt, snr=0.0) + assert delay <= 5.0 + + def test_direct_delay_uses_factor(self, handler): + handler.direct_tx_delay_factor = 1.23 + pkt = _make_direct_packet() + delay = handler._calculate_tx_delay(pkt, snr=0.0) + # Direct packets use direct_tx_delay_factor directly (in seconds) + # Score adjustment may change it, but base should be 1.23 when score is off + assert delay == pytest.approx(1.23, abs=0.01) + + def test_score_adjustment_reduces_delay(self, handler): + handler.use_score_for_tx = True + pkt = _make_flood_packet(payload=b"\x01" * 50) + # High SNR → high score → shorter delay + delays = [] + for _ in range(50): + d = handler._calculate_tx_delay(pkt, snr=10.0) + delays.append(d) + avg_high_snr = sum(delays) / len(delays) + + # Low SNR → low score → longer delay + delays_low = [] + for _ in range(50): + d = handler._calculate_tx_delay(pkt, snr=-5.0) + delays_low.append(d) + avg_low_snr = sum(delays_low) / len(delays_low) + + # Using statistical comparison — average high-SNR delay should be lower + # (Both use random, but multiplier differs) + # This is non-deterministic; we just check score path doesn't crash + assert avg_high_snr >= 0.0 + assert avg_low_snr >= 0.0 + + def test_zero_tx_delay_factor(self, handler): + handler.tx_delay_factor = 0.0 + pkt = _make_flood_packet() + delay = handler._calculate_tx_delay(pkt, snr=0.0) + assert delay == 0.0 + + def test_transport_direct_uses_direct_delay(self, handler): + handler.direct_tx_delay_factor = 0.77 + pkt = _make_transport_direct_packet() + delay = handler._calculate_tx_delay(pkt, snr=0.0) + assert delay == pytest.approx(0.77, abs=0.01) + + +# =================================================================== +# 8. Hash stability through forwarding operations +# =================================================================== + +class TestHashStabilityThroughForwarding: + """Verify hash is computed on original packet (before path mutation).""" + + def test_flood_hash_unchanged_after_forward(self, handler): + pkt = _make_flood_packet(payload=b"\xDE\xAD") + hash_before = pkt.calculate_packet_hash().hex().upper() + + handler.flood_forward(pkt) + # The hash stored should be the pre-modification hash + assert hash_before in handler.seen_packets + + def test_direct_hash_unchanged_after_forward(self, handler): + pkt = _make_direct_packet(payload=b"\xBE\xEF", + path=bytes([LOCAL_HASH, 0xCC])) + hash_before = pkt.calculate_packet_hash().hex().upper() + + handler.direct_forward(pkt) + assert hash_before in handler.seen_packets + + def test_flood_second_identical_detected_as_duplicate(self, handler): + """Two identical packets with the same payload (but path not yet modified) + should be correctly detected as duplicates.""" + p1 = _make_flood_packet(payload=b"\xCA\xFE") + p2 = _make_flood_packet(payload=b"\xCA\xFE") + handler.flood_forward(p1) + result = handler.flood_forward(p2) + assert result is None + + def test_direct_second_identical_detected_as_duplicate(self, handler): + p1 = _make_direct_packet(payload=b"\xCA\xFE", + path=bytes([LOCAL_HASH, 0x11])) + p2 = _make_direct_packet(payload=b"\xCA\xFE", + path=bytes([LOCAL_HASH, 0x11])) + handler.direct_forward(p1) + result = handler.direct_forward(p2) + assert result is None + + +# =================================================================== +# 9. Global flood policy +# =================================================================== + +class TestGlobalFloodPolicy: + """global_flood_allow=False blocks plain flood, transport checked.""" + + def test_flood_blocked_by_policy(self, handler): + handler.config["mesh"]["global_flood_allow"] = False + pkt = _make_flood_packet() + result = handler.flood_forward(pkt) + assert result is None + + def test_direct_unaffected_by_flood_policy(self, handler): + handler.config["mesh"]["global_flood_allow"] = False + pkt = _make_direct_packet() + result = handler.direct_forward(pkt) + assert result is not None # direct is not blocked by flood policy + + def test_transport_flood_checked_when_policy_off(self, handler): + handler.config["mesh"]["global_flood_allow"] = False + pkt = _make_transport_flood_packet() + # Will call _check_transport_codes which will fail (no storage keys) + result = handler.flood_forward(pkt) + assert result is None + + +class TestFloodLoopDetection: + """MeshCore-style loop detection for flood forwarding.""" + + def test_loop_detect_off_allows_looped_path(self, handler): + handler.config["mesh"]["loop_detect"] = "off" + handler.reload_runtime_config() + pkt = _make_flood_packet(path=bytes([LOCAL_HASH, 0x11, LOCAL_HASH])) + result = handler.flood_forward(pkt) + assert result is not None + + def test_loop_detect_minimal_drops_at_four(self, handler): + handler.config["mesh"]["loop_detect"] = "minimal" + handler.reload_runtime_config() + pkt = _make_flood_packet(path=bytes([LOCAL_HASH, LOCAL_HASH, LOCAL_HASH, LOCAL_HASH])) + result = handler.flood_forward(pkt) + assert result is None + assert "loop detected" in (pkt.drop_reason or "").lower() + + def test_loop_detect_minimal_allows_below_threshold(self, handler): + handler.config["mesh"]["loop_detect"] = "minimal" + handler.reload_runtime_config() + pkt = _make_flood_packet(path=bytes([LOCAL_HASH, LOCAL_HASH, LOCAL_HASH])) + result = handler.flood_forward(pkt) + assert result is not None + + def test_loop_detect_moderate_drops_at_two(self, handler): + handler.config["mesh"]["loop_detect"] = "moderate" + handler.reload_runtime_config() + pkt = _make_flood_packet(path=bytes([LOCAL_HASH, 0x22, LOCAL_HASH])) + result = handler.flood_forward(pkt) + assert result is None + + def test_loop_detect_strict_drops_at_one(self, handler): + handler.config["mesh"]["loop_detect"] = "strict" + handler.reload_runtime_config() + pkt = _make_flood_packet(path=bytes([0x33, LOCAL_HASH, 0x44])) + result = handler.flood_forward(pkt) + assert result is None + + +# =================================================================== +# 10. Airtime / duty-cycle integration +# =================================================================== + +class TestAirtimeIntegration: + """Airtime calculation and duty-cycle enforcement.""" + + def test_airtime_positive(self, handler): + airtime = handler.airtime_mgr.calculate_airtime(50) + assert airtime > 0.0 + + def test_can_transmit_fresh(self, handler): + can_tx, wait = handler.airtime_mgr.can_transmit(100.0) + assert can_tx is True + assert wait == 0.0 + + def test_cannot_transmit_after_exhaustion(self, handler): + # Fill up the budget + handler.airtime_mgr.record_tx(handler.airtime_mgr.max_airtime_per_minute) + can_tx, wait = handler.airtime_mgr.can_transmit(1.0) + assert can_tx is False + assert wait > 0.0 + + def test_duty_cycle_disabled(self, handler): + handler.config["duty_cycle"]["enforcement_enabled"] = False + handler.airtime_mgr.config = handler.config + handler.airtime_mgr.record_tx(999999) + can_tx, wait = handler.airtime_mgr.can_transmit(999999) + assert can_tx is True + + def test_airtime_increases_with_packet_size(self, handler): + short = handler.airtime_mgr.calculate_airtime(10) + long_ = handler.airtime_mgr.calculate_airtime(200) + assert long_ > short + + +# =================================================================== +# 11. Config reload +# =================================================================== + +class TestConfigReload: + """reload_runtime_config updates in-memory state.""" + + def test_tx_delay_factor_reloaded(self, handler): + handler.config["delays"]["tx_delay_factor"] = 3.14 + handler.reload_runtime_config() + assert handler.tx_delay_factor == 3.14 + + def test_direct_tx_delay_factor_reloaded(self, handler): + handler.config["delays"]["direct_tx_delay_factor"] = 2.5 + handler.reload_runtime_config() + assert handler.direct_tx_delay_factor == 2.5 + + def test_use_score_for_tx_reloaded(self, handler): + handler.config["repeater"]["use_score_for_tx"] = True + handler.reload_runtime_config() + assert handler.use_score_for_tx is True + + def test_cache_ttl_reloaded(self, handler): + handler.config["repeater"]["cache_ttl"] = 120 + handler.reload_runtime_config() + assert handler.cache_ttl == 120 + + +# =================================================================== +# 12. _get_drop_reason +# =================================================================== + +class TestGetDropReason: + """_get_drop_reason: determine why a packet was not forwarded.""" + + def test_duplicate_reason(self, handler): + pkt = _make_flood_packet() + handler.mark_seen(pkt) + reason = handler._get_drop_reason(pkt) + assert reason == "Duplicate" + + def test_empty_payload_reason(self, handler): + pkt = _make_flood_packet(payload=b"") + pkt.payload_len = 0 + pkt.payload = bytearray() + reason = handler._get_drop_reason(pkt) + assert "Empty" in reason + + def test_path_too_long_reason(self, handler): + pkt = _make_flood_packet(path=bytes(range(MAX_PATH_SIZE))) + reason = handler._get_drop_reason(pkt) + assert "Path too long" in reason + + def test_flood_policy_reason(self, handler): + handler.config["mesh"]["global_flood_allow"] = False + pkt = _make_flood_packet() + reason = handler._get_drop_reason(pkt) + assert "flood" in reason.lower() + + def test_direct_not_for_us_reason(self, handler): + pkt = _make_direct_packet(path=bytes([0xFF, 0xCC])) + reason = handler._get_drop_reason(pkt) + assert "not for us" in reason + + def test_direct_no_path_reason(self, handler): + pkt = _make_direct_packet(path=b"") + pkt.path_len = 0 + reason = handler._get_drop_reason(pkt) + assert "no path" in reason + + +# =================================================================== +# 13. Transport route forwarding +# =================================================================== + +class TestTransportForwarding: + """TRANSPORT_FLOOD and TRANSPORT_DIRECT: packet routing through process_packet.""" + + def test_transport_flood_appends_path(self, handler): + pkt = _make_transport_flood_packet(path=b"\x11") + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + fwd_pkt, _ = result + assert fwd_pkt.path[-1] == LOCAL_HASH + assert len(fwd_pkt.path) == 2 + + def test_transport_direct_consumes_path(self, handler): + pkt = _make_transport_direct_packet(path=bytes([LOCAL_HASH, 0xCC])) + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + fwd_pkt, _ = result + assert list(fwd_pkt.path) == [0xCC] + + def test_transport_codes_preserved_after_flood(self, handler): + pkt = _make_transport_flood_packet(transport_codes=(0xAAAA, 0xBBBB)) + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + fwd_pkt, _ = result + assert fwd_pkt.transport_codes == [0xAAAA, 0xBBBB] + + def test_transport_codes_preserved_after_direct(self, handler): + pkt = _make_transport_direct_packet(transport_codes=(0x1111, 0x2222)) + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + fwd_pkt, _ = result + assert fwd_pkt.transport_codes == [0x1111, 0x2222] + + +# =================================================================== +# 14. Statistics tracking +# =================================================================== + +class TestStatistics: + """RX/TX/dropped counters and recent_packets list.""" + + def test_initial_counters_zero(self, handler): + assert handler.rx_count == 0 + assert handler.forwarded_count == 0 + assert handler.dropped_count == 0 + + def test_get_stats_returns_dict(self, handler): + with patch.object(handler, "storage", None): + stats = handler.get_stats() + assert "rx_count" in stats + assert "forwarded_count" in stats + assert "dropped_count" in stats + assert "local_hash" in stats + assert "uptime_seconds" in stats + + def test_get_stats_local_hash_format(self, handler): + with patch.object(handler, "storage", None): + stats = handler.get_stats() + assert stats["local_hash"] == f"0x{LOCAL_HASH:02x}" + + +# =================================================================== +# 15. Edge cases and regression tests +# =================================================================== + +class TestEdgeCases: + """Miscellaneous edge cases and regressions.""" + + def test_path_as_list_converted_to_bytearray(self, handler): + """flood_forward should handle path being a list (not bytearray).""" + pkt = _make_flood_packet() + pkt.path = [0x11, 0x22] + pkt.path_len = 2 + result = handler.flood_forward(pkt) + assert result is not None + assert isinstance(result.path, bytearray) + assert list(result.path) == [0x11, 0x22, LOCAL_HASH] + + def test_flood_forward_idempotent_on_second_call(self, handler): + """Calling flood_forward again with the SAME packet object should + detect as duplicate (the first call already mark_seen'd it).""" + pkt = _make_flood_packet(payload=b"\xFF" * 10) + r1 = handler.flood_forward(pkt) + assert r1 is not None + # Now pkt has local_hash appended, but hash was computed pre-append. + # A new packet with same original payload should be duplicate. + pkt2 = _make_flood_packet(payload=b"\xFF" * 10) + r2 = handler.flood_forward(pkt2) + assert r2 is None + + def test_large_payload_still_forwarded(self, handler): + pkt = _make_flood_packet(payload=bytes(range(256)) * 4) + result = handler.flood_forward(pkt) + assert result is not None + + def test_payload_type_encoding_in_header(self, handler): + """Verify header construction encodes payload_type correctly.""" + for pt in range(16): + pkt = _make_flood_packet(payload=bytes([pt, 0xFF]), payload_type=pt) + assert pkt.get_payload_type() == pt + result = handler.flood_forward(pkt) + assert result is not None + + def test_many_distinct_packets_all_forwarded(self, handler): + """100 unique packets should all be forwarded (no false duplicates).""" + for i in range(100): + pkt = _make_flood_packet(payload=i.to_bytes(4, "big")) + result = handler.flood_forward(pkt) + assert result is not None, f"Packet {i} incorrectly detected as duplicate" + + def test_cache_eviction_order_is_fifo(self, handler): + handler.max_cache_size = 3 + pkts = [_make_flood_packet(payload=bytes([i])) for i in range(4)] + for p in pkts: + handler.mark_seen(p) + # First one evicted + assert handler.is_duplicate(pkts[0]) is False + # Last three still present + for p in pkts[1:]: + assert handler.is_duplicate(p) is True + + def test_do_not_retransmit_with_custom_drop_reason(self, handler): + pkt = _make_flood_packet() + pkt.mark_do_not_retransmit() + pkt.drop_reason = "Custom reason" + result = handler.flood_forward(pkt) + assert result is None + # Custom reason should be preserved (not overwritten) + assert pkt.drop_reason == "Custom reason" + + def test_monitor_mode_skips_processing(self, handler): + """In monitor mode, process_packet is not called at all in __call__, + but process_packet itself doesn't check mode — that's done in __call__. + We test that process_packet works irrespective of mode.""" + handler.config["repeater"]["mode"] = "monitor" + pkt = _make_flood_packet() + # process_packet doesn't check mode — should still work + result = handler.process_packet(pkt, snr=0.0) + assert result is not None + + +# =================================================================== +# 16. Airtime calculation correctness +# =================================================================== + +class TestAirtimeCalculation: + """Semtech LoRa airtime formula validation.""" + + def test_known_airtime_sf7_125khz(self, handler): + """SF7, 125kHz, CR4/5, 10-byte payload — well-known reference value.""" + mgr = handler.airtime_mgr + # Override to known settings + at = mgr.calculate_airtime(10, spreading_factor=7, bandwidth_hz=125000, + coding_rate=5, preamble_len=8) + # Semtech calculator: ~36ms for these params + assert 30.0 < at < 50.0 + + def test_sf12_much_slower_than_sf7(self, handler): + mgr = handler.airtime_mgr + at7 = mgr.calculate_airtime(50, spreading_factor=7) + at12 = mgr.calculate_airtime(50, spreading_factor=12) + # SF12 is roughly 32x slower than SF7 per symbol + assert at12 > at7 * 10 + + def test_zero_payload_still_has_preamble(self, handler): + mgr = handler.airtime_mgr + at = mgr.calculate_airtime(0) + assert at > 0.0 + + +# =================================================================== +# 17. Curated good-packet and bad-packet arrays +# =================================================================== + +# ---- 20 GOOD packets: all should be forwarded by process_packet ---- +GOOD_PACKETS = [ + # (id, description, builder) + ("good_flood_minimal", + "Flood, 1-byte payload, empty path", + lambda: _make_flood_packet(payload=b"\x01")), + + ("good_flood_typical", + "Flood, 10-byte payload, 2-hop path", + lambda: _make_flood_packet(payload=bytes(range(10)), path=b"\x11\x22")), + + ("good_flood_max_payload_type", + "Flood, payload_type=15 (max 4-bit)", + lambda: _make_flood_packet(payload=b"\xAA\xBB", payload_type=15)), + + ("good_flood_payload_type_0", + "Flood, payload_type=0 (plain text)", + lambda: _make_flood_packet(payload=b"\x01\x02\x03", payload_type=0)), + + ("good_flood_long_payload", + "Flood, 200-byte payload", + lambda: _make_flood_packet(payload=bytes(range(200)))), + + ("good_flood_single_byte_path", + "Flood, path has 1 prior hop", + lambda: _make_flood_packet(payload=b"\xDE\xAD", path=b"\x42")), + + ("good_flood_path_near_max", + "Flood, path = MAX_PATH_SIZE - 1 (room for our hash)", + lambda: _make_flood_packet(payload=b"\xFF", path=bytes(range(MAX_PATH_SIZE - 1)))), + + ("good_flood_binary_payload", + "Flood, all-zero payload", + lambda: _make_flood_packet(payload=b"\x00" * 16)), + + ("good_flood_high_entropy", + "Flood, high-entropy random-looking payload", + lambda: _make_flood_packet(payload=bytes(i ^ 0xA5 for i in range(64)))), + + ("good_flood_advert_type", + "Flood, payload_type=4 (ADVERT)", + lambda: _make_flood_packet(payload=b"\xAB\x01\x02\x03", payload_type=4)), + + ("good_direct_minimal", + "Direct, 1-byte payload, single hop to us", + lambda: _make_direct_packet(payload=b"\x01", path=bytes([LOCAL_HASH]))), + + ("good_direct_multihop", + "Direct, 3-hop remaining path (us + 2 more)", + lambda: _make_direct_packet(payload=b"\xCA\xFE", path=bytes([LOCAL_HASH, 0x11, 0x22]))), + + ("good_direct_long_payload", + "Direct, 150-byte payload", + lambda: _make_direct_packet(payload=bytes(range(150)), path=bytes([LOCAL_HASH, 0xBB]))), + + ("good_direct_type_2", + "Direct, payload_type=2 (ACK)", + lambda: _make_direct_packet(payload=b"\x01\x02", path=bytes([LOCAL_HASH]), + payload_type=2)), + + ("good_direct_long_remaining_path", + "Direct, 10 hops remaining after us", + lambda: _make_direct_packet(payload=b"\xFF\xEE", + path=bytes([LOCAL_HASH] + list(range(10))))), + + ("good_transport_flood_basic", + "Transport flood, basic payload + transport codes", + lambda: _make_transport_flood_packet(payload=b"\x01\x02\x03\x04")), + + ("good_transport_flood_with_path", + "Transport flood, existing 3-hop path", + lambda: _make_transport_flood_packet(payload=b"\xAA\xBB", path=b"\x11\x22\x33")), + + ("good_transport_flood_max_codes", + "Transport flood, max uint16 transport codes", + lambda: _make_transport_flood_packet(payload=b"\xFF", + transport_codes=(0xFFFF, 0xFFFF))), + + ("good_transport_direct_basic", + "Transport direct, basic hop to us", + lambda: _make_transport_direct_packet(payload=b"\x01\x02")), + + ("good_transport_direct_long_path", + "Transport direct, 5 remaining hops", + lambda: _make_transport_direct_packet( + payload=b"\xDE\xAD\xBE\xEF", + path=bytes([LOCAL_HASH, 0x11, 0x22, 0x33, 0x44]))), +] + + +# ---- 20 BAD packets: all should be dropped / return None ---- +BAD_PACKETS = [ + # (id, description, builder) + ("bad_empty_payload", + "Empty bytearray payload", + lambda: _make_flood_packet(payload=b""), + "Empty payload"), + + ("bad_none_payload", + "payload = None", + lambda: (lambda p: (setattr(p, "payload", None), p)[-1])(_make_flood_packet()), + "Empty payload"), + + ("bad_path_at_max", + "Path exactly MAX_PATH_SIZE — no room to append", + lambda: _make_flood_packet(payload=b"\x01", path=bytes(range(MAX_PATH_SIZE))), + "Path length"), + + ("bad_path_over_max", + "Path exceeds MAX_PATH_SIZE", + lambda: _make_flood_packet(payload=b"\x01", path=bytes(range(MAX_PATH_SIZE + 5))), + "Path length"), + + ("bad_do_not_retransmit", + "Marked do-not-retransmit", + lambda: (lambda p: (p.mark_do_not_retransmit(), p)[-1])(_make_flood_packet()), + "do not retransmit"), + + ("bad_direct_wrong_hop", + "Direct packet, path[0] != LOCAL_HASH", + lambda: _make_direct_packet(path=bytes([0xFF, 0xCC])), + "not for us"), + + ("bad_direct_empty_path", + "Direct packet with empty path", + lambda: _make_direct_packet(path=b""), + "no path"), + + ("bad_direct_none_path", + "Direct packet with path = None", + lambda: (lambda p: (setattr(p, "path", None), setattr(p, "path_len", 0), p)[-1])( + _make_direct_packet()), + "no path"), + + ("bad_flood_policy_off", + "Plain flood when global_flood_allow=False (needs config override)", + lambda: _make_flood_packet(payload=b"\x01\x02"), + "transport codes"), + + ("bad_transport_flood_policy_off", + "Transport flood when policy off (no valid transport key)", + lambda: _make_transport_flood_packet(payload=b"\x01\x02"), + "transport"), + + ("bad_direct_empty_payload", + "Direct with empty payload (now caught by validate_packet)", + lambda: (lambda p: (setattr(p, "payload", bytearray()), setattr(p, "payload_len", 0), p)[-1])( + _make_direct_packet(path=bytes([LOCAL_HASH]))), + "Empty payload"), + + ("bad_flood_zero_len_payload", + "Flood with payload_len forced to 0", + lambda: (lambda p: (setattr(p, "payload_len", 0), setattr(p, "payload", bytearray()), p)[-1])( + _make_flood_packet(payload=b"\x01")), + "Empty payload"), + + ("bad_direct_only_wrong_hops", + "Direct path of all 0xFF bytes (none match LOCAL_HASH)", + lambda: _make_direct_packet(path=bytes([0xFF, 0xFE, 0xFD])), + "not for us"), + + ("bad_transport_direct_wrong_hop", + "Transport direct with wrong first hop", + lambda: _make_transport_direct_packet(path=bytes([0x01, 0x02])), + "not for us"), + + ("bad_transport_direct_empty_path", + "Transport direct with empty path", + lambda: _make_transport_direct_packet(path=b""), + "no path"), + + ("bad_transport_direct_none_path", + "Transport direct with path = None", + lambda: (lambda p: (setattr(p, "path", None), setattr(p, "path_len", 0), p)[-1])( + _make_transport_direct_packet()), + "no path"), + + ("bad_flood_payload_255_zeros", + "Flood with payload = bytearray(0) (empty)", + lambda: (lambda p: (setattr(p, "payload", bytearray()), setattr(p, "payload_len", 0), p)[-1])( + _make_flood_packet()), + "Empty payload"), + + ("bad_direct_none_payload", + "Direct with None payload (now caught by validate_packet)", + lambda: (lambda p: (setattr(p, "payload", None), p)[-1])( + _make_direct_packet(path=bytes([LOCAL_HASH]))), + "Empty payload"), + + ("bad_flood_do_not_retransmit_custom", + "Flood, do-not-retransmit with custom drop reason", + lambda: (lambda p: (p.mark_do_not_retransmit(), setattr(p, "drop_reason", "Advert consumed"), p)[-1])( + _make_flood_packet(payload=b"\xAB")), + "Advert consumed"), + + ("bad_direct_do_not_retransmit", + "Direct, marked do-not-retransmit (now caught by direct_forward)", + lambda: (lambda p: (p.mark_do_not_retransmit(), p)[-1])( + _make_direct_packet(payload=b"\x99", path=bytes([LOCAL_HASH, 0x11]))), + "do not retransmit"), +] + + +# Pytest ids for readable output +_good_ids = [g[0] for g in GOOD_PACKETS] +_bad_ids = [b[0] for b in BAD_PACKETS] + + +class TestGoodPacketArray: + """All 20 good packets should be forwarded successfully.""" + + @pytest.mark.parametrize( + "name, desc, builder", GOOD_PACKETS, ids=_good_ids, + ) + def test_process_packet_forwards(self, handler, name, desc, builder): + pkt = builder() + result = handler.process_packet(pkt, snr=5.0) + assert result is not None, f"[{name}] {desc} — unexpectedly dropped" + fwd_pkt, delay = result + assert delay >= 0.0 + + @pytest.mark.parametrize( + "name, desc, builder", GOOD_PACKETS, ids=_good_ids, + ) + def test_good_packet_not_duplicate_on_first_see(self, handler, name, desc, builder): + pkt = builder() + assert handler.is_duplicate(pkt) is False, f"[{name}] falsely flagged as duplicate" + + @pytest.mark.parametrize( + "name, desc, builder", GOOD_PACKETS, ids=_good_ids, + ) + def test_good_packet_path_modified(self, handler, name, desc, builder): + pkt = builder() + route = pkt.header & PH_ROUTE_MASK + original_path = list(pkt.path) if pkt.path else [] + + result = handler.process_packet(pkt, snr=5.0) + assert result is not None + + fwd_pkt, _ = result + if route in (ROUTE_TYPE_FLOOD, ROUTE_TYPE_TRANSPORT_FLOOD): + assert fwd_pkt.path[-1] == LOCAL_HASH, f"[{name}] local hash not appended" + assert len(fwd_pkt.path) == len(original_path) + 1 + else: + # Direct: first hop consumed + assert len(fwd_pkt.path) == len(original_path) - 1 + + +class TestBadPacketArray: + """All 20 bad packets should be dropped by the engine.""" + + @pytest.mark.parametrize( + "name, desc, builder, expected_reason", + BAD_PACKETS, ids=_bad_ids, + ) + def test_process_packet_drops(self, handler, name, desc, builder, expected_reason): + # Two entries need global_flood_allow=False + if "policy_off" in name: + handler.config["mesh"]["global_flood_allow"] = False + + pkt = builder() + result = handler.process_packet(pkt, snr=5.0) + assert result is None, f"[{name}] {desc} — should have been dropped" + + @pytest.mark.parametrize( + "name, desc, builder, expected_reason", + BAD_PACKETS, ids=_bad_ids, + ) + def test_drop_reason_set(self, handler, name, desc, builder, expected_reason): + if "policy_off" in name: + handler.config["mesh"]["global_flood_allow"] = False + + pkt = builder() + handler.process_packet(pkt, snr=5.0) + assert pkt.drop_reason is not None, f"[{name}] drop_reason not set" + assert expected_reason.lower() in pkt.drop_reason.lower(), ( + f"[{name}] expected '{expected_reason}' in drop_reason, got '{pkt.drop_reason}'" + ) + + @pytest.mark.parametrize( + "name, desc, builder, expected_reason", + BAD_PACKETS, ids=_bad_ids, + ) + def test_bad_packet_not_marked_seen(self, handler, name, desc, builder, expected_reason): + """Dropped packets must NOT pollute the seen cache.""" + if "policy_off" in name: + handler.config["mesh"]["global_flood_allow"] = False + + pkt = builder() + handler.process_packet(pkt, snr=5.0) + # Should not be in seen_packets (except do-not-retransmit and policy + # packets which fail AFTER validation but BEFORE mark_seen) + # Actually none of these should be marked seen — the engine only + # calls mark_seen on packets that pass all checks. + pkt_hash = pkt.calculate_packet_hash().hex().upper() if pkt.payload else None + if pkt_hash: + assert pkt_hash not in handler.seen_packets, ( + f"[{name}] bad packet was incorrectly added to seen cache" + )