mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-04 08:52:28 +02:00
Merge pull request #253 from pyMC-dev/wip/null-radio-before-pr250
Wip/null radio before pr250
This commit is contained in:
+2
-5
@@ -1,7 +1,3 @@
|
||||
# Default Repeater Configuration
|
||||
# radio_type: sx1262 | kiss (use kiss for serial KISS TNC modem)
|
||||
radio_type: sx1262
|
||||
|
||||
repeater:
|
||||
# Node name for logging and identification
|
||||
node_name: "mesh-repeater-01"
|
||||
@@ -317,7 +313,8 @@ identities:
|
||||
# - kiss (KISS-modem over a serial port; alias: kiss-modem)
|
||||
# - pymc_tcp (pymc_usb firmware modem over Wi-Fi/TCP)
|
||||
# - pymc_usb (pymc_usb firmware modem over USB-CDC)
|
||||
radio_type: sx1262
|
||||
# - null/none (disable radio hardware; daemon starts without RF I/O)
|
||||
radio_type: null
|
||||
|
||||
# CH341 USB-to-SPI adapter settings (only used when radio_type: sx1262_ch341)
|
||||
# NOTE: VID/PID are integers. Hex is also accepted in YAML, e.g. 0x1A86.
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
"ch341-usb-sx1262": {
|
||||
"name": "CH341 USB-SPI + SX1262 (example)",
|
||||
"description": "SX1262 via CH341 USB-to-SPI adapter. NOTE: pin numbers are CH341 GPIO 0-7, not BCM.",
|
||||
"connection_type": "usb",
|
||||
"radio_type": "sx1262_ch341",
|
||||
"vid": 6790,
|
||||
"pid": 21778,
|
||||
@@ -322,6 +323,30 @@
|
||||
"use_dio2_rf": false,
|
||||
"use_dio3_tcxo": true,
|
||||
"preamble_length": 17
|
||||
},
|
||||
"pymc_usb": {
|
||||
"name": "pymc_usb modem (USB-CDC)",
|
||||
"description": "ESP32-S3 / nRF52 board running pymc_usb firmware as a USB-CDC LoRa modem. Pick this if the modem is plugged into the host's USB port (e.g. /dev/ttyACM0). Edit the 'pymc_usb' section after first-run to point at the right serial device.",
|
||||
"connection_type": "usb",
|
||||
"radio_type": "pymc_usb",
|
||||
"tx_power": 22,
|
||||
"preamble_length": 16
|
||||
},
|
||||
"pymc_tcp": {
|
||||
"name": "pymc_tcp modem (Wi-Fi / Ethernet)",
|
||||
"description": "ESP32 board running pymc_usb firmware exposed as a TCP server over Wi-Fi or Ethernet. After first-run, set 'pymc_tcp.host' to the modem's LAN address or mDNS name (e.g. pymc-3e2834.local).",
|
||||
"connection_type": "network",
|
||||
"radio_type": "pymc_tcp",
|
||||
"tx_power": 22,
|
||||
"preamble_length": 16
|
||||
},
|
||||
"kiss": {
|
||||
"name": "KISS modem (serial)",
|
||||
"description": "MeshCore KISS modem over serial - requires pyMC_core with KISS support.",
|
||||
"connection_type": "usb",
|
||||
"radio_type": "kiss",
|
||||
"tx_power": 14,
|
||||
"preamble_length": 17
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
-5
@@ -2,13 +2,47 @@ import base64
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, Optional, overload
|
||||
|
||||
import yaml
|
||||
|
||||
logger = logging.getLogger("Config")
|
||||
|
||||
|
||||
class NullRadio:
|
||||
"""No-op radio used when radio_type disables hardware initialization."""
|
||||
|
||||
def __init__(self):
|
||||
self._rx_callback = None
|
||||
|
||||
def begin(self):
|
||||
return True
|
||||
|
||||
async def send(self, data: bytes):
|
||||
raise RuntimeError("Radio is disabled (radio_type is null/none)")
|
||||
|
||||
async def wait_for_rx(self) -> bytes:
|
||||
import asyncio
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(3600)
|
||||
|
||||
def sleep(self):
|
||||
return None
|
||||
|
||||
def get_last_rssi(self) -> int:
|
||||
return 0
|
||||
|
||||
def get_last_snr(self) -> float:
|
||||
return 0.0
|
||||
|
||||
def set_rx_callback(self, callback):
|
||||
self._rx_callback = callback
|
||||
|
||||
def check_radio_health(self):
|
||||
return True
|
||||
|
||||
|
||||
def resolve_storage_dir(
|
||||
config: Dict[str, Any],
|
||||
*,
|
||||
@@ -299,7 +333,13 @@ def _load_or_create_identity_key(path: Optional[str] = None) -> bytes:
|
||||
|
||||
def get_radio_for_board(board_config: dict):
|
||||
|
||||
def _parse_int(value, *, default=None) -> int:
|
||||
@overload
|
||||
def _parse_int(value, *, default: None = None) -> Optional[int]: ...
|
||||
|
||||
@overload
|
||||
def _parse_int(value, *, default: int) -> int: ...
|
||||
|
||||
def _parse_int(value, *, default=None):
|
||||
if value is None:
|
||||
return default
|
||||
if isinstance(value, int):
|
||||
@@ -322,7 +362,16 @@ def get_radio_for_board(board_config: dict):
|
||||
return [_parse_int(item) for item in stripped.split(",") if item.strip()]
|
||||
raise ValueError(f"Invalid int list value type: {type(value)}")
|
||||
|
||||
radio_type = board_config.get("radio_type", "sx1262").lower().strip()
|
||||
radio_type_raw = board_config.get("radio_type")
|
||||
if radio_type_raw is None:
|
||||
radio_type = "none"
|
||||
else:
|
||||
radio_type = str(radio_type_raw).lower().strip()
|
||||
|
||||
if radio_type in ("", "none", "null", "disabled", "off", "no_radio"):
|
||||
logger.warning("Radio disabled by configuration (radio_type=%r)", radio_type_raw)
|
||||
return NullRadio()
|
||||
|
||||
if radio_type == "kiss-modem":
|
||||
radio_type = "kiss"
|
||||
|
||||
@@ -483,7 +532,7 @@ def get_radio_for_board(board_config: dict):
|
||||
spreading_factor=int(radio_cfg.get("spreading_factor", 8)),
|
||||
coding_rate=int(radio_cfg.get("coding_rate", 8)),
|
||||
tx_power=int(radio_cfg.get("tx_power", 22)),
|
||||
sync_word=_parse_int(radio_cfg.get("sync_word", 0x12)),
|
||||
sync_word=_parse_int(radio_cfg.get("sync_word", 0x12), default=0x12),
|
||||
preamble_length=int(radio_cfg.get("preamble_length", 16)),
|
||||
lbt_enabled=bool(tcp_cfg.get("lbt_enabled", True)),
|
||||
lbt_max_attempts=int(tcp_cfg.get("lbt_max_attempts", 5)),
|
||||
@@ -527,7 +576,7 @@ def get_radio_for_board(board_config: dict):
|
||||
spreading_factor=int(radio_cfg.get("spreading_factor", 8)),
|
||||
coding_rate=int(radio_cfg.get("coding_rate", 8)),
|
||||
tx_power=int(radio_cfg.get("tx_power", 22)),
|
||||
sync_word=_parse_int(radio_cfg.get("sync_word", 0x12)),
|
||||
sync_word=_parse_int(radio_cfg.get("sync_word", 0x12), default=0x12),
|
||||
preamble_length=int(radio_cfg.get("preamble_length", 16)),
|
||||
lbt_enabled=bool(usb_cfg.get("lbt_enabled", True)),
|
||||
lbt_max_attempts=int(usb_cfg.get("lbt_max_attempts", 5)),
|
||||
|
||||
+42
-4
@@ -8,7 +8,7 @@ import socket
|
||||
import time
|
||||
|
||||
from repeater.companion.utils import validate_companion_node_name, normalize_companion_identity_key
|
||||
from repeater.config import get_radio_for_board, load_config, save_config
|
||||
from repeater.config import NullRadio, get_radio_for_board, load_config, save_config
|
||||
from repeater.config_manager import ConfigManager
|
||||
from repeater.data_acquisition.glass_handler import GlassHandler
|
||||
from repeater.data_acquisition.gps_service import GPSService
|
||||
@@ -59,6 +59,8 @@ class RepeaterDaemon:
|
||||
self.companion_frame_servers: list = []
|
||||
self._shutdown_started = False
|
||||
self._main_task = None
|
||||
self.radio_status = "unknown"
|
||||
self.radio_error = None
|
||||
|
||||
log_level = config.get("logging", {}).get("level", "INFO")
|
||||
logging.basicConfig(
|
||||
@@ -97,11 +99,34 @@ class RepeaterDaemon:
|
||||
#-----------------------------------------------
|
||||
|
||||
if self.radio is None:
|
||||
radio_type = self.config.get("radio_type", "sx1262")
|
||||
radio_type_raw = self.config.get("radio_type")
|
||||
radio_type = "none" if radio_type_raw is None else str(radio_type_raw)
|
||||
radio_type_lower = radio_type.lower().strip()
|
||||
radio_explicitly_disabled = radio_type_lower in (
|
||||
"",
|
||||
"none",
|
||||
"null",
|
||||
"disabled",
|
||||
"off",
|
||||
"no_radio",
|
||||
)
|
||||
logger.info(f"Initializing radio hardware... (radio_type={radio_type})")
|
||||
try:
|
||||
self.radio = get_radio_for_board(self.config)
|
||||
|
||||
if isinstance(self.radio, NullRadio):
|
||||
self.radio_status = "disabled" if radio_explicitly_disabled else "degraded"
|
||||
if self.radio_status == "disabled":
|
||||
self.radio_error = None
|
||||
else:
|
||||
self.radio_error = (
|
||||
self.radio_error
|
||||
or f"Radio type '{radio_type}' unavailable; running in no-radio mode"
|
||||
)
|
||||
else:
|
||||
self.radio_status = "ok"
|
||||
self.radio_error = None
|
||||
|
||||
# KISS modem: schedule RX callbacks on the event loop for thread safety
|
||||
if hasattr(self.radio, "set_event_loop"):
|
||||
self.radio.set_event_loop(asyncio.get_running_loop())
|
||||
@@ -133,7 +158,14 @@ class RepeaterDaemon:
|
||||
logger.info("Radio hardware initialized")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize radio hardware: {e}")
|
||||
raise RuntimeError("Repeater requires real LoRa hardware") from e
|
||||
self.radio_status = "degraded"
|
||||
self.radio_error = str(e)
|
||||
logger.warning(
|
||||
"Radio type '%s' unavailable; starting in no-radio mode to keep service alive. "
|
||||
"Check radio configuration and hardware mapping.",
|
||||
radio_type,
|
||||
)
|
||||
self.radio = NullRadio()
|
||||
|
||||
try:
|
||||
from pymc_core import LocalIdentity
|
||||
@@ -944,6 +976,10 @@ class RepeaterDaemon:
|
||||
if self.sensor_manager:
|
||||
stats["sensors"] = self.sensor_manager.get_summary()
|
||||
|
||||
stats["radio_status"] = self.radio_status
|
||||
if self.radio_error:
|
||||
stats["radio_error"] = self.radio_error
|
||||
|
||||
return stats
|
||||
|
||||
async def _get_companion_stats(self, stats_type: int) -> dict:
|
||||
@@ -1200,7 +1236,9 @@ class RepeaterDaemon:
|
||||
|
||||
# Release CH341 USB device if in use
|
||||
try:
|
||||
if self.config.get("radio_type", "sx1262").lower() == "sx1262_ch341":
|
||||
radio_type_raw = self.config.get("radio_type")
|
||||
radio_type = "" if radio_type_raw is None else str(radio_type_raw).lower()
|
||||
if radio_type == "sx1262_ch341":
|
||||
from pymc_core.hardware.ch341.ch341_async import CH341Async
|
||||
|
||||
CH341Async.reset_instance()
|
||||
|
||||
@@ -317,13 +317,18 @@ class APIEndpoints:
|
||||
)
|
||||
has_default_password = admin_password in ["admin123", ""]
|
||||
|
||||
needs_setup = has_default_name or has_default_password
|
||||
radio_type_raw = config.get("radio_type")
|
||||
radio_type = "" if radio_type_raw is None else str(radio_type_raw).lower().strip()
|
||||
radio_not_configured = radio_type in ("", "none", "null", "disabled", "off", "no_radio")
|
||||
|
||||
needs_setup = has_default_name or has_default_password or radio_not_configured
|
||||
|
||||
return {
|
||||
"needs_setup": needs_setup,
|
||||
"reasons": {
|
||||
"default_name": has_default_name,
|
||||
"default_password": has_default_password,
|
||||
"radio_not_configured": radio_not_configured,
|
||||
},
|
||||
}
|
||||
except Exception as e:
|
||||
@@ -360,16 +365,6 @@ class APIEndpoints:
|
||||
}
|
||||
)
|
||||
|
||||
# Add MeshCore KISS modem option (serial TNC)
|
||||
hardware_list.append(
|
||||
{
|
||||
"key": "kiss",
|
||||
"name": "KISS modem (serial)",
|
||||
"description": "MeshCore KISS modem over serial – requires pyMC_core with KISS support",
|
||||
"config": {},
|
||||
}
|
||||
)
|
||||
|
||||
return {"hardware": hardware_list}
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading hardware options: {e}")
|
||||
@@ -491,6 +486,49 @@ class APIEndpoints:
|
||||
config_yaml["radio"]["tx_power"] = int(radio_preset.get("tx_power", 14))
|
||||
if "preamble_length" not in config_yaml["radio"]:
|
||||
config_yaml["radio"]["preamble_length"] = 17
|
||||
elif hardware_key == "pymc_usb":
|
||||
# pymc_usb modem: external SX1262 board over USB-CDC.
|
||||
# Accept pymc_usb_port / pymc_usb_baudrate from the request body
|
||||
# (mirrors the KISS pattern) so a future SPA can expose inputs;
|
||||
# fall back to /dev/ttyACM0 at 921600 baud, which matches the
|
||||
# firmware default and the typical USB-CDC modem device on Linux.
|
||||
config_yaml["radio_type"] = "pymc_usb"
|
||||
usb_port = (data.get("pymc_usb_port") or "").strip() or "/dev/ttyACM0"
|
||||
usb_baud = int(data.get("pymc_usb_baudrate", data.get("pymc_usb_baud", 921600)))
|
||||
pymc_usb_section = config_yaml.setdefault("pymc_usb", {})
|
||||
pymc_usb_section["port"] = usb_port
|
||||
pymc_usb_section["baudrate"] = usb_baud
|
||||
pymc_usb_section.setdefault("lbt_enabled", True)
|
||||
pymc_usb_section.setdefault("lbt_max_attempts", 5)
|
||||
if "tx_power" in hw_config:
|
||||
config_yaml["radio"]["tx_power"] = hw_config.get("tx_power", 22)
|
||||
if "preamble_length" in hw_config:
|
||||
config_yaml["radio"]["preamble_length"] = hw_config.get("preamble_length", 16)
|
||||
elif hardware_key == "pymc_tcp":
|
||||
# pymc_tcp modem: external SX1262 board exposed as TCP over Wi-Fi/Ethernet.
|
||||
# 'host' has no sensible default — must be the modem's LAN address or
|
||||
# mDNS name. Accept it from the request body if the SPA provides it,
|
||||
# otherwise write a clearly-placeholder hostname so the file is valid
|
||||
# YAML and the user gets a startup error pointing them at the right
|
||||
# section to edit (see config.py: ValueError 'Missing host …').
|
||||
config_yaml["radio_type"] = "pymc_tcp"
|
||||
tcp_host = (data.get("pymc_tcp_host") or "").strip() or "REPLACE_WITH_MODEM_HOST"
|
||||
tcp_port = int(data.get("pymc_tcp_port", 5055))
|
||||
pymc_tcp_section = config_yaml.setdefault("pymc_tcp", {})
|
||||
pymc_tcp_section["host"] = tcp_host
|
||||
pymc_tcp_section["port"] = tcp_port
|
||||
tcp_token = data.get("pymc_tcp_token")
|
||||
if tcp_token is not None:
|
||||
pymc_tcp_section["token"] = str(tcp_token)
|
||||
else:
|
||||
pymc_tcp_section.setdefault("token", "")
|
||||
pymc_tcp_section.setdefault("connect_timeout", 5.0)
|
||||
pymc_tcp_section.setdefault("lbt_enabled", True)
|
||||
pymc_tcp_section.setdefault("lbt_max_attempts", 5)
|
||||
if "tx_power" in hw_config:
|
||||
config_yaml["radio"]["tx_power"] = hw_config.get("tx_power", 22)
|
||||
if "preamble_length" in hw_config:
|
||||
config_yaml["radio"]["preamble_length"] = hw_config.get("preamble_length", 16)
|
||||
else:
|
||||
# SX1262 / sx1262_ch341: radio_type and optional CH341 from hw_config
|
||||
if "radio_type" in hw_config:
|
||||
@@ -577,7 +615,7 @@ class APIEndpoints:
|
||||
result_config = {
|
||||
"node_name": node_name,
|
||||
"hardware": hardware_key,
|
||||
"radio_type": config_yaml.get("radio_type", "sx1262"),
|
||||
"radio_type": config_yaml.get("radio_type"),
|
||||
"frequency": freq_mhz,
|
||||
"spreading_factor": radio_preset.get("spreading_factor"),
|
||||
"bandwidth": radio_preset.get("bandwidth"),
|
||||
@@ -586,6 +624,15 @@ class APIEndpoints:
|
||||
if hardware_key == "kiss":
|
||||
result_config["kiss_port"] = config_yaml.get("kiss", {}).get("port")
|
||||
result_config["kiss_baud_rate"] = config_yaml.get("kiss", {}).get("baud_rate")
|
||||
elif hardware_key == "pymc_usb":
|
||||
pymc_usb_cfg = config_yaml.get("pymc_usb", {})
|
||||
result_config["pymc_usb_port"] = pymc_usb_cfg.get("port")
|
||||
result_config["pymc_usb_baudrate"] = pymc_usb_cfg.get("baudrate")
|
||||
elif hardware_key == "pymc_tcp":
|
||||
pymc_tcp_cfg = config_yaml.get("pymc_tcp", {})
|
||||
result_config["pymc_tcp_host"] = pymc_tcp_cfg.get("host")
|
||||
result_config["pymc_tcp_port"] = pymc_tcp_cfg.get("port")
|
||||
# token deliberately omitted from response (sensitive)
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Setup completed successfully. Service is restarting...",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
.ml-0[data-v-dad29312]{margin-left:0}.ml-4[data-v-dad29312]{margin-left:1rem}.ml-8[data-v-dad29312]{margin-left:2rem}.ml-12[data-v-dad29312]{margin-left:3rem}.ml-16[data-v-dad29312]{margin-left:4rem}.ml-20[data-v-dad29312]{margin-left:5rem}.ml-24[data-v-dad29312]{margin-left:6rem}.ml-28[data-v-dad29312]{margin-left:7rem}.ml-32[data-v-dad29312]{margin-left:8rem}.dropdown-enter-active[data-v-a86a677e],.dropdown-leave-active[data-v-a86a677e]{transition:opacity .12s,transform .12s}.dropdown-enter-from[data-v-a86a677e],.dropdown-leave-to[data-v-a86a677e]{opacity:0;transform:translateY(-4px)}.expand-enter-active[data-v-00e540ed],.expand-leave-active[data-v-00e540ed]{transition:all .2s;overflow:hidden}.expand-enter-from[data-v-00e540ed],.expand-leave-to[data-v-00e540ed]{opacity:0;max-height:0}.expand-enter-to[data-v-00e540ed],.expand-leave-from[data-v-00e540ed]{opacity:1;max-height:2000px}.tab-fade-left[data-v-d04d8825]{background:linear-gradient(to right, var(--color-surface) 30%, transparent)}.tab-fade-right[data-v-d04d8825]{background:linear-gradient(to left, var(--color-surface) 30%, transparent)}.tab-fade-enter-active[data-v-d04d8825],.tab-fade-leave-active[data-v-d04d8825]{transition:opacity .2s}.tab-fade-enter-from[data-v-d04d8825],.tab-fade-leave-to[data-v-d04d8825]{opacity:0}
|
||||
.ml-0[data-v-dad29312]{margin-left:0}.ml-4[data-v-dad29312]{margin-left:1rem}.ml-8[data-v-dad29312]{margin-left:2rem}.ml-12[data-v-dad29312]{margin-left:3rem}.ml-16[data-v-dad29312]{margin-left:4rem}.ml-20[data-v-dad29312]{margin-left:5rem}.ml-24[data-v-dad29312]{margin-left:6rem}.ml-28[data-v-dad29312]{margin-left:7rem}.ml-32[data-v-dad29312]{margin-left:8rem}.dropdown-enter-active[data-v-de709eb9],.dropdown-leave-active[data-v-de709eb9]{transition:opacity .12s,transform .12s}.dropdown-enter-from[data-v-de709eb9],.dropdown-leave-to[data-v-de709eb9]{opacity:0;transform:translateY(-4px)}.expand-enter-active[data-v-00e540ed],.expand-leave-active[data-v-00e540ed]{transition:all .2s;overflow:hidden}.expand-enter-from[data-v-00e540ed],.expand-leave-to[data-v-00e540ed]{opacity:0;max-height:0}.expand-enter-to[data-v-00e540ed],.expand-leave-from[data-v-00e540ed]{opacity:1;max-height:2000px}.tab-fade-left[data-v-aef8d875]{background:linear-gradient(to right, var(--color-surface) 30%, transparent)}.tab-fade-right[data-v-aef8d875]{background:linear-gradient(to left, var(--color-surface) 30%, transparent)}.tab-fade-enter-active[data-v-aef8d875],.tab-fade-leave-active[data-v-aef8d875]{transition:opacity .2s}.tab-fade-enter-from[data-v-aef8d875],.tab-fade-leave-to[data-v-aef8d875]{opacity:0}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{Ct as e,c as t,g as n,i as r,k as i,l as a,s as o,u as s,xt as c}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{g as l}from"./index-1AODY5To.js";var u={class:`modal-card max-w-md`},d={class:`flex items-center justify-between mb-4`},f={class:`text-xl font-semibold text-content-primary dark:text-content-primary`},p={class:`mb-6`},m={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},h={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},g={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},_={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},v={class:`flex gap-3`},y=n({__name:`ConfirmDialog`,props:{show:{type:Boolean},title:{default:`Confirm Action`},message:{},confirmText:{default:`Confirm`},cancelText:{default:`Cancel`},variant:{default:`warning`}},emits:[`close`,`confirm`],setup(n,{emit:y}){let b=n,x=y,S={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`},C={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(n,y)=>(i(),t(r,{to:`body`},[b.show?(i(),s(`div`,{key:0,onClick:y[3]||=l(e=>x(`close`),[`self`]),class:`modal-backdrop`},[o(`div`,u,[o(`div`,d,[o(`h3`,f,e(b.title),1),o(`button`,{onClick:y[0]||=e=>x(`close`),class:`text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors`},[...y[4]||=[o(`svg`,{class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[o(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`})],-1)]])]),o(`div`,p,[o(`div`,{class:c([`inline-flex p-3 rounded-xl mb-4`,S[b.variant]])},[b.variant===`danger`?(i(),s(`svg`,m,[...y[5]||=[o(`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)]])):b.variant===`warning`?(i(),s(`svg`,h,[...y[6]||=[o(`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)]])):(i(),s(`svg`,g,[...y[7]||=[o(`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),o(`p`,_,e(b.message),1)]),o(`div`,v,[o(`button`,{onClick:y[1]||=e=>x(`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`},e(b.cancelText),1),o(`button`,{onClick:y[2]||=e=>x(`confirm`),class:c([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,C[b.variant]])},e(b.confirmText),3)])])])):a(``,!0)]))}});export{y as t};
|
||||
import{Ct as e,c as t,g as n,i as r,k as i,l as a,s as o,u as s,xt as c}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{g as l}from"./index-CG181k2K.js";var u={class:`modal-card max-w-md`},d={class:`flex items-center justify-between mb-4`},f={class:`text-xl font-semibold text-content-primary dark:text-content-primary`},p={class:`mb-6`},m={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},h={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},g={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},_={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},v={class:`flex gap-3`},y=n({__name:`ConfirmDialog`,props:{show:{type:Boolean},title:{default:`Confirm Action`},message:{},confirmText:{default:`Confirm`},cancelText:{default:`Cancel`},variant:{default:`warning`}},emits:[`close`,`confirm`],setup(n,{emit:y}){let b=n,x=y,S={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`},C={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(n,y)=>(i(),t(r,{to:`body`},[b.show?(i(),s(`div`,{key:0,onClick:y[3]||=l(e=>x(`close`),[`self`]),class:`modal-backdrop`},[o(`div`,u,[o(`div`,d,[o(`h3`,f,e(b.title),1),o(`button`,{onClick:y[0]||=e=>x(`close`),class:`text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors`},[...y[4]||=[o(`svg`,{class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[o(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`})],-1)]])]),o(`div`,p,[o(`div`,{class:c([`inline-flex p-3 rounded-xl mb-4`,S[b.variant]])},[b.variant===`danger`?(i(),s(`svg`,m,[...y[5]||=[o(`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)]])):b.variant===`warning`?(i(),s(`svg`,h,[...y[6]||=[o(`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)]])):(i(),s(`svg`,g,[...y[7]||=[o(`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),o(`p`,_,e(b.message),1)]),o(`div`,v,[o(`button`,{onClick:y[1]||=e=>x(`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`},e(b.cancelText),1),o(`button`,{onClick:y[2]||=e=>x(`confirm`),class:c([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,C[b.variant]])},e(b.confirmText),3)])])])):a(``,!0)]))}});export{y as t};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.bg-gradient-light[data-v-9842a645]{background:linear-gradient(#0d73774d,#aae8e833)}.bg-gradient-dark[data-v-9842a645]{background:linear-gradient(#aae8e82e,#0d73771a)}.login-card[data-v-9842a645]{-webkit-backdrop-filter:blur(40px)saturate(180%);background:#ffffffd9}.dark .login-card[data-v-9842a645]{background:#1a1e1fcc}.input-glass[data-v-9842a645]{-webkit-backdrop-filter:blur(20px);background:#ffffffe6;border:1px solid #d1d5db}.dark .input-glass[data-v-9842a645]{background:#ffffff0d;border-color:#ffffff1a}.input-glass[data-v-9842a645]:focus{background:#fff}.dark .input-glass[data-v-9842a645]:focus{background:#ffffff1a}.input-glass[data-v-9842a645]:focus{box-shadow:0 0 0 1px #aae8e833,0 0 20px #aae8e826,inset 0 1px #ffffff1a}.input-glow[data-v-9842a645]{opacity:0;transition:opacity .3s;box-shadow:inset 0 1px #ffffff0d}.input-glass:focus+.input-glow[data-v-9842a645]{opacity:1;box-shadow:0 0 20px #aae8e833,inset 0 1px #ffffff1a}.button-glass[data-v-9842a645]{-webkit-backdrop-filter:blur(20px);position:relative}.button-glass[data-v-9842a645]:before{content:"";-webkit-mask-composite:xor;background:linear-gradient(90deg,#0000 0%,#aae8e84d 50%,#0000 100%);border-radius:12px;padding:1px;transition:transform 1s;position:absolute;inset:0;transform:translate(-100%);-webkit-mask-image:linear-gradient(#fff 0 0),linear-gradient(#fff 0 0);-webkit-mask-position:0 0,0 0;-webkit-mask-size:auto,auto;-webkit-mask-repeat:repeat,repeat;-webkit-mask-clip:content-box,border-box;-webkit-mask-origin:content-box,border-box;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-source-type:auto,auto;mask-mode:match-source,match-source}.button-glass[data-v-9842a645]:hover:not(:disabled):before{transform:translate(100%)}.button-glass[data-v-9842a645]{box-shadow:0 0 0 1px #aae8e833,0 4px 16px #0003,inset 0 1px #ffffff1a}.button-glass[data-v-9842a645]:hover:not(:disabled){box-shadow:0 0 0 1px #aae8e866,0 0 30px #aae8e84d,0 4px 20px #0000004d,inset 0 1px #ffffff26}@keyframes float-9842a645{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes pulse-slow-9842a645{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.05)}}@keyframes pulse-slower-9842a645{0%,to{opacity:.75;transform:scale(1)}50%{opacity:.5;transform:scale(1.08)}}@keyframes pulse-slowest-9842a645{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.06)}}.animate-pulse-slow[data-v-9842a645]{animation:8s ease-in-out infinite pulse-slow-9842a645}.animate-pulse-slower[data-v-9842a645]{animation:10s ease-in-out infinite pulse-slower-9842a645}.animate-pulse-slowest[data-v-9842a645]{animation:12s ease-in-out infinite pulse-slowest-9842a645}@keyframes shake-9842a645{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-5px)}20%,40%,60%,80%{transform:translate(5px)}}.animate-shake[data-v-9842a645]{animation:.5s ease-in-out shake-9842a645}.form-group[data-v-9842a645]{position:relative}.form-group:hover label[data-v-9842a645]{color:#aae8e8e6;transition:color .3s}
|
||||
@@ -0,0 +1 @@
|
||||
.bg-gradient-light[data-v-2c828a8f]{background:linear-gradient(#0d73774d,#aae8e833)}.bg-gradient-dark[data-v-2c828a8f]{background:linear-gradient(#aae8e82e,#0d73771a)}.login-card[data-v-2c828a8f]{-webkit-backdrop-filter:blur(40px)saturate(180%);background:#ffffffd9}.dark .login-card[data-v-2c828a8f]{background:#1a1e1fcc}.input-glass[data-v-2c828a8f]{-webkit-backdrop-filter:blur(20px);background:#ffffffe6;border:1px solid #d1d5db}.dark .input-glass[data-v-2c828a8f]{background:#ffffff0d;border-color:#ffffff1a}.input-glass[data-v-2c828a8f]:focus{background:#fff}.dark .input-glass[data-v-2c828a8f]:focus{background:#ffffff1a}.input-glass[data-v-2c828a8f]:focus{box-shadow:0 0 0 1px #aae8e833,0 0 20px #aae8e826,inset 0 1px #ffffff1a}.input-glow[data-v-2c828a8f]{opacity:0;transition:opacity .3s;box-shadow:inset 0 1px #ffffff0d}.input-glass:focus+.input-glow[data-v-2c828a8f]{opacity:1;box-shadow:0 0 20px #aae8e833,inset 0 1px #ffffff1a}.button-glass[data-v-2c828a8f]{-webkit-backdrop-filter:blur(20px);position:relative}.button-glass[data-v-2c828a8f]:before{content:"";-webkit-mask-composite:xor;background:linear-gradient(90deg,#0000 0%,#aae8e84d 50%,#0000 100%);border-radius:12px;padding:1px;transition:transform 1s;position:absolute;inset:0;transform:translate(-100%);-webkit-mask-image:linear-gradient(#fff 0 0),linear-gradient(#fff 0 0);-webkit-mask-position:0 0,0 0;-webkit-mask-size:auto,auto;-webkit-mask-repeat:repeat,repeat;-webkit-mask-clip:content-box,border-box;-webkit-mask-origin:content-box,border-box;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-source-type:auto,auto;mask-mode:match-source,match-source}.button-glass[data-v-2c828a8f]:hover:not(:disabled):before{transform:translate(100%)}.button-glass[data-v-2c828a8f]{box-shadow:0 0 0 1px #aae8e833,0 4px 16px #0003,inset 0 1px #ffffff1a}.button-glass[data-v-2c828a8f]:hover:not(:disabled){box-shadow:0 0 0 1px #aae8e866,0 0 30px #aae8e84d,0 4px 20px #0000004d,inset 0 1px #ffffff26}@keyframes float-2c828a8f{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes pulse-slow-2c828a8f{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.05)}}@keyframes pulse-slower-2c828a8f{0%,to{opacity:.75;transform:scale(1)}50%{opacity:.5;transform:scale(1.08)}}@keyframes pulse-slowest-2c828a8f{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.06)}}.animate-pulse-slow[data-v-2c828a8f]{animation:8s ease-in-out infinite pulse-slow-2c828a8f}.animate-pulse-slower[data-v-2c828a8f]{animation:10s ease-in-out infinite pulse-slower-2c828a8f}.animate-pulse-slowest[data-v-2c828a8f]{animation:12s ease-in-out infinite pulse-slowest-2c828a8f}@keyframes shake-2c828a8f{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-5px)}20%,40%,60%,80%{transform:translate(5px)}}.animate-shake[data-v-2c828a8f]{animation:.5s ease-in-out shake-2c828a8f}.form-group[data-v-2c828a8f]{position:relative}.form-group:hover label[data-v-2c828a8f]{color:#aae8e8e6;transition:color .3s}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{Ct as e,c as t,g as n,i as r,k as i,l as a,s as o,u as s,xt as c}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{g as l}from"./index-1AODY5To.js";var u={class:`modal-card max-w-md`},d={class:`mb-6`},f={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},p={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},m={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},h={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},g={class:`flex`},_=n({__name:`MessageDialog`,props:{show:{type:Boolean},message:{},variant:{default:`success`}},emits:[`close`],setup(n,{emit:_}){let v=n,y=_,b={success:`bg-green-100 dark:bg-green-500/20 border-green-600/40 dark:border-green-500/30 text-green-600 dark:text-green-400`,error:`bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400`,info:`bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400`},x={success:`bg-green-500 hover:bg-green-600`,error:`bg-red-500 hover:bg-red-600`,info:`bg-blue-500 hover:bg-blue-600`};return(n,_)=>(i(),t(r,{to:`body`},[v.show?(i(),s(`div`,{key:0,onClick:_[1]||=l(e=>y(`close`),[`self`]),class:`modal-backdrop`},[o(`div`,u,[o(`div`,d,[o(`div`,{class:c([`inline-flex p-3 rounded-xl mb-4`,b[v.variant]])},[v.variant===`success`?(i(),s(`svg`,f,[..._[2]||=[o(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M5 13l4 4L19 7`},null,-1)]])):v.variant===`error`?(i(),s(`svg`,p,[..._[3]||=[o(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`},null,-1)]])):(i(),s(`svg`,m,[..._[4]||=[o(`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),o(`p`,h,e(v.message),1)]),o(`div`,g,[o(`button`,{onClick:_[0]||=e=>y(`close`),class:c([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,x[v.variant]])},` OK `,2)])])])):a(``,!0)]))}});export{_ as t};
|
||||
import{Ct as e,c as t,g as n,i as r,k as i,l as a,s as o,u as s,xt as c}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{g as l}from"./index-CG181k2K.js";var u={class:`modal-card max-w-md`},d={class:`mb-6`},f={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},p={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},m={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},h={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},g={class:`flex`},_=n({__name:`MessageDialog`,props:{show:{type:Boolean},message:{},variant:{default:`success`}},emits:[`close`],setup(n,{emit:_}){let v=n,y=_,b={success:`bg-green-100 dark:bg-green-500/20 border-green-600/40 dark:border-green-500/30 text-green-600 dark:text-green-400`,error:`bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400`,info:`bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400`},x={success:`bg-green-500 hover:bg-green-600`,error:`bg-red-500 hover:bg-red-600`,info:`bg-blue-500 hover:bg-blue-600`};return(n,_)=>(i(),t(r,{to:`body`},[v.show?(i(),s(`div`,{key:0,onClick:_[1]||=l(e=>y(`close`),[`self`]),class:`modal-backdrop`},[o(`div`,u,[o(`div`,d,[o(`div`,{class:c([`inline-flex p-3 rounded-xl mb-4`,b[v.variant]])},[v.variant===`success`?(i(),s(`svg`,f,[..._[2]||=[o(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M5 13l4 4L19 7`},null,-1)]])):v.variant===`error`?(i(),s(`svg`,p,[..._[3]||=[o(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`},null,-1)]])):(i(),s(`svg`,m,[..._[4]||=[o(`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),o(`p`,h,e(v.message),1)]),o(`div`,g,[o(`button`,{onClick:_[0]||=e=>y(`close`),class:c([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,x[v.variant]])},` OK `,2)])])])):a(``,!0)]))}});export{_ as t};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{a as e}from"./index-1AODY5To.js";export{e as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{a as e}from"./index-CG181k2K.js";export{e as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{B as e,Ct as t,R as n,Y as r,c as i,g as a,i as o,k as s,l as c,m as l,r as u,s as d,u as f,w as p}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{t as m}from"./api-Djyhg6JK.js";import{t as h}from"./Spinner-CYvUNW0P.js";import{g,l as _}from"./index-1AODY5To.js";var v={class:`modal-card max-w-md shadow-xl`},y={key:0,class:`flex flex-col items-center gap-5 py-2`},b={class:`flex items-start gap-3 mb-4`},x={class:`text-base font-semibold text-content-primary dark:text-content-primary`},S={class:`mt-1 text-sm text-content-secondary dark:text-content-muted`},C=50,w=5,T=a({__name:`RestartModal`,props:{modelValue:{type:Boolean},message:{},title:{default:`Service Restart Required`}},emits:[`update:modelValue`],setup(a,{emit:T}){let E=a,D=T,O=r(!1),k=r(!1),A=null,j=0,M=0;function N(){O.value&&!k.value||(O.value=!1,k.value=!1,A&&=(clearTimeout(A),null),j=0,M=0,D(`update:modelValue`,!1))}async function P(){O.value=!0,k.value=!1;try{await m.post(`/restart_service`,{})}catch{}j=0,M=0,A=setTimeout(F,1e4)}function F(){j++,fetch(`/api/needs_setup`,{method:`GET`}).then(e=>{e.ok?(M++,M>=w?window.location.reload():A=setTimeout(F,1e3)):(M=0,I())}).catch(()=>{M=0,I()})}function I(){j<C?A=setTimeout(F,1e3):(O.value=!1,k.value=!0)}return n(()=>E.modelValue,e=>{e||(O.value=!1,k.value=!1,A&&=(clearTimeout(A),null),j=0,M=0)}),p(()=>{A&&clearTimeout(A)}),(n,r)=>(s(),i(o,{to:`body`},[l(_,{"enter-active-class":`transition-opacity duration-200`,"enter-from-class":`opacity-0`,"leave-active-class":`transition-opacity duration-200`,"leave-to-class":`opacity-0`},{default:e(()=>[a.modelValue?(s(),f(`div`,{key:0,class:`fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm`,onClick:g(N,[`self`])},[d(`div`,v,[O.value?(s(),f(`div`,y,[l(h,{size:`lg`}),r[0]||=d(`div`,{class:`text-center`},[d(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Restarting… `),d(`p`,{class:`mt-1 text-sm text-content-secondary dark:text-content-muted`},` Please wait while the service restarts. This may take up to a minute. `)],-1)])):k.value?(s(),f(u,{key:1},[r[1]||=d(`div`,{class:`flex items-start gap-3 mb-4`},[d(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center`},[d(`svg`,{class:`w-5 h-5 text-red-600 dark:text-red-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[d(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z`})])]),d(`div`,null,[d(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Service Did Not Restart `),d(`p`,{class:`mt-1 text-sm text-content-secondary dark:text-content-muted`},` The service did not respond after 60 seconds. Please log into the device and check the system logs. `)])],-1),d(`div`,{class:`modal-actions`},[d(`button`,{onClick:N,class:`modal-btn-cancel`},`Dismiss`)])],64)):(s(),f(u,{key:2},[d(`div`,b,[r[2]||=d(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center`},[d(`svg`,{class:`w-5 h-5 text-amber-600 dark:text-amber-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[d(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z`})])],-1),d(`div`,null,[d(`h3`,x,t(a.title),1),d(`p`,S,t(a.message),1)])]),d(`div`,{class:`modal-actions`},[d(`button`,{onClick:N,class:`modal-btn-cancel`},`Cancel`),d(`button`,{onClick:P,class:`modal-btn-primary`},`Restart`)])],64))])])):c(``,!0)]),_:1})]))}});export{T as t};
|
||||
import{B as e,Ct as t,R as n,Y as r,c as i,g as a,i as o,k as s,l as c,m as l,r as u,s as d,u as f,w as p}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{t as m}from"./api-DBiYn0RS.js";import{t as h}from"./Spinner-CYvUNW0P.js";import{g,l as _}from"./index-CG181k2K.js";var v={class:`modal-card max-w-md shadow-xl`},y={key:0,class:`flex flex-col items-center gap-5 py-2`},b={class:`flex items-start gap-3 mb-4`},x={class:`text-base font-semibold text-content-primary dark:text-content-primary`},S={class:`mt-1 text-sm text-content-secondary dark:text-content-muted`},C=50,w=5,T=a({__name:`RestartModal`,props:{modelValue:{type:Boolean},message:{},title:{default:`Service Restart Required`}},emits:[`update:modelValue`],setup(a,{emit:T}){let E=a,D=T,O=r(!1),k=r(!1),A=null,j=0,M=0;function N(){O.value&&!k.value||(O.value=!1,k.value=!1,A&&=(clearTimeout(A),null),j=0,M=0,D(`update:modelValue`,!1))}async function P(){O.value=!0,k.value=!1;try{await m.post(`/restart_service`,{})}catch{}j=0,M=0,A=setTimeout(F,1e4)}function F(){j++,fetch(`/api/needs_setup`,{method:`GET`}).then(e=>{e.ok?(M++,M>=w?window.location.reload():A=setTimeout(F,1e3)):(M=0,I())}).catch(()=>{M=0,I()})}function I(){j<C?A=setTimeout(F,1e3):(O.value=!1,k.value=!0)}return n(()=>E.modelValue,e=>{e||(O.value=!1,k.value=!1,A&&=(clearTimeout(A),null),j=0,M=0)}),p(()=>{A&&clearTimeout(A)}),(n,r)=>(s(),i(o,{to:`body`},[l(_,{"enter-active-class":`transition-opacity duration-200`,"enter-from-class":`opacity-0`,"leave-active-class":`transition-opacity duration-200`,"leave-to-class":`opacity-0`},{default:e(()=>[a.modelValue?(s(),f(`div`,{key:0,class:`fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm`,onClick:g(N,[`self`])},[d(`div`,v,[O.value?(s(),f(`div`,y,[l(h,{size:`lg`}),r[0]||=d(`div`,{class:`text-center`},[d(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Restarting… `),d(`p`,{class:`mt-1 text-sm text-content-secondary dark:text-content-muted`},` Please wait while the service restarts. This may take up to a minute. `)],-1)])):k.value?(s(),f(u,{key:1},[r[1]||=d(`div`,{class:`flex items-start gap-3 mb-4`},[d(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center`},[d(`svg`,{class:`w-5 h-5 text-red-600 dark:text-red-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[d(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z`})])]),d(`div`,null,[d(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Service Did Not Restart `),d(`p`,{class:`mt-1 text-sm text-content-secondary dark:text-content-muted`},` The service did not respond after 60 seconds. Please log into the device and check the system logs. `)])],-1),d(`div`,{class:`modal-actions`},[d(`button`,{onClick:N,class:`modal-btn-cancel`},`Dismiss`)])],64)):(s(),f(u,{key:2},[d(`div`,b,[r[2]||=d(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center`},[d(`svg`,{class:`w-5 h-5 text-amber-600 dark:text-amber-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[d(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z`})])],-1),d(`div`,null,[d(`h3`,x,t(a.title),1),d(`p`,S,t(a.message),1)])]),d(`div`,{class:`modal-actions`},[d(`button`,{onClick:N,class:`modal-btn-cancel`},`Cancel`),d(`button`,{onClick:P,class:`modal-btn-primary`},`Restart`)])],64))])])):c(``,!0)]),_:1})]))}});export{T as t};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{Ct as e,g as t,j as n,k as r,l as i,o as a,r as o,s,u as c,xt as l}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{t as u}from"./system-eurEQN-G.js";import{t as d}from"./index-1AODY5To.js";var f={class:`space-y-4`},p={class:`glass-card rounded-[15px] p-4 sm:p-6`},m={class:`mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4`},h={class:`text-xs uppercase tracking-wide text-content-muted`},g={class:`mt-2 text-lg font-semibold text-content-heading dark:text-white`},_={key:0,class:`glass-card rounded-[15px] p-5 text-content-muted`},v={class:`flex flex-wrap items-center justify-between gap-3`},y={class:`text-lg font-semibold text-content-heading dark:text-white`},b={class:`text-sm text-content-muted`},x={class:`mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2`},S={class:`text-sm`},C={class:`ml-2 text-content-heading dark:text-white`},w={key:0,class:`text-sm`},T={class:`ml-2 text-red-600 dark:text-red-300`},E={class:`mt-4 overflow-x-auto rounded-[12px] border border-stroke-subtle dark:border-white/10`},D={class:`min-w-full text-sm`},O={class:`px-3 py-2 font-medium text-content-heading dark:text-white`},k={class:`px-3 py-2 text-content-muted break-all`},A={key:0},j={key:1,class:`glass-card rounded-[15px] p-5 text-content-muted`},M=t({name:`SensorsView`,__name:`Sensors`,setup(t){let M=u(),N=a(()=>M.stats?.sensors??null),P=a(()=>N.value?.readings??[]),F=a(()=>{let e=N.value;return e?[{label:`Enabled`,value:e.enabled?`Yes`:`No`},{label:`Running`,value:e.running?`Yes`:`No`},{label:`Configured / Loaded`,value:`${e.configured??0} / ${e.loaded??0}`},{label:`Poll Interval`,value:typeof e.poll_interval_seconds==`number`?`${e.poll_interval_seconds.toFixed(1)}s`:`n/a`}]:[{label:`Enabled`,value:`n/a`},{label:`Running`,value:`n/a`},{label:`Configured`,value:`n/a`},{label:`Poll Interval`,value:`n/a`}]}),I=e=>{if(e==null)return`n/a`;if(typeof e==`boolean`)return e?`true`:`false`;if(typeof e==`number`)return Number.isFinite(e)?String(e):`n/a`;if(typeof e==`string`)return e;try{return JSON.stringify(e)}catch{return String(e)}},L=e=>{if(!e)return`n/a`;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()},R=async()=>{await M.fetchStats()};return d(async()=>{await M.fetchStats()},{intervalMs:1e4,immediate:!0}),(t,a)=>(r(),c(`div`,f,[s(`div`,p,[s(`div`,{class:`flex items-start justify-between gap-4`},[a[0]||=s(`div`,null,[s(`h1`,{class:`text-xl sm:text-2xl font-semibold text-content-heading dark:text-white`},`Sensors`),s(`p`,{class:`mt-1 text-sm text-content-muted`},` Live sensor summary from the existing stats API. `)],-1),s(`button`,{class:`rounded-[10px] border border-stroke-subtle dark:border-white/10 px-3 py-2 text-sm hover:bg-black/5 dark:hover:bg-white/5`,onClick:R},` Refresh `)]),s(`div`,m,[(r(!0),c(o,null,n(F.value,t=>(r(),c(`div`,{key:t.label,class:`rounded-[12px] border border-stroke-subtle dark:border-white/10 p-3`},[s(`p`,h,e(t.label),1),s(`p`,g,e(t.value),1)]))),128))])]),N.value?i(``,!0):(r(),c(`div`,_,` Sensor data is not available yet. Ensure the repeater has started and stats are loading. `)),(r(!0),c(o,null,n(P.value,(t,u)=>(r(),c(`div`,{key:`${t.name||`sensor`}-${u}`,class:`glass-card rounded-[15px] p-4 sm:p-5`},[s(`div`,v,[s(`div`,null,[s(`h2`,y,e(t.name||`Sensor ${u+1}`),1),s(`p`,b,`Type: `+e(t.type||`unknown`),1)]),s(`span`,{class:l([`rounded-full px-3 py-1 text-xs font-semibold`,t.ok?`bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300`:`bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-300`])},e(t.ok?`OK`:`Error`),3)]),s(`div`,x,[s(`div`,S,[a[1]||=s(`span`,{class:`text-content-muted`},`Timestamp:`,-1),s(`span`,C,e(L(t.timestamp)),1)]),t.error?(r(),c(`div`,w,[a[2]||=s(`span`,{class:`text-content-muted`},`Error:`,-1),s(`span`,T,e(t.error),1)])):i(``,!0)]),s(`div`,E,[s(`table`,D,[a[4]||=s(`thead`,{class:`bg-black/5 dark:bg-white/5`},[s(`tr`,null,[s(`th`,{class:`px-3 py-2 text-left text-content-muted`},`Field`),s(`th`,{class:`px-3 py-2 text-left text-content-muted`},`Value`)])],-1),s(`tbody`,null,[(r(!0),c(o,null,n(t.data||{},(t,n)=>(r(),c(`tr`,{key:String(n),class:`border-t border-stroke-subtle dark:border-white/10`},[s(`td`,O,e(n),1),s(`td`,k,e(I(t)),1)]))),128)),!t.data||Object.keys(t.data).length===0?(r(),c(`tr`,A,[...a[3]||=[s(`td`,{class:`px-3 py-3 text-content-muted`,colspan:`2`},`No fields in payload`,-1)]])):i(``,!0)])])])]))),128)),N.value&&P.value.length===0?(r(),c(`div`,j,` Sensors are configured but no readings are available yet. `)):i(``,!0)]))}});export{M as default};
|
||||
import{Ct as e,g as t,j as n,k as r,l as i,o as a,r as o,s,u as c,xt as l}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{t as u}from"./system-BfTzQTOF.js";import{t as d}from"./index-CG181k2K.js";var f={class:`space-y-4`},p={class:`glass-card rounded-[15px] p-4 sm:p-6`},m={class:`mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4`},h={class:`text-xs uppercase tracking-wide text-content-muted`},g={class:`mt-2 text-lg font-semibold text-content-heading dark:text-white`},_={key:0,class:`glass-card rounded-[15px] p-5 text-content-muted`},v={class:`flex flex-wrap items-center justify-between gap-3`},y={class:`text-lg font-semibold text-content-heading dark:text-white`},b={class:`text-sm text-content-muted`},x={class:`mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2`},S={class:`text-sm`},C={class:`ml-2 text-content-heading dark:text-white`},w={key:0,class:`text-sm`},T={class:`ml-2 text-red-600 dark:text-red-300`},E={class:`mt-4 overflow-x-auto rounded-[12px] border border-stroke-subtle dark:border-white/10`},D={class:`min-w-full text-sm`},O={class:`px-3 py-2 font-medium text-content-heading dark:text-white`},k={class:`px-3 py-2 text-content-muted break-all`},A={key:0},j={key:1,class:`glass-card rounded-[15px] p-5 text-content-muted`},M=t({name:`SensorsView`,__name:`Sensors`,setup(t){let M=u(),N=a(()=>M.stats?.sensors??null),P=a(()=>N.value?.readings??[]),F=a(()=>{let e=N.value;return e?[{label:`Enabled`,value:e.enabled?`Yes`:`No`},{label:`Running`,value:e.running?`Yes`:`No`},{label:`Configured / Loaded`,value:`${e.configured??0} / ${e.loaded??0}`},{label:`Poll Interval`,value:typeof e.poll_interval_seconds==`number`?`${e.poll_interval_seconds.toFixed(1)}s`:`n/a`}]:[{label:`Enabled`,value:`n/a`},{label:`Running`,value:`n/a`},{label:`Configured`,value:`n/a`},{label:`Poll Interval`,value:`n/a`}]}),I=e=>{if(e==null)return`n/a`;if(typeof e==`boolean`)return e?`true`:`false`;if(typeof e==`number`)return Number.isFinite(e)?String(e):`n/a`;if(typeof e==`string`)return e;try{return JSON.stringify(e)}catch{return String(e)}},L=e=>{if(!e)return`n/a`;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()},R=async()=>{await M.fetchStats()};return d(async()=>{await M.fetchStats()},{intervalMs:1e4,immediate:!0}),(t,a)=>(r(),c(`div`,f,[s(`div`,p,[s(`div`,{class:`flex items-start justify-between gap-4`},[a[0]||=s(`div`,null,[s(`h1`,{class:`text-xl sm:text-2xl font-semibold text-content-heading dark:text-white`},`Sensors`),s(`p`,{class:`mt-1 text-sm text-content-muted`},` Live sensor summary from the existing stats API. `)],-1),s(`button`,{class:`rounded-[10px] border border-stroke-subtle dark:border-white/10 px-3 py-2 text-sm hover:bg-black/5 dark:hover:bg-white/5`,onClick:R},` Refresh `)]),s(`div`,m,[(r(!0),c(o,null,n(F.value,t=>(r(),c(`div`,{key:t.label,class:`rounded-[12px] border border-stroke-subtle dark:border-white/10 p-3`},[s(`p`,h,e(t.label),1),s(`p`,g,e(t.value),1)]))),128))])]),N.value?i(``,!0):(r(),c(`div`,_,` Sensor data is not available yet. Ensure the repeater has started and stats are loading. `)),(r(!0),c(o,null,n(P.value,(t,u)=>(r(),c(`div`,{key:`${t.name||`sensor`}-${u}`,class:`glass-card rounded-[15px] p-4 sm:p-5`},[s(`div`,v,[s(`div`,null,[s(`h2`,y,e(t.name||`Sensor ${u+1}`),1),s(`p`,b,`Type: `+e(t.type||`unknown`),1)]),s(`span`,{class:l([`rounded-full px-3 py-1 text-xs font-semibold`,t.ok?`bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300`:`bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-300`])},e(t.ok?`OK`:`Error`),3)]),s(`div`,x,[s(`div`,S,[a[1]||=s(`span`,{class:`text-content-muted`},`Timestamp:`,-1),s(`span`,C,e(L(t.timestamp)),1)]),t.error?(r(),c(`div`,w,[a[2]||=s(`span`,{class:`text-content-muted`},`Error:`,-1),s(`span`,T,e(t.error),1)])):i(``,!0)]),s(`div`,E,[s(`table`,D,[a[4]||=s(`thead`,{class:`bg-black/5 dark:bg-white/5`},[s(`tr`,null,[s(`th`,{class:`px-3 py-2 text-left text-content-muted`},`Field`),s(`th`,{class:`px-3 py-2 text-left text-content-muted`},`Value`)])],-1),s(`tbody`,null,[(r(!0),c(o,null,n(t.data||{},(t,n)=>(r(),c(`tr`,{key:String(n),class:`border-t border-stroke-subtle dark:border-white/10`},[s(`td`,O,e(n),1),s(`td`,k,e(I(t)),1)]))),128)),!t.data||Object.keys(t.data).length===0?(r(),c(`tr`,A,[...a[3]||=[s(`td`,{class:`px-3 py-3 text-content-muted`,colspan:`2`},`No fields in payload`,-1)]])):i(``,!0)])])])]))),128)),N.value&&P.value.length===0?(r(),c(`div`,j,` Sensors are configured but no readings are available yet. `)):i(``,!0)]))}});export{M as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.glass-card[data-v-90c614aa]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffff0d;border:1px solid #ffffff1a}.modal-enter-active[data-v-90c614aa],.modal-leave-active[data-v-90c614aa]{transition:opacity .3s}.modal-enter-from[data-v-90c614aa],.modal-leave-to[data-v-90c614aa]{opacity:0}.modal-enter-active .glass-card[data-v-90c614aa],.modal-leave-active .glass-card[data-v-90c614aa]{transition:transform .3s}.modal-enter-from .glass-card[data-v-90c614aa],.modal-leave-to .glass-card[data-v-90c614aa]{transform:scale(.9)}.slide-enter-active[data-v-90c614aa],.slide-leave-active[data-v-90c614aa]{transition:all .3s}.slide-enter-from[data-v-90c614aa],.slide-leave-to[data-v-90c614aa]{opacity:0;transform:translateY(-10px)}@keyframes float-slow-90c614aa{0%,to{opacity:.8;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.6;transform:translate(20px,-20px)scale(1.05)rotate(-24.22deg)}}@keyframes float-slower-90c614aa{0%,to{opacity:.75;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.5;transform:translate(-30px,20px)scale(1.08)rotate(-24.22deg)}}@keyframes float-slowest-90c614aa{0%,to{opacity:.8;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.55;transform:translate(25px,25px)scale(1.1)rotate(-24.22deg)}}.animate-pulse-slow[data-v-90c614aa]{will-change:transform, opacity;animation:15s ease-in-out infinite float-slow-90c614aa}.animate-pulse-slower[data-v-90c614aa]{will-change:transform, opacity;animation:18s ease-in-out infinite float-slower-90c614aa}.animate-pulse-slowest[data-v-90c614aa]{will-change:transform, opacity;animation:20s ease-in-out infinite float-slowest-90c614aa}
|
||||
@@ -0,0 +1 @@
|
||||
.glass-card[data-v-5b488fbf]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffff0d;border:1px solid #ffffff1a}.modal-enter-active[data-v-5b488fbf],.modal-leave-active[data-v-5b488fbf]{transition:opacity .3s}.modal-enter-from[data-v-5b488fbf],.modal-leave-to[data-v-5b488fbf]{opacity:0}.modal-enter-active .glass-card[data-v-5b488fbf],.modal-leave-active .glass-card[data-v-5b488fbf]{transition:transform .3s}.modal-enter-from .glass-card[data-v-5b488fbf],.modal-leave-to .glass-card[data-v-5b488fbf]{transform:scale(.9)}.slide-enter-active[data-v-5b488fbf],.slide-leave-active[data-v-5b488fbf]{transition:all .3s}.slide-enter-from[data-v-5b488fbf],.slide-leave-to[data-v-5b488fbf]{opacity:0;transform:translateY(-10px)}@keyframes float-slow-5b488fbf{0%,to{opacity:.8;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.6;transform:translate(20px,-20px)scale(1.05)rotate(-24.22deg)}}@keyframes float-slower-5b488fbf{0%,to{opacity:.75;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.5;transform:translate(-30px,20px)scale(1.08)rotate(-24.22deg)}}@keyframes float-slowest-5b488fbf{0%,to{opacity:.8;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.55;transform:translate(25px,25px)scale(1.1)rotate(-24.22deg)}}.animate-pulse-slow[data-v-5b488fbf]{will-change:transform, opacity;animation:15s ease-in-out infinite float-slow-5b488fbf}.animate-pulse-slower[data-v-5b488fbf]{will-change:transform, opacity;animation:18s ease-in-out infinite float-slower-5b488fbf}.animate-pulse-slowest[data-v-5b488fbf]{will-change:transform, opacity;animation:20s ease-in-out infinite float-slowest-5b488fbf}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{J as e,Y as t,o as n}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{n as r}from"./pinia-DP0dFoGY.js";import{t as i}from"./api-Djyhg6JK.js";import{t as a}from"./packets-B8qUuzga.js";import{t as o}from"./system-eurEQN-G.js";var s={0:`Unknown`,1:`Chat Node`,2:`Repeater`,3:`Room Server`,4:`Hybrid Node`},c=r(`neighbors`,()=>{let e=t({}),r=t(!1),a=t(null),o=t(48),c=n(()=>Object.values(e.value).flat()),l=n(()=>c.value.length);function u(e=10*6e4){return a.value===null?!0:Date.now()-a.value>e}async function d(t=o.value){r.value=!0,o.value=t;let n=Object.entries(s),c=await Promise.allSettled(n.map(async([e,n])=>{try{let r=[],a=0,o=0;for(;o<200;){let e=await i.get(`/adverts_by_contact_type?contact_type=${encodeURIComponent(n)}&hours=${t}&limit=500&offset=${a}`),s=e.success&&Array.isArray(e.data)?e.data:[];if(s.length===0||(r.push(...s),s.length<500))break;a+=500,o+=1}return{typeKey:e,adverts:r}}catch{return{typeKey:e,adverts:[]}}})),l={};for(let e of c)e.status===`fulfilled`&&e.value.adverts.length>0&&(l[e.value.typeKey]=e.value.adverts);e.value=l,a.value=Date.now(),r.value=!1}function f(){e.value={},r.value=!1,a.value=null,o.value=48}return{advertsByType:e,isLoading:r,lastFetched:a,currentHours:o,allAdverts:c,totalCount:l,isStale:u,fetchAll:d,reset:f}}),l={stats:3e4,packetStats:6e4,noiseFloor:15e3,recentPackets:3e4,sparklines:3e5,advertTier:6e4,neighbors:10*6e4},u=r(`dataService`,()=>{let n=o(),r=a(),s=c(),u=t({currentTier:`unknown`,advertsAllowed:0,advertsDropped:0,activePenalties:0}),d=t(!1),f=t(null),p=e({stats:`pending`,packetStats:`pending`,noiseFloor:`pending`,recentPackets:`pending`,sparklines:`pending`,advertTier:`pending`,neighbors:`pending`}),m=new Map,h=new Map,g=[],_=!1;async function v(e,t=2){for(let n=0;n<t;n++)try{return await e()}catch(e){if(n===t-1)throw e;await new Promise(e=>setTimeout(e,500*2**n))}throw Error(`unreachable`)}async function y(){try{let e=(await i.get(`/advert_rate_limit_stats`))?.data;u.value={currentTier:typeof e?.adaptive?.current_tier==`string`?e.adaptive.current_tier:`unknown`,advertsAllowed:e?.stats?.adverts_allowed||0,advertsDropped:e?.stats?.adverts_dropped||0,activePenalties:Object.keys(e?.active_penalties||{}).length},m.set(`advertTier`,Date.now())}catch{}}async function b(e){if(e===`neighbors`){if(!s.isStale())return}else{let t=m.get(e);if(t!==void 0&&Date.now()-t<l[e])return}let t=h.get(e);if(t)return t;let i;switch(e){case`stats`:i=n.fetchStats().then(()=>{m.set(`stats`,Date.now())});break;case`packetStats`:i=r.fetchPacketStats({hours:24}).then(()=>{m.set(`packetStats`,Date.now())});break;case`noiseFloor`:i=r.fetchNoiseFloorHistory({hours:24,limit:500}).then(()=>{m.set(`noiseFloor`,Date.now())});break;case`recentPackets`:i=r.fetchRecentPackets({limit:100}).then(()=>{m.set(`recentPackets`,Date.now())});break;case`sparklines`:i=r.initializeSparklineHistory().then(()=>{m.set(`sparklines`,Date.now())});break;case`advertTier`:i=y();break;case`neighbors`:i=s.fetchAll(s.currentHours).then(()=>{});break}return h.set(e,i),i.finally(()=>h.delete(e)),i}async function x(e,t){p[e]=`loading`;try{await t(),p[e]=`done`}catch{p[e]=`error`}}async function S(){if(!_){_=!0,d.value=!0,p.stats=`loading`,f.value=`requesting`;try{await v(()=>n.fetchStats({onFirstByte:()=>{f.value=`reading`}})),m.set(`stats`,Date.now()),p.stats=`done`}catch{p.stats=`error`,console.error(`[DataService] Failed to fetch stats after retries`)}finally{f.value=null}await Promise.allSettled([x(`packetStats`,()=>r.fetchPacketStats({hours:24}).then(()=>{m.set(`packetStats`,Date.now())})),x(`noiseFloor`,()=>r.fetchNoiseFloorHistory({hours:24,limit:500}).then(()=>{m.set(`noiseFloor`,Date.now())})),x(`recentPackets`,()=>r.fetchRecentPackets({limit:100}).then(()=>{m.set(`recentPackets`,Date.now())}))]),await Promise.allSettled([x(`sparklines`,()=>r.initializeSparklineHistory().then(()=>{m.set(`sparklines`,Date.now())})),x(`advertTier`,()=>y()),x(`neighbors`,()=>s.fetchAll(s.currentHours).then(()=>{}))]),d.value=!1,C()}}function C(){T(),g.push(window.setInterval(()=>void b(`advertTier`),3e4)),g.push(window.setInterval(()=>void b(`packetStats`),6e4)),g.push(window.setInterval(()=>void b(`noiseFloor`),15e3)),g.push(window.setInterval(()=>void b(`sparklines`),3e5)),g.push(window.setInterval(()=>{let e=n.lastUpdated?.getTime()??0;Date.now()-e>25e3&&b(`stats`)},3e4))}async function w(){await new Promise(e=>setTimeout(e,3e3)),await Promise.allSettled([b(`stats`),b(`packetStats`),b(`recentPackets`)])}function T(){for(let e of g)clearInterval(e);g=[]}function E(){T(),_=!1,m.clear(),h.clear(),d.value=!1,Object.keys(p).forEach(e=>{p[e]=`pending`}),u.value={currentTier:`unknown`,advertsAllowed:0,advertsDropped:0,activePenalties:0}}return{advertTier:u,isBootstrapping:d,statsSubStatus:f,loadProgress:p,bootstrap:S,ensure:b,onReconnect:w,stopPolling:T,reset:E}});export{s as n,c as r,u as t};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 148 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 203 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 287 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 387 KiB |
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{t as e}from"./packets-B8qUuzga.js";export{e as usePacketStore};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./packets-BBUX0ge1.js";export{e as usePacketStore};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./system-eurEQN-G.js";export{e as useSystemStore};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{Y as e,o as t}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{n}from"./pinia-DP0dFoGY.js";import{n as r,t as i}from"./api-Djyhg6JK.js";import{t as a}from"./packets-B8qUuzga.js";var o=`pymc_config_cache`;function s(){try{let e=sessionStorage.getItem(o);return e?JSON.parse(e):null}catch{return null}}function c(e){if(e)try{sessionStorage.setItem(o,JSON.stringify(e))}catch{}}function l(){try{sessionStorage.removeItem(o)}catch{}}var u=n(`system`,()=>{let n=s(),o=e(n?{config:n}:null),u=e(!1),d=e(null),f=e(null),p=e(`forward`),m=e(!0),h=e(0),g=e(10),_=e(!1),v=t(()=>o.value?.config?.node_name??`Unknown`),y=t(()=>{let e=o.value?.public_key;return!e||e===`Unknown`?`Unknown`:e.length>=16?`${e.slice(0,8)} ... ${e.slice(-8)}`:`${e}`}),b=t(()=>o.value!==null),x=t(()=>o.value?.version??`Unknown`),S=t(()=>o.value?.core_version??`Unknown`),C=t(()=>o.value?.noise_floor_dbm??null),w=t(()=>g.value>0?Math.min(h.value/g.value*100,100):0),T=t(()=>p.value===`no_tx`?{text:`No TX`,title:`No repeat, no local TX; adverts skipped`}:p.value===`monitor`?{text:`Monitor Mode`,title:`Monitoring only - not forwarding packets`}:m.value?{text:`Active`,title:`Forwarding with duty cycle enforcement`}:{text:`No Limits`,title:`Forwarding without duty cycle enforcement`}),E=t(()=>({mode:p.value})),D=t(()=>m.value?{active:!0,warning:!1}:{active:!1,warning:!0}),O=e=>{_.value=e},k=null;async function A(e){return k===null?(k=(async()=>{try{u.value=!0,d.value=null;let t=new AbortController,n=15e3,i=window.setTimeout(()=>t.abort(),n),s=!1,l=()=>{s||(s=!0,e?.onFirstByte?.()),clearTimeout(i),i=window.setTimeout(()=>t.abort(),n)},p;try{p=await r.get(`/stats`,{signal:t.signal,onDownloadProgress:l,timeout:0})}finally{clearTimeout(i)}let m=p.data,h;if(m.success&&m.data)h=m.data;else if(m&&`version`in m)h=m;else throw Error(m.error||`Failed to fetch stats`);return o.value=h,f.value=new Date,j(h),c(h.config),a().systemStats=h,h}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error fetching stats:`,e),e}finally{u.value=!1}})(),k.finally(()=>{k=null}),k):k}function j(e){if(e.config){let t=e.config.repeater?.mode;t===`forward`||t===`monitor`||t===`no_tx`?p.value=t:t!==void 0&&(p.value=`forward`);let n=e.config.duty_cycle;if(n){m.value=n.enforcement_enabled!==!1;let e=n.max_airtime_percent;typeof e==`number`?g.value=e:e&&typeof e==`object`&&`parsedValue`in e&&(g.value=e.parsedValue||10)}}let t=e.utilization_percent;typeof t==`number`?h.value=t:t&&typeof t==`object`&&`parsedValue`in t&&(h.value=t.parsedValue||0)}async function M(e){try{let t=await i.post(`/set_mode`,{mode:e});if(t.success)return p.value=e,!0;throw Error(t.error||`Failed to set mode`)}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting mode:`,e),e}}async function N(e){try{let t=await i.post(`/set_duty_cycle`,{enabled:e});if(t.success)return m.value=e,!0;throw Error(t.error||`Failed to set duty cycle`)}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting duty cycle:`,e),e}}async function P(){try{let e=await i.post(`/send_advert`,{},{timeout:1e4});if(e.success)return!0;throw Error(e.error||`Failed to send advert`)}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error sending advert:`,e),e}}async function F(){return await N(!m.value)}function I(e){o.value?(e.uptime_seconds!==void 0&&(o.value.uptime_seconds=e.uptime_seconds),e.noise_floor_dbm!==void 0&&(o.value.noise_floor_dbm=e.noise_floor_dbm)):o.value=e,f.value=new Date,j(e)}async function L(e=5e3,t=!1){t||await A();let n=null;return t||(n=setInterval(async()=>{try{await A()}catch(e){console.error(`Auto-refresh error:`,e)}},e)),()=>{n&&clearInterval(n)}}function R(){o.value=null,d.value=null,f.value=null,u.value=!1,p.value=`forward`,m.value=!0,h.value=0,g.value=10,l()}return{stats:o,isLoading:u,error:d,lastUpdated:f,currentMode:p,dutyCycleEnabled:m,dutyCycleUtilization:h,dutyCycleMax:g,cadCalibrationRunning:_,nodeName:v,pubKey:y,hasStats:b,version:x,coreVersion:S,noiseFloorDbm:C,dutyCyclePercentage:w,statusBadge:T,modeButtonState:E,dutyCycleButtonState:D,fetchStats:A,setMode:M,setDutyCycle:N,sendAdvert:P,toggleDutyCycle:F,startAutoRefresh:L,updateRealtimeStats:I,reset:R,setCadCalibrationRunning:O}});export{u as t};
|
||||
import{Y as e,o as t}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{n}from"./pinia-DP0dFoGY.js";import{n as r,t as i}from"./api-DBiYn0RS.js";import{t as a}from"./packets-BBUX0ge1.js";var o=`pymc_config_cache`;function s(){try{let e=sessionStorage.getItem(o);return e?JSON.parse(e):null}catch{return null}}function c(e){if(e)try{sessionStorage.setItem(o,JSON.stringify(e))}catch{}}function l(){try{sessionStorage.removeItem(o)}catch{}}var u=n(`system`,()=>{let n=s(),o=e(n?{config:n}:null),u=e(!1),d=e(null),f=e(null),p=e(`forward`),m=e(!0),h=e(0),g=e(10),_=e(!1),v=t(()=>o.value?.config?.node_name??`Unknown`),y=t(()=>{let e=o.value?.public_key;return!e||e===`Unknown`?`Unknown`:e.length>=16?`${e.slice(0,8)} ... ${e.slice(-8)}`:`${e}`}),b=t(()=>o.value!==null),x=t(()=>o.value?.version??`Unknown`),S=t(()=>o.value?.core_version??`Unknown`),C=t(()=>o.value?.noise_floor_dbm??null),w=t(()=>g.value>0?Math.min(h.value/g.value*100,100):0),T=t(()=>p.value===`no_tx`?{text:`No TX`,title:`No repeat, no local TX; adverts skipped`}:p.value===`monitor`?{text:`Monitor Mode`,title:`Monitoring only - not forwarding packets`}:m.value?{text:`Active`,title:`Forwarding with duty cycle enforcement`}:{text:`No Limits`,title:`Forwarding without duty cycle enforcement`}),E=t(()=>({mode:p.value})),D=t(()=>m.value?{active:!0,warning:!1}:{active:!1,warning:!0}),O=e=>{_.value=e},k=null;async function A(e){return k===null?(k=(async()=>{try{u.value=!0,d.value=null;let t=new AbortController,n=15e3,i=window.setTimeout(()=>t.abort(),n),s=!1,l=()=>{s||(s=!0,e?.onFirstByte?.()),clearTimeout(i),i=window.setTimeout(()=>t.abort(),n)},p;try{p=await r.get(`/stats`,{signal:t.signal,onDownloadProgress:l,timeout:0})}finally{clearTimeout(i)}let m=p.data,h;if(m.success&&m.data)h=m.data;else if(m&&`version`in m)h=m;else throw Error(m.error||`Failed to fetch stats`);return o.value=h,f.value=new Date,j(h),c(h.config),a().systemStats=h,h}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error fetching stats:`,e),e}finally{u.value=!1}})(),k.finally(()=>{k=null}),k):k}function j(e){if(e.config){let t=e.config.repeater?.mode;t===`forward`||t===`monitor`||t===`no_tx`?p.value=t:t!==void 0&&(p.value=`forward`);let n=e.config.duty_cycle;if(n){m.value=n.enforcement_enabled!==!1;let e=n.max_airtime_percent;typeof e==`number`?g.value=e:e&&typeof e==`object`&&`parsedValue`in e&&(g.value=e.parsedValue||10)}}let t=e.utilization_percent;typeof t==`number`?h.value=t:t&&typeof t==`object`&&`parsedValue`in t&&(h.value=t.parsedValue||0)}async function M(e){try{let t=await i.post(`/set_mode`,{mode:e});if(t.success)return p.value=e,!0;throw Error(t.error||`Failed to set mode`)}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting mode:`,e),e}}async function N(e){try{let t=await i.post(`/set_duty_cycle`,{enabled:e});if(t.success)return m.value=e,!0;throw Error(t.error||`Failed to set duty cycle`)}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting duty cycle:`,e),e}}async function P(){try{let e=await i.post(`/send_advert`,{},{timeout:1e4});if(e.success)return!0;throw Error(e.error||`Failed to send advert`)}catch(e){throw d.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error sending advert:`,e),e}}async function F(){return await N(!m.value)}function I(e){o.value?(e.uptime_seconds!==void 0&&(o.value.uptime_seconds=e.uptime_seconds),e.noise_floor_dbm!==void 0&&(o.value.noise_floor_dbm=e.noise_floor_dbm)):o.value=e,f.value=new Date,j(e)}async function L(e=5e3,t=!1){t||await A();let n=null;return t||(n=setInterval(async()=>{try{await A()}catch(e){console.error(`Auto-refresh error:`,e)}},e)),()=>{n&&clearInterval(n)}}function R(){o.value=null,d.value=null,f.value=null,u.value=!1,p.value=`forward`,m.value=!0,h.value=0,g.value=10,l()}return{stats:o,isLoading:u,error:d,lastUpdated:f,currentMode:p,dutyCycleEnabled:m,dutyCycleUtilization:h,dutyCycleMax:g,cadCalibrationRunning:_,nodeName:v,pubKey:y,hasStats:b,version:x,coreVersion:S,noiseFloorDbm:C,dutyCyclePercentage:w,statusBadge:T,modeButtonState:E,dutyCycleButtonState:D,fetchStats:A,setMode:M,setDutyCycle:N,sendAdvert:P,toggleDutyCycle:F,startAutoRefresh:L,updateRealtimeStats:I,reset:R,setCadCalibrationRunning:O}});export{u as t};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./system-BfTzQTOF.js";export{e as useSystemStore};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{o as e}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{t}from"./system-eurEQN-G.js";var n={7:-7.5,8:-10,9:-12.5,10:-15,11:-17.5,12:-20},r=-116,i=8,a=5;function o(e,t){return e-t}function s(e){return n[e]??n[i]}function c(e,t){let n=t+a;if(e<=t){let n=e<=t-5?0:1;return{bars:n,color:`text-red-600 dark:text-red-400`,snr:e,quality:n===0?`None`:`Poor`}}if(e<n){let n=(e-t)/a<.5?2:3;return{bars:n,color:n===2?`text-orange-600 dark:text-orange-400`:`text-yellow-600 dark:text-yellow-400`,snr:e,quality:`Fair`}}let r=e-n>=10?5:4;return{bars:r,color:r===5?`text-green-600 dark:text-green-400`:`text-green-600 dark:text-green-300`,snr:e,quality:r===5?`Excellent`:`Good`}}function l(){let n=t(),a=e(()=>n.noiseFloorDbm??r),l=e(()=>n.stats?.config?.radio?.spreading_factor??i),u=e(()=>s(l.value));return{getSignalQuality:e=>{if(!e||e>0||e<-120)return{bars:0,color:`text-gray-400 dark:text-gray-500`,snr:-999,quality:`None`};let t=o(e,a.value);return c(Math.max(-30,Math.min(20,t)),u.value)},noiseFloor:a,spreadingFactor:l,minSNR:u}}export{l as t};
|
||||
import{o as e}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{t}from"./system-BfTzQTOF.js";var n={7:-7.5,8:-10,9:-12.5,10:-15,11:-17.5,12:-20},r=-116,i=8,a=5;function o(e,t){return e-t}function s(e){return n[e]??n[i]}function c(e,t){let n=t+a;if(e<=t){let n=e<=t-5?0:1;return{bars:n,color:`text-red-600 dark:text-red-400`,snr:e,quality:n===0?`None`:`Poor`}}if(e<n){let n=(e-t)/a<.5?2:3;return{bars:n,color:n===2?`text-orange-600 dark:text-orange-400`:`text-yellow-600 dark:text-yellow-400`,snr:e,quality:`Fair`}}let r=e-n>=10?5:4;return{bars:r,color:r===5?`text-green-600 dark:text-green-400`:`text-green-600 dark:text-green-300`,snr:e,quality:r===5?`Excellent`:`Good`}}function l(){let n=t(),a=e(()=>n.noiseFloorDbm??r),l=e(()=>n.stats?.config?.radio?.spreading_factor??i),u=e(()=>s(l.value));return{getSignalQuality:e=>{if(!e||e>0||e<-120)return{bars:0,color:`text-gray-400 dark:text-gray-500`,snr:-999,quality:`None`};let t=o(e,a.value);return c(Math.max(-30,Math.min(20,t)),u.value)},noiseFloor:a,spreadingFactor:l,minSNR:u}}export{l as t};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{Y as e,o as t}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{n}from"./pinia-DP0dFoGY.js";import{c as r,f as i,i as a,l as o}from"./api-Djyhg6JK.js";import{t as s}from"./packets-B8qUuzga.js";import{t as c}from"./system-eurEQN-G.js";import{t as l}from"./dataService-7KoBNfjw.js";var u=n(`websocket`,()=>{let n=e(null),u=e(`idle`),d=e(0),f=e(Date.now()),p=e(null),m=e(null),h=e(!1),g=e(!1),_=e(!1),v=e({visible:!1,message:``,variant:`info`}),y=null,b=s(),x=c(),S=a(),C=l(),w=t(()=>u.value===`open`);function T(e,t,n=0){y!==null&&(clearTimeout(y),y=null),v.value={visible:!0,message:e,variant:t},n>0&&(y=window.setTimeout(()=>{E()},n))}function E(){y!==null&&(clearTimeout(y),y=null),v.value.visible=!1}function D(){p.value!==null&&(clearTimeout(p.value),p.value=null)}function O(){m.value!==null&&(clearInterval(m.value),m.value=null)}function k(){T(`Reconnecting...`,`info`)}function A(){let e=o();return!h.value&&!g.value&&!!e&&!i()&&S.canMaintainConnections}function j(){let e,t=o(),n=r(),i=new URLSearchParams;return t&&i.set(`token`,t),n&&i.set(`client_id`,n),e=`${window.location.protocol===`https:`?`wss:`:`ws:`}//${``?.trim()?new URL(``).host:window.location.host}/ws/packets?${i.toString()}`,e}async function M(){await C.onReconnect()}function N(e=!1){O(),n.value&&e&&(n.value.onopen=null,n.value.onmessage=null,n.value.onerror=null,n.value.onclose=null)}function P(){if(D(),!A()){u.value=`closed`;return}if(d.value>=6){u.value=`closed`,T(`Connection lost`,`error`,5e3);return}u.value=`reconnecting`,k();let e=Math.min(1e3*2**d.value,3e4);d.value+=1,p.value=window.setTimeout(()=>{p.value=null,F(!0)},e)}function F(e=!1){if(!A()||n.value?.readyState===WebSocket.OPEN||n.value?.readyState===WebSocket.CONNECTING)return;D(),N(!0),u.value=e||d.value>0||_.value?`reconnecting`:`connecting`,_.value&&k();let t=new WebSocket(j());n.value=t,t.onopen=()=>{u.value=`open`,f.value=Date.now();let e=d.value>0||_.value;d.value=0,_.value=!1,O(),m.value=window.setInterval(()=>{n.value?.readyState===WebSocket.OPEN&&(n.value.send(JSON.stringify({type:`ping`})),Date.now()-f.value>6e4&&(N(!0),n.value?.close()))},3e4),e?(C.onReconnect(),T(`Back online`,`success`,2500)):E()},t.onmessage=e=>{try{let t=JSON.parse(e.data);t.type===`packet`?b.addRealtimePacket(t.data):t.type===`stats`?(t.data?.packet_stats&&b.updateRealtimeStats({packet_stats:t.data.packet_stats}),t.data?.system_stats&&x.updateRealtimeStats(t.data.system_stats)):t.type===`packet_stats`?b.updateRealtimeStats(t.data):t.type===`system_stats`?x.updateRealtimeStats(t.data):(t.type===`pong`||t.type===`ping`)&&(f.value=Date.now(),t.type===`ping`&&n.value?.readyState===WebSocket.OPEN&&n.value.send(JSON.stringify({type:`pong`})))}catch(e){console.error(`[WebSocket] Parse error:`,e)}},t.onerror=()=>{u.value=d.value>0?`reconnecting`:`closed`},t.onclose=e=>{let t=n.value;if(N(),t===n.value&&(n.value=null),h.value||g.value){u.value=`closed`;return}if(e.code===1008||e.code===4001||e.code===4003){S.handleAuthFailure(`expired`);return}P()}}function I(e=`lifecycle`){if(g.value=!0,D(),u.value=`closed`,e===`offline`?(_.value=!0,T(`Connection lost`,`error`,4e3)):e===`hidden`?(_.value=!0,E()):e===`logout`&&(_.value=!1,E()),n.value){let e=n.value;n.value=null,N(!0),e.close()}}function L(){h.value=!1,g.value=!1}function R(e={}){h.value=e.preventReconnect??h.value,e.silent||E(),I(e.preventReconnect?`logout`:`lifecycle`),d.value=0}return{isConnected:w,connectionState:u,reconnectAttempts:d,snackbar:v,connect:F,disconnect:R,pause:I,allowReconnect:L,hideSnackbar:E,resyncData:M}});export{u as t};
|
||||
import{Y as e,o as t}from"./runtime-core.esm-bundler-C5QBTNWE.js";import{n}from"./pinia-DP0dFoGY.js";import{c as r,f as i,i as a,l as o}from"./api-DBiYn0RS.js";import{t as s}from"./packets-BBUX0ge1.js";import{t as c}from"./system-BfTzQTOF.js";import{t as l}from"./dataService-KNFTsxUb.js";var u=n(`websocket`,()=>{let n=e(null),u=e(`idle`),d=e(0),f=e(Date.now()),p=e(null),m=e(null),h=e(!1),g=e(!1),_=e(!1),v=e({visible:!1,message:``,variant:`info`}),y=null,b=s(),x=c(),S=a(),C=l(),w=t(()=>u.value===`open`);function T(e,t,n=0){y!==null&&(clearTimeout(y),y=null),v.value={visible:!0,message:e,variant:t},n>0&&(y=window.setTimeout(()=>{E()},n))}function E(){y!==null&&(clearTimeout(y),y=null),v.value.visible=!1}function D(){p.value!==null&&(clearTimeout(p.value),p.value=null)}function O(){m.value!==null&&(clearInterval(m.value),m.value=null)}function k(){T(`Reconnecting...`,`info`)}function A(){let e=o();return!h.value&&!g.value&&!!e&&!i()&&S.canMaintainConnections}function j(){let e,t=o(),n=r(),i=new URLSearchParams;return t&&i.set(`token`,t),n&&i.set(`client_id`,n),e=`${window.location.protocol===`https:`?`wss:`:`ws:`}//${``?.trim()?new URL(``).host:window.location.host}/ws/packets?${i.toString()}`,e}async function M(){await C.onReconnect()}function N(e=!1){O(),n.value&&e&&(n.value.onopen=null,n.value.onmessage=null,n.value.onerror=null,n.value.onclose=null)}function P(){if(D(),!A()){u.value=`closed`;return}if(d.value>=6){u.value=`closed`,T(`Connection lost`,`error`,5e3);return}u.value=`reconnecting`,k();let e=Math.min(1e3*2**d.value,3e4);d.value+=1,p.value=window.setTimeout(()=>{p.value=null,F(!0)},e)}function F(e=!1){if(!A()||n.value?.readyState===WebSocket.OPEN||n.value?.readyState===WebSocket.CONNECTING)return;D(),N(!0),u.value=e||d.value>0||_.value?`reconnecting`:`connecting`,_.value&&k();let t=new WebSocket(j());n.value=t,t.onopen=()=>{u.value=`open`,f.value=Date.now();let e=d.value>0||_.value;d.value=0,_.value=!1,O(),m.value=window.setInterval(()=>{n.value?.readyState===WebSocket.OPEN&&(n.value.send(JSON.stringify({type:`ping`})),Date.now()-f.value>6e4&&(N(!0),n.value?.close()))},3e4),e?(C.onReconnect(),T(`Back online`,`success`,2500)):E()},t.onmessage=e=>{try{let t=JSON.parse(e.data);t.type===`packet`?b.addRealtimePacket(t.data):t.type===`stats`?(t.data?.packet_stats&&b.updateRealtimeStats({packet_stats:t.data.packet_stats}),t.data?.system_stats&&x.updateRealtimeStats(t.data.system_stats)):t.type===`packet_stats`?b.updateRealtimeStats(t.data):t.type===`system_stats`?x.updateRealtimeStats(t.data):(t.type===`pong`||t.type===`ping`)&&(f.value=Date.now(),t.type===`ping`&&n.value?.readyState===WebSocket.OPEN&&n.value.send(JSON.stringify({type:`pong`})))}catch(e){console.error(`[WebSocket] Parse error:`,e)}},t.onerror=()=>{u.value=d.value>0?`reconnecting`:`closed`},t.onclose=e=>{let t=n.value;if(N(),t===n.value&&(n.value=null),h.value||g.value){u.value=`closed`;return}if(e.code===1008||e.code===4001||e.code===4003){S.handleAuthFailure(`expired`);return}C.noteDisconnect(),P()}}function I(e=`lifecycle`){if(g.value=!0,D(),u.value=`closed`,e===`offline`?(_.value=!0,T(`Connection lost`,`error`,4e3)):e===`hidden`?(_.value=!0,E()):e===`logout`&&(_.value=!1,E()),n.value){let e=n.value;n.value=null,N(!0),e.close()}}function L(){h.value=!1,g.value=!1}function R(e={}){h.value=e.preventReconnect??h.value,e.silent||E(),I(e.preventReconnect?`logout`:`lifecycle`),d.value=0}return{isConnected:w,connectionState:u,reconnectAttempts:d,snackbar:v,connect:F,disconnect:R,pause:I,allowReconnect:L,hideSnackbar:E,resyncData:M}});export{u as t};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./websocket-BqxrlZFR.js";export{e as useWebSocketStore};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./websocket-BMiU0zVq.js";export{e as useWebSocketStore};
|
||||
@@ -8,19 +8,19 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-1AODY5To.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CG181k2K.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-TcpyXLsZ.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/chunk-DECur_0Z.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-C5QBTNWE.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pinia-DP0dFoGY.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/api-Djyhg6JK.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/api-DBiYn0RS.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/Spinner-CYvUNW0P.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/useTheme-D8lKuC-u.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/packets-B8qUuzga.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/system-eurEQN-G.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/dataService-7KoBNfjw.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/websocket-BMiU0zVq.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DtkDamlO.css">
|
||||
<link rel="modulepreload" crossorigin href="/assets/packets-BBUX0ge1.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/system-BfTzQTOF.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/dataService-KNFTsxUb.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/websocket-BqxrlZFR.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DInITHV1.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from repeater.web.api_endpoints import APIEndpoints
|
||||
|
||||
|
||||
def _make_api(config):
|
||||
api = APIEndpoints.__new__(APIEndpoints)
|
||||
api.config = config
|
||||
return api
|
||||
|
||||
|
||||
def test_needs_setup_triggers_when_radio_type_missing():
|
||||
api = _make_api(
|
||||
{
|
||||
"repeater": {
|
||||
"node_name": "mesh-node-01",
|
||||
"security": {"admin_password": "strong-password"},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
result = api.needs_setup()
|
||||
|
||||
assert result["needs_setup"] is True
|
||||
assert result["reasons"]["radio_not_configured"] is True
|
||||
assert result["reasons"]["default_name"] is False
|
||||
assert result["reasons"]["default_password"] is False
|
||||
|
||||
|
||||
def test_needs_setup_does_not_trigger_for_configured_radio():
|
||||
api = _make_api(
|
||||
{
|
||||
"radio_type": "sx1262",
|
||||
"repeater": {
|
||||
"node_name": "mesh-node-01",
|
||||
"security": {"admin_password": "strong-password"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
result = api.needs_setup()
|
||||
|
||||
assert result["needs_setup"] is False
|
||||
assert result["reasons"]["radio_not_configured"] is False
|
||||
@@ -54,6 +54,16 @@ def test_get_radio_for_board_passes_en_pins(monkeypatch):
|
||||
assert "en_pin" not in captured_kwargs
|
||||
|
||||
|
||||
def test_get_radio_for_board_null_radio_type_returns_null_radio():
|
||||
radio = get_radio_for_board({"radio_type": None})
|
||||
assert type(radio).__name__ == "NullRadio"
|
||||
|
||||
|
||||
def test_get_radio_for_board_missing_radio_type_returns_null_radio():
|
||||
radio = get_radio_for_board({})
|
||||
assert type(radio).__name__ == "NullRadio"
|
||||
|
||||
|
||||
# ─── pymc_tcp / pymc_usb branches ────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
"""Tests for setup_wizard pymc_usb / pymc_tcp branches.
|
||||
|
||||
These verify that when the first-run /setup wizard is finished with one of
|
||||
the two pymc_* hardware tiles selected, api_endpoints.setup_wizard() writes
|
||||
a config.yaml that matches what get_radio_for_board() expects (see
|
||||
repeater/config.py and tests/test_radio_config.py).
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import types
|
||||
|
||||
import cherrypy
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from repeater.web.api_endpoints import APIEndpoints
|
||||
|
||||
|
||||
# Minimal initial config.yaml the wizard writes into.
|
||||
_BASE_CONFIG = {
|
||||
"repeater": {"node_name": "mesh-repeater-01", "security": {"admin_password": "admin123"}},
|
||||
"radio": {},
|
||||
}
|
||||
|
||||
_BASE_REQUEST = {
|
||||
"node_name": "pymc-test",
|
||||
"admin_password": "supersecret",
|
||||
"radio_preset": {
|
||||
"frequency": 869.618,
|
||||
"spreading_factor": 8,
|
||||
"bandwidth": 62.5,
|
||||
"coding_rate": 8,
|
||||
"tx_power": 22,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def wizard_env(tmp_path, monkeypatch):
|
||||
"""Bootstrap a tempdir with config.yaml + radio-settings.json + mocked cherrypy."""
|
||||
config_path = tmp_path / "config.yaml"
|
||||
with open(config_path, "w") as f:
|
||||
yaml.safe_dump(_BASE_CONFIG, f)
|
||||
|
||||
radio_settings = {
|
||||
"hardware": {
|
||||
"pymc_usb": {
|
||||
"name": "pymc_usb modem (USB-CDC)",
|
||||
"radio_type": "pymc_usb",
|
||||
"tx_power": 22,
|
||||
"preamble_length": 16,
|
||||
},
|
||||
"pymc_tcp": {
|
||||
"name": "pymc_tcp modem (Wi-Fi / Ethernet)",
|
||||
"radio_type": "pymc_tcp",
|
||||
"tx_power": 22,
|
||||
"preamble_length": 16,
|
||||
},
|
||||
}
|
||||
}
|
||||
with open(tmp_path / "radio-settings.json", "w") as f:
|
||||
json.dump(radio_settings, f)
|
||||
|
||||
# resolve_storage_dir() returns the directory of config_path when the
|
||||
# config has no explicit storage_dir set — that's exactly what we want
|
||||
# so the wizard finds our radio-settings.json next to config.yaml.
|
||||
config = {
|
||||
"repeater": {
|
||||
"storage_dir": str(tmp_path),
|
||||
"node_name": "mesh-repeater-01",
|
||||
"security": {"admin_password": "admin123"},
|
||||
}
|
||||
}
|
||||
endpoints = APIEndpoints(config=config, config_path=str(config_path))
|
||||
|
||||
# Stub the post-wizard service restart — we don't want a real systemctl call.
|
||||
fake_service_utils = types.ModuleType("repeater.service_utils")
|
||||
fake_service_utils.restart_service = lambda: None
|
||||
monkeypatch.setitem(sys.modules, "repeater.service_utils", fake_service_utils)
|
||||
|
||||
def _set_request(body):
|
||||
# cherrypy.request is a thread-local — populate the bits the handler reads.
|
||||
cherrypy.request.method = "POST"
|
||||
cherrypy.request.json = body
|
||||
|
||||
return tmp_path, config_path, endpoints, _set_request
|
||||
|
||||
|
||||
def _read_yaml(path):
|
||||
with open(path) as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
# ─── pymc_usb ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_wizard_pymc_usb_defaults(wizard_env):
|
||||
tmp_path, config_path, endpoints, set_request = wizard_env
|
||||
|
||||
body = dict(_BASE_REQUEST, hardware_key="pymc_usb")
|
||||
set_request(body)
|
||||
|
||||
result = endpoints.setup_wizard()
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["config"]["radio_type"] == "pymc_usb"
|
||||
assert result["config"]["pymc_usb_port"] == "/dev/ttyACM0"
|
||||
assert result["config"]["pymc_usb_baudrate"] == 921600
|
||||
|
||||
written = _read_yaml(config_path)
|
||||
assert written["radio_type"] == "pymc_usb"
|
||||
assert written["pymc_usb"]["port"] == "/dev/ttyACM0"
|
||||
assert written["pymc_usb"]["baudrate"] == 921600
|
||||
assert written["pymc_usb"]["lbt_enabled"] is True
|
||||
assert written["pymc_usb"]["lbt_max_attempts"] == 5
|
||||
assert written["radio"]["tx_power"] == 22
|
||||
assert written["radio"]["preamble_length"] == 16
|
||||
# config.py rejects pymc_usb if 'sx1262' / 'ch341' keys leak in — none here.
|
||||
assert "sx1262" not in written
|
||||
|
||||
|
||||
def test_wizard_pymc_usb_overrides_from_request(wizard_env):
|
||||
tmp_path, config_path, endpoints, set_request = wizard_env
|
||||
|
||||
body = dict(
|
||||
_BASE_REQUEST,
|
||||
hardware_key="pymc_usb",
|
||||
pymc_usb_port="/dev/ttyUSB0",
|
||||
pymc_usb_baudrate=115200,
|
||||
)
|
||||
set_request(body)
|
||||
|
||||
result = endpoints.setup_wizard()
|
||||
|
||||
assert result["success"] is True
|
||||
written = _read_yaml(config_path)
|
||||
assert written["pymc_usb"]["port"] == "/dev/ttyUSB0"
|
||||
assert written["pymc_usb"]["baudrate"] == 115200
|
||||
|
||||
|
||||
# ─── pymc_tcp ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_wizard_pymc_tcp_placeholder(wizard_env):
|
||||
"""No host in request → wizard writes a sentinel placeholder. config.py
|
||||
will then refuse to start with a clear error pointing at pymc_tcp.host."""
|
||||
tmp_path, config_path, endpoints, set_request = wizard_env
|
||||
|
||||
body = dict(_BASE_REQUEST, hardware_key="pymc_tcp")
|
||||
set_request(body)
|
||||
|
||||
result = endpoints.setup_wizard()
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["config"]["radio_type"] == "pymc_tcp"
|
||||
assert result["config"]["pymc_tcp_host"] == "REPLACE_WITH_MODEM_HOST"
|
||||
assert result["config"]["pymc_tcp_port"] == 5055
|
||||
|
||||
written = _read_yaml(config_path)
|
||||
assert written["radio_type"] == "pymc_tcp"
|
||||
assert written["pymc_tcp"]["host"] == "REPLACE_WITH_MODEM_HOST"
|
||||
assert written["pymc_tcp"]["port"] == 5055
|
||||
assert written["pymc_tcp"]["token"] == ""
|
||||
assert written["pymc_tcp"]["connect_timeout"] == 5.0
|
||||
assert written["pymc_tcp"]["lbt_enabled"] is True
|
||||
# token deliberately stripped from response.
|
||||
assert "pymc_tcp_token" not in result["config"]
|
||||
|
||||
|
||||
def test_wizard_pymc_tcp_full_fields(wizard_env):
|
||||
tmp_path, config_path, endpoints, set_request = wizard_env
|
||||
|
||||
body = dict(
|
||||
_BASE_REQUEST,
|
||||
hardware_key="pymc_tcp",
|
||||
pymc_tcp_host="pymc-3e2834.local",
|
||||
pymc_tcp_port=6000,
|
||||
pymc_tcp_token="hunter2",
|
||||
)
|
||||
set_request(body)
|
||||
|
||||
result = endpoints.setup_wizard()
|
||||
|
||||
assert result["success"] is True
|
||||
written = _read_yaml(config_path)
|
||||
assert written["pymc_tcp"]["host"] == "pymc-3e2834.local"
|
||||
assert written["pymc_tcp"]["port"] == 6000
|
||||
assert written["pymc_tcp"]["token"] == "hunter2"
|
||||
|
||||
|
||||
# ─── KISS regression guard ────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_wizard_kiss_branch_unchanged(wizard_env, tmp_path):
|
||||
"""Make sure adding the pymc_* branches didn't break the existing KISS path."""
|
||||
tmp_path, config_path, endpoints, set_request = wizard_env
|
||||
|
||||
body = dict(_BASE_REQUEST, hardware_key="kiss")
|
||||
set_request(body)
|
||||
|
||||
result = endpoints.setup_wizard()
|
||||
|
||||
assert result["success"] is True
|
||||
written = _read_yaml(config_path)
|
||||
assert written["radio_type"] == "kiss"
|
||||
assert written["kiss"]["port"] == "/dev/ttyUSB0"
|
||||
assert written["kiss"]["baud_rate"] == 115200
|
||||
Reference in New Issue
Block a user