mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-07 17:33:16 +02:00
feat: Add sensor plugin framework and Sensors
This commit is contained in:
@@ -207,6 +207,35 @@ gps:
|
||||
time_sync_min_offset_seconds: 1.0
|
||||
time_sync_min_valid_year: 2020
|
||||
|
||||
# Sensor plug-ins (polled in background and exposed under /api/stats -> sensors)
|
||||
sensors:
|
||||
# Master switch for sensor subsystem
|
||||
enabled: true
|
||||
|
||||
# Poll interval for all configured sensors
|
||||
poll_interval_seconds: 10.0
|
||||
|
||||
# If true, missing Python packages for a sensor may be installed at runtime
|
||||
# using "python -m pip install ...".
|
||||
auto_install_packages: true
|
||||
|
||||
# Sensor definitions. Add one item per sensor instance.
|
||||
definitions:
|
||||
# Built-in sensor: host CPU/memory/disk/network stats
|
||||
- type: hardware_stats
|
||||
name: system-health
|
||||
enabled: true
|
||||
|
||||
# Example INA219 sensor (commented out by default)
|
||||
# - type: ina219
|
||||
# name: power-monitor
|
||||
# enabled: true
|
||||
# auto_install_packages: true
|
||||
# settings:
|
||||
# i2c_address: 64 # 0x40 in decimal
|
||||
# max_expected_amps: 2.0
|
||||
# shunt_ohms: 0.1
|
||||
|
||||
# Mesh Network Configuration
|
||||
mesh:
|
||||
# Unscoped flood policy - controls whether the repeater allows or denies unscoped flooding
|
||||
|
||||
@@ -136,6 +136,14 @@ def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
|
||||
"persist_gps_fix_interval_seconds": 600.0,
|
||||
}
|
||||
|
||||
if "sensors" not in config:
|
||||
config["sensors"] = {
|
||||
"enabled": False,
|
||||
"poll_interval_seconds": 30.0,
|
||||
"auto_install_packages": False,
|
||||
"definitions": [],
|
||||
}
|
||||
|
||||
# Ensure repeater.security exists with defaults for upgrades from older configs
|
||||
if "repeater" not in config:
|
||||
config["repeater"] = {}
|
||||
|
||||
@@ -24,6 +24,7 @@ from repeater.handler_helpers import (
|
||||
)
|
||||
from repeater.identity_manager import IdentityManager
|
||||
from repeater.packet_router import PacketRouter
|
||||
from repeater.sensors import SensorManager
|
||||
from repeater.web.http_server import HTTPStatsServer, _log_buffer
|
||||
|
||||
logger = logging.getLogger("RepeaterDaemon")
|
||||
@@ -51,6 +52,7 @@ class RepeaterDaemon:
|
||||
self.protocol_request_helper = None
|
||||
self.glass_handler = None
|
||||
self.gps_service = None
|
||||
self.sensor_manager = None
|
||||
self.acl = None
|
||||
self.router = None
|
||||
self.companion_bridges: dict[int, object] = {}
|
||||
@@ -264,6 +266,13 @@ class RepeaterDaemon:
|
||||
)
|
||||
logger.info("Config manager initialized")
|
||||
|
||||
self.sensor_manager = SensorManager(self.config)
|
||||
self.sensor_manager.start()
|
||||
if self.sensor_manager.get_summary().get("loaded", 0):
|
||||
logger.info("Sensor manager initialized")
|
||||
else:
|
||||
logger.info("No configured sensors loaded")
|
||||
|
||||
self.gps_service = GPSService(
|
||||
self.config,
|
||||
location_update_callback=self._update_repeater_location_from_gps,
|
||||
@@ -932,6 +941,9 @@ class RepeaterDaemon:
|
||||
if self.gps_service:
|
||||
stats["gps"] = self.gps_service.get_summary()
|
||||
|
||||
if self.sensor_manager:
|
||||
stats["sensors"] = self.sensor_manager.get_summary()
|
||||
|
||||
return stats
|
||||
|
||||
async def _get_companion_stats(self, stats_type: int) -> dict:
|
||||
@@ -1154,6 +1166,13 @@ class RepeaterDaemon:
|
||||
except Exception as e:
|
||||
logger.warning(f"Error stopping Glass handler: {e}")
|
||||
|
||||
# Stop sensor manager.
|
||||
if self.sensor_manager:
|
||||
try:
|
||||
self.sensor_manager.stop()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error stopping sensor manager: {e}")
|
||||
|
||||
# Stop GPS diagnostics.
|
||||
if self.gps_service:
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from .base import SensorBase
|
||||
from .manager import SensorManager
|
||||
from .registry import SensorRegistry
|
||||
|
||||
# HardwareStatsSensor is optional (requires psutil); import directly if needed:
|
||||
# from repeater.sensors.hardware_stats import HardwareStatsSensor
|
||||
|
||||
__all__ = [
|
||||
"SensorBase",
|
||||
"SensorManager",
|
||||
"SensorRegistry",
|
||||
]
|
||||
@@ -0,0 +1,118 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, Iterable, Optional, Tuple
|
||||
|
||||
|
||||
class SensorBase(ABC):
|
||||
"""Base class for lightweight sensor plug-ins."""
|
||||
|
||||
sensor_type = "sensor"
|
||||
|
||||
def __init__(self, name: str, config: Optional[Dict[str, Any]] = None, log: Optional[logging.Logger] = None):
|
||||
self.name = name
|
||||
self.config = config or {}
|
||||
self.settings = self.config.get("settings", {}) if isinstance(self.config, dict) else {}
|
||||
self.enabled = bool(self.config.get("enabled", True)) if isinstance(self.config, dict) else True
|
||||
self.log = log or logging.getLogger(self.__class__.__name__)
|
||||
|
||||
@abstractmethod
|
||||
def _read(self) -> Dict[str, Any]:
|
||||
"""Read the sensor and return a raw payload dictionary."""
|
||||
|
||||
def read(self) -> Dict[str, Any]:
|
||||
timestamp = datetime.now(timezone.utc).isoformat()
|
||||
if not self.enabled:
|
||||
return self._result(ok=False, timestamp=timestamp, error="disabled")
|
||||
|
||||
try:
|
||||
data = self._read()
|
||||
except Exception as exc:
|
||||
self.log.warning("Sensor read failed for %s: %s", self.name, exc)
|
||||
return self._result(ok=False, timestamp=timestamp, error=f"{type(exc).__name__}: {exc}")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
if not isinstance(data, dict):
|
||||
data = {"value": data}
|
||||
|
||||
return self._result(ok=True, timestamp=timestamp, data=data)
|
||||
|
||||
def _result(
|
||||
self,
|
||||
*,
|
||||
ok: bool,
|
||||
timestamp: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
error: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
result: Dict[str, Any] = {
|
||||
"name": self.name,
|
||||
"type": self.sensor_type,
|
||||
"ok": ok,
|
||||
"timestamp": timestamp,
|
||||
"data": data or {},
|
||||
}
|
||||
if error:
|
||||
result["error"] = error
|
||||
return result
|
||||
|
||||
def _auto_install_enabled(self) -> bool:
|
||||
"""Return true when package auto-install is enabled for this sensor."""
|
||||
raw = self.config.get("auto_install_packages") if isinstance(self.config, dict) else False
|
||||
return bool(raw)
|
||||
|
||||
def ensure_python_modules(self, modules: Iterable[Tuple[str, str]]) -> bool:
|
||||
"""Ensure Python modules are importable; optionally install via pip if missing.
|
||||
|
||||
modules: iterable of (import_name, pip_package_name).
|
||||
"""
|
||||
missing: list[Tuple[str, str]] = []
|
||||
for import_name, package_name in modules:
|
||||
if importlib.util.find_spec(import_name) is None:
|
||||
missing.append((import_name, package_name))
|
||||
|
||||
if not missing:
|
||||
return True
|
||||
|
||||
if not self._auto_install_enabled():
|
||||
names = ", ".join(pkg for _, pkg in missing)
|
||||
self.log.warning(
|
||||
"Missing sensor dependencies for %s: %s (set auto_install_packages=true to install automatically)",
|
||||
self.name,
|
||||
names,
|
||||
)
|
||||
return False
|
||||
|
||||
for import_name, package_name in missing:
|
||||
self.log.info("Installing missing dependency for %s: %s", self.name, package_name)
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", package_name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
self.log.warning(
|
||||
"Failed installing %s for %s: %s",
|
||||
package_name,
|
||||
self.name,
|
||||
(result.stderr or result.stdout or "unknown error").strip(),
|
||||
)
|
||||
return False
|
||||
|
||||
if importlib.util.find_spec(import_name) is None:
|
||||
self.log.warning(
|
||||
"Dependency %s installed but module %s still unavailable for %s",
|
||||
package_name,
|
||||
import_name,
|
||||
self.name,
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from repeater.data_acquisition.hardware_stats import HardwareStatsCollector
|
||||
|
||||
from .base import SensorBase
|
||||
from .registry import SensorRegistry
|
||||
|
||||
|
||||
@SensorRegistry.register("hardware_stats")
|
||||
class HardwareStatsSensor(SensorBase):
|
||||
sensor_type = "hardware_stats"
|
||||
|
||||
def __init__(self, name: str, config: Dict[str, Any] | None = None, log=None):
|
||||
super().__init__(name=name, config=config, log=log)
|
||||
self.collector = HardwareStatsCollector()
|
||||
|
||||
def _read(self) -> Dict[str, Any]:
|
||||
return self.collector.get_stats()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
INA219 current/voltage/power monitor sensor plug-in.
|
||||
|
||||
Requires: pip install adafruit-circuitpython-ina219
|
||||
|
||||
Config example:
|
||||
- type: ina219
|
||||
name: "power_monitor"
|
||||
enabled: true
|
||||
auto_install_packages: false
|
||||
settings:
|
||||
i2c_address: 0x40 # Default INA219 I2C address
|
||||
max_expected_amps: 2.0
|
||||
shunt_ohms: 0.1 # 0.1 Ohm shunt resistor
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from .base import SensorBase
|
||||
from .registry import SensorRegistry
|
||||
|
||||
|
||||
@SensorRegistry.register("ina219")
|
||||
class INA219Sensor(SensorBase):
|
||||
sensor_type = "ina219"
|
||||
|
||||
def __init__(self, name: str, config: Optional[Dict[str, Any]] = None, log=None):
|
||||
super().__init__(name=name, config=config, log=log)
|
||||
|
||||
self.i2c_address = self.settings.get("i2c_address", 0x40)
|
||||
self.max_expected_amps = float(self.settings.get("max_expected_amps", 2.0))
|
||||
self.shunt_ohms = float(self.settings.get("shunt_ohms", 0.1))
|
||||
|
||||
self.available = False
|
||||
if not self.ensure_python_modules(
|
||||
[
|
||||
("board", "board"),
|
||||
("busio", "adafruit-blinka"),
|
||||
("adafruit_ina219", "adafruit-circuitpython-ina219"),
|
||||
]
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
import board # type: ignore[import-not-found]
|
||||
import busio # type: ignore[import-not-found]
|
||||
from adafruit_ina219 import Adafruit_INA219 # type: ignore[import-not-found]
|
||||
|
||||
# Create I2C interface
|
||||
i2c = busio.I2C(board.SCL, board.SDA)
|
||||
|
||||
# Create sensor object
|
||||
self.ina219 = Adafruit_INA219(
|
||||
i2c_addr=self.i2c_address,
|
||||
i2c=i2c,
|
||||
)
|
||||
|
||||
# Configure for expected current range
|
||||
self.ina219.set_calibration_32V_2A() if self.max_expected_amps <= 2.0 else None
|
||||
|
||||
self.available = True
|
||||
self.log.info(
|
||||
"INA219 initialized (addr=0x%02X, shunt=%.3fΩ, max_A=%.1f)",
|
||||
self.i2c_address,
|
||||
self.shunt_ohms,
|
||||
self.max_expected_amps,
|
||||
)
|
||||
except Exception as exc:
|
||||
self.log.warning(
|
||||
"INA219 init failed (addr=0x%02X): %s",
|
||||
self.i2c_address,
|
||||
exc,
|
||||
)
|
||||
self.available = False
|
||||
|
||||
def _read(self) -> Dict[str, Any]:
|
||||
"""Read voltage, current, and power from INA219."""
|
||||
if not self.available:
|
||||
raise RuntimeError("INA219 device not available")
|
||||
|
||||
try:
|
||||
return {
|
||||
"bus_voltage_v": round(self.ina219.bus_voltage, 3),
|
||||
"shunt_voltage_v": round(self.ina219.shunt_voltage, 4),
|
||||
"current_ma": round(self.ina219.current, 2),
|
||||
"power_mw": round(self.ina219.power, 2),
|
||||
}
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"INA219 read failed: {exc}") from exc
|
||||
@@ -0,0 +1,176 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import threading
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .registry import SensorRegistry
|
||||
|
||||
|
||||
class SensorManager:
|
||||
"""Load and read sensor plug-ins declared in config with background polling."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Dict[str, Any],
|
||||
*,
|
||||
log: Optional[logging.Logger] = None,
|
||||
registry: type[SensorRegistry] = SensorRegistry,
|
||||
):
|
||||
self.config = config if isinstance(config, dict) else {}
|
||||
self.log = log or logging.getLogger(self.__class__.__name__)
|
||||
self.registry = registry
|
||||
self.sensors = []
|
||||
|
||||
# Background polling
|
||||
self._poll_thread: Optional[threading.Thread] = None
|
||||
self._stop_event = threading.Event()
|
||||
self._latest_readings: List[Dict[str, Any]] = []
|
||||
self._readings_lock = threading.RLock()
|
||||
self._running = False
|
||||
|
||||
self.reload()
|
||||
|
||||
def _get_sensor_definitions(self) -> List[Dict[str, Any]]:
|
||||
"""Extract sensor definitions from config."""
|
||||
section = self.config.get("sensors", {})
|
||||
if not isinstance(section, dict):
|
||||
return []
|
||||
global_auto_install = bool(section.get("auto_install_packages", False))
|
||||
definitions = section.get("definitions") or section.get("sensors")
|
||||
if not isinstance(definitions, list):
|
||||
return []
|
||||
out: List[Dict[str, Any]] = []
|
||||
for d in definitions:
|
||||
if not isinstance(d, dict):
|
||||
continue
|
||||
entry = dict(d)
|
||||
entry.setdefault("auto_install_packages", global_auto_install)
|
||||
out.append(entry)
|
||||
return out
|
||||
|
||||
def _load_sensor_module(self, sensor_type: str) -> None:
|
||||
"""Import sensor module so @SensorRegistry.register decorators execute."""
|
||||
module_name = str(sensor_type).strip().lower().replace("-", "_")
|
||||
package_name = __name__.rsplit(".", 1)[0]
|
||||
importlib.import_module(f"{package_name}.{module_name}")
|
||||
|
||||
def reload(self) -> None:
|
||||
self.sensors = []
|
||||
for definition in self._get_sensor_definitions():
|
||||
if not definition.get("enabled", True):
|
||||
continue
|
||||
|
||||
sensor_type = definition.get("type")
|
||||
name = definition.get("name") or str(sensor_type or "sensor")
|
||||
if not sensor_type:
|
||||
self.log.warning("Skipping sensor definition %r: missing type", name)
|
||||
continue
|
||||
|
||||
try:
|
||||
self._load_sensor_module(sensor_type)
|
||||
sensor = self.registry.create(sensor_type, name=name, config=definition)
|
||||
except Exception as exc:
|
||||
self.log.warning("Skipping sensor %r of type %r: %s", name, sensor_type, exc)
|
||||
continue
|
||||
|
||||
self.sensors.append(sensor)
|
||||
|
||||
def start(self) -> None:
|
||||
if self._running:
|
||||
return
|
||||
self.reload()
|
||||
|
||||
# Start background polling thread if enabled and sensors exist
|
||||
section = self.config.get("sensors", {})
|
||||
if not isinstance(section, dict) or not section.get("enabled", False):
|
||||
self.log.debug("Sensor manager disabled in config")
|
||||
return
|
||||
|
||||
if not self.sensors:
|
||||
self.log.debug("No sensors loaded; skipping background polling")
|
||||
return
|
||||
|
||||
self._stop_event.clear()
|
||||
self._poll_thread = threading.Thread(
|
||||
target=self._poll_loop, name="sensor-manager", daemon=True
|
||||
)
|
||||
self._poll_thread.start()
|
||||
self._running = True
|
||||
self.log.info("Sensor manager polling started (%d sensors)", len(self.sensors))
|
||||
|
||||
def stop(self) -> None:
|
||||
if not self._running:
|
||||
return
|
||||
self._stop_event.set()
|
||||
if self._poll_thread and self._poll_thread.is_alive():
|
||||
self._poll_thread.join(timeout=2.0)
|
||||
self._running = False
|
||||
self.log.info("Sensor manager polling stopped")
|
||||
|
||||
def read_all(self) -> List[Dict[str, Any]]:
|
||||
readings: List[Dict[str, Any]] = []
|
||||
for sensor in self.sensors:
|
||||
try:
|
||||
readings.append(sensor.read())
|
||||
except Exception as exc:
|
||||
self.log.warning("Sensor manager caught read error for %s: %s", sensor.name, exc)
|
||||
readings.append(
|
||||
{
|
||||
"name": sensor.name,
|
||||
"type": getattr(sensor, "sensor_type", "sensor"),
|
||||
"ok": False,
|
||||
"timestamp": None,
|
||||
"data": {},
|
||||
"error": f"{type(exc).__name__}: {exc}",
|
||||
}
|
||||
)
|
||||
return readings
|
||||
|
||||
def _poll_loop(self) -> None:
|
||||
"""Background thread: poll sensors at configured interval and cache readings."""
|
||||
section = self.config.get("sensors", {})
|
||||
poll_interval = 30.0
|
||||
if isinstance(section, dict):
|
||||
try:
|
||||
poll_interval = float(section.get("poll_interval_seconds", 30.0))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
self.log.debug("Sensor polling loop started (interval=%.1f sec)", poll_interval)
|
||||
|
||||
while not self._stop_event.is_set():
|
||||
try:
|
||||
readings = self.read_all()
|
||||
with self._readings_lock:
|
||||
self._latest_readings = readings
|
||||
except Exception as exc:
|
||||
self.log.warning("Sensor poll cycle failed: %s", exc)
|
||||
|
||||
# Wait for next poll or stop signal
|
||||
self._stop_event.wait(poll_interval)
|
||||
|
||||
self.log.debug("Sensor polling loop stopped")
|
||||
|
||||
def get_summary(self) -> Dict[str, Any]:
|
||||
section = self.config.get("sensors", {})
|
||||
poll_interval = 30.0
|
||||
if isinstance(section, dict):
|
||||
try:
|
||||
poll_interval = float(section.get("poll_interval_seconds", 30.0))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
# Get cached readings (or empty list if not running)
|
||||
with self._readings_lock:
|
||||
readings = list(self._latest_readings) if self._latest_readings else []
|
||||
|
||||
return {
|
||||
"enabled": bool(isinstance(section, dict) and section.get("enabled", False)),
|
||||
"poll_interval_seconds": poll_interval,
|
||||
"configured": len(self._get_sensor_definitions()),
|
||||
"loaded": len(self.sensors),
|
||||
"running": self._running,
|
||||
"readings": readings,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
from .base import SensorBase
|
||||
|
||||
|
||||
class SensorRegistry:
|
||||
_factories: Dict[str, Callable[..., SensorBase]] = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls, sensor_type: str, factory: Optional[Callable[..., SensorBase]] = None):
|
||||
"""Register a sensor factory or class under a sensor type."""
|
||||
key = str(sensor_type).strip().lower()
|
||||
|
||||
def _decorator(target):
|
||||
cls._factories[key] = target
|
||||
return target
|
||||
|
||||
if factory is not None:
|
||||
cls._factories[key] = factory
|
||||
return factory
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
def create(cls, sensor_type: str, name: str, config: Optional[Dict[str, Any]] = None, **kwargs) -> SensorBase:
|
||||
key = str(sensor_type).strip().lower()
|
||||
factory = cls._factories.get(key)
|
||||
if factory is None:
|
||||
raise ValueError(f"Unknown sensor type: {sensor_type}")
|
||||
|
||||
if isinstance(factory, type) and issubclass(factory, SensorBase):
|
||||
return factory(name=name, config=config, **kwargs)
|
||||
return factory(name=name, config=config, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def available_types(cls) -> list[str]:
|
||||
return sorted(cls._factories)
|
||||
+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
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.ml-0[data-v-b31cd389]{margin-left:0}.ml-4[data-v-b31cd389]{margin-left:1rem}.ml-8[data-v-b31cd389]{margin-left:2rem}.ml-12[data-v-b31cd389]{margin-left:3rem}.ml-16[data-v-b31cd389]{margin-left:4rem}.ml-20[data-v-b31cd389]{margin-left:5rem}.ml-24[data-v-b31cd389]{margin-left:6rem}.ml-28[data-v-b31cd389]{margin-left:7rem}.ml-32[data-v-b31cd389]{margin-left:8rem}.dropdown-enter-active[data-v-f23b2d7c],.dropdown-leave-active[data-v-f23b2d7c]{transition:opacity .12s,transform .12s}.dropdown-enter-from[data-v-f23b2d7c],.dropdown-leave-to[data-v-f23b2d7c]{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-b3e04bd5]{background:linear-gradient(to right, var(--color-surface) 30%, transparent)}.tab-fade-right[data-v-b3e04bd5]{background:linear-gradient(to left, var(--color-surface) 30%, transparent)}.tab-fade-enter-active[data-v-b3e04bd5],.tab-fade-leave-active[data-v-b3e04bd5]{transition:opacity .2s}.tab-fade-enter-from[data-v-b3e04bd5],.tab-fade-leave-to[data-v-b3e04bd5]{opacity:0}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.ml-0[data-v-b31cd389]{margin-left:0}.ml-4[data-v-b31cd389]{margin-left:1rem}.ml-8[data-v-b31cd389]{margin-left:2rem}.ml-12[data-v-b31cd389]{margin-left:3rem}.ml-16[data-v-b31cd389]{margin-left:4rem}.ml-20[data-v-b31cd389]{margin-left:5rem}.ml-24[data-v-b31cd389]{margin-left:6rem}.ml-28[data-v-b31cd389]{margin-left:7rem}.ml-32[data-v-b31cd389]{margin-left:8rem}.dropdown-enter-active[data-v-f23b2d7c],.dropdown-leave-active[data-v-f23b2d7c]{transition:opacity .12s,transform .12s}.dropdown-enter-from[data-v-f23b2d7c],.dropdown-leave-to[data-v-f23b2d7c]{opacity:0;transform:translateY(-4px)}.expand-enter-active[data-v-218b986b],.expand-leave-active[data-v-218b986b]{transition:all .2s;overflow:hidden}.expand-enter-from[data-v-218b986b],.expand-leave-to[data-v-218b986b]{opacity:0;max-height:0}.expand-enter-to[data-v-218b986b],.expand-leave-from[data-v-218b986b]{opacity:1;max-height:2000px}.tab-fade-left[data-v-3a402227]{background:linear-gradient(to right, var(--color-surface) 30%, transparent)}.tab-fade-right[data-v-3a402227]{background:linear-gradient(to left, var(--color-surface) 30%, transparent)}.tab-fade-enter-active[data-v-3a402227],.tab-fade-leave-active[data-v-3a402227]{transition:opacity .2s}.tab-fade-enter-from[data-v-3a402227],.tab-fade-leave-to[data-v-3a402227]{opacity:0}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{E as e,c as t,g as n,gt as r,i,l as a,mt as o,s,u as c}from"./runtime-core.esm-bundler-BiwffnQU.js";import{_ as l}from"./index-Bwi6QvPf.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)=>(e(),t(i,{to:`body`},[b.show?(e(),c(`div`,{key:0,onClick:y[3]||=l(e=>x(`close`),[`self`]),class:`modal-backdrop`},[s(`div`,u,[s(`div`,d,[s(`h3`,f,r(b.title),1),s(`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]||=[s(`svg`,{class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[s(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`})],-1)]])]),s(`div`,p,[s(`div`,{class:o([`inline-flex p-3 rounded-xl mb-4`,S[b.variant]])},[b.variant===`danger`?(e(),c(`svg`,m,[...y[5]||=[s(`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`?(e(),c(`svg`,h,[...y[6]||=[s(`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)]])):(e(),c(`svg`,g,[...y[7]||=[s(`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),s(`p`,_,r(b.message),1)]),s(`div`,v,[s(`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`},r(b.cancelText),1),s(`button`,{onClick:y[2]||=e=>x(`confirm`),class:o([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,C[b.variant]])},r(b.confirmText),3)])])])):a(``,!0)]))}});export{y as t};
|
||||
import{E as e,c as t,g as n,gt as r,i,l as a,mt as o,s,u as c}from"./runtime-core.esm-bundler-BiwffnQU.js";import{_ as l}from"./index-T8oWhJNd.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)=>(e(),t(i,{to:`body`},[b.show?(e(),c(`div`,{key:0,onClick:y[3]||=l(e=>x(`close`),[`self`]),class:`modal-backdrop`},[s(`div`,u,[s(`div`,d,[s(`h3`,f,r(b.title),1),s(`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]||=[s(`svg`,{class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[s(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`})],-1)]])]),s(`div`,p,[s(`div`,{class:o([`inline-flex p-3 rounded-xl mb-4`,S[b.variant]])},[b.variant===`danger`?(e(),c(`svg`,m,[...y[5]||=[s(`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`?(e(),c(`svg`,h,[...y[6]||=[s(`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)]])):(e(),c(`svg`,g,[...y[7]||=[s(`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),s(`p`,_,r(b.message),1)]),s(`div`,v,[s(`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`},r(b.cancelText),1),s(`button`,{onClick:y[2]||=e=>x(`confirm`),class:o([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,C[b.variant]])},r(b.confirmText),3)])])])):a(``,!0)]))}});export{y as t};
|
||||
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
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.bg-gradient-light[data-v-a26c2589]{background:linear-gradient(#0d73774d,#aae8e833)}.bg-gradient-dark[data-v-a26c2589]{background:linear-gradient(#aae8e82e,#0d73771a)}.login-card[data-v-a26c2589]{-webkit-backdrop-filter:blur(40px)saturate(180%);background:#ffffffd9}.dark .login-card[data-v-a26c2589]{background:#1a1e1fcc}.input-glass[data-v-a26c2589]{-webkit-backdrop-filter:blur(20px);background:#ffffffe6;border:1px solid #d1d5db}.dark .input-glass[data-v-a26c2589]{background:#ffffff0d;border-color:#ffffff1a}.input-glass[data-v-a26c2589]:focus{background:#fff}.dark .input-glass[data-v-a26c2589]:focus{background:#ffffff1a}.input-glass[data-v-a26c2589]:focus{box-shadow:0 0 0 1px #aae8e833,0 0 20px #aae8e826,inset 0 1px #ffffff1a}.input-glow[data-v-a26c2589]{opacity:0;transition:opacity .3s;box-shadow:inset 0 1px #ffffff0d}.input-glass:focus+.input-glow[data-v-a26c2589]{opacity:1;box-shadow:0 0 20px #aae8e833,inset 0 1px #ffffff1a}.button-glass[data-v-a26c2589]{-webkit-backdrop-filter:blur(20px);position:relative}.button-glass[data-v-a26c2589]: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-a26c2589]:hover:not(:disabled):before{transform:translate(100%)}.button-glass[data-v-a26c2589]{box-shadow:0 0 0 1px #aae8e833,0 4px 16px #0003,inset 0 1px #ffffff1a}.button-glass[data-v-a26c2589]:hover:not(:disabled){box-shadow:0 0 0 1px #aae8e866,0 0 30px #aae8e84d,0 4px 20px #0000004d,inset 0 1px #ffffff26}@keyframes float-a26c2589{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes pulse-slow-a26c2589{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.05)}}@keyframes pulse-slower-a26c2589{0%,to{opacity:.75;transform:scale(1)}50%{opacity:.5;transform:scale(1.08)}}@keyframes pulse-slowest-a26c2589{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.06)}}.animate-pulse-slow[data-v-a26c2589]{animation:8s ease-in-out infinite pulse-slow-a26c2589}.animate-pulse-slower[data-v-a26c2589]{animation:10s ease-in-out infinite pulse-slower-a26c2589}.animate-pulse-slowest[data-v-a26c2589]{animation:12s ease-in-out infinite pulse-slowest-a26c2589}@keyframes shake-a26c2589{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-5px)}20%,40%,60%,80%{transform:translate(5px)}}.animate-shake[data-v-a26c2589]{animation:.5s ease-in-out shake-a26c2589}.form-group[data-v-a26c2589]{position:relative}.form-group:hover label[data-v-a26c2589]{color:#aae8e8e6;transition:color .3s}
|
||||
@@ -0,0 +1 @@
|
||||
.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}
|
||||
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
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{E as e,c as t,g as n,gt as r,i,l as a,mt as o,s,u as c}from"./runtime-core.esm-bundler-BiwffnQU.js";import{_ as l}from"./index-Bwi6QvPf.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,_)=>(e(),t(i,{to:`body`},[v.show?(e(),c(`div`,{key:0,onClick:_[1]||=l(e=>y(`close`),[`self`]),class:`modal-backdrop`},[s(`div`,u,[s(`div`,d,[s(`div`,{class:o([`inline-flex p-3 rounded-xl mb-4`,b[v.variant]])},[v.variant===`success`?(e(),c(`svg`,f,[..._[2]||=[s(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M5 13l4 4L19 7`},null,-1)]])):v.variant===`error`?(e(),c(`svg`,p,[..._[3]||=[s(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`},null,-1)]])):(e(),c(`svg`,m,[..._[4]||=[s(`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),s(`p`,h,r(v.message),1)]),s(`div`,g,[s(`button`,{onClick:_[0]||=e=>y(`close`),class:o([`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{E as e,c as t,g as n,gt as r,i,l as a,mt as o,s,u as c}from"./runtime-core.esm-bundler-BiwffnQU.js";import{_ as l}from"./index-T8oWhJNd.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,_)=>(e(),t(i,{to:`body`},[v.show?(e(),c(`div`,{key:0,onClick:_[1]||=l(e=>y(`close`),[`self`]),class:`modal-backdrop`},[s(`div`,u,[s(`div`,d,[s(`div`,{class:o([`inline-flex p-3 rounded-xl mb-4`,b[v.variant]])},[v.variant===`success`?(e(),c(`svg`,f,[..._[2]||=[s(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M5 13l4 4L19 7`},null,-1)]])):v.variant===`error`?(e(),c(`svg`,p,[..._[3]||=[s(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`},null,-1)]])):(e(),c(`svg`,m,[..._[4]||=[s(`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),s(`p`,h,r(v.message),1)]),s(`div`,g,[s(`button`,{onClick:_[0]||=e=>y(`close`),class:o([`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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{o as e}from"./index-T8oWhJNd.js";export{e as default};
|
||||
@@ -1 +0,0 @@
|
||||
import{o as e}from"./index-Bwi6QvPf.js";export{e as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{E as e,H as t,N as n,S as r,c as i,g as a,gt as o,i as s,j as c,l,m as u,r as d,s as f,u as p}from"./runtime-core.esm-bundler-BiwffnQU.js";import{t as m}from"./api-DRn_D8oU.js";import{_ as h,t as g,u as _}from"./index-Bwi6QvPf.js";var v={class:`bg-white dark:bg-[var(--color-surface-elevated)] rounded-xl shadow-xl border border-stroke-subtle dark:border-stroke/20 p-6 max-w-md w-full mx-4`},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=t(!1),k=t(!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 c(()=>E.modelValue,e=>{e||(O.value=!1,k.value=!1,A&&=(clearTimeout(A),null),j=0,M=0)}),r(()=>{A&&clearTimeout(A)}),(t,r)=>(e(),i(s,{to:`body`},[u(_,{"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:n(()=>[a.modelValue?(e(),p(`div`,{key:0,class:`fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm`,onClick:h(N,[`self`])},[f(`div`,v,[O.value?(e(),p(`div`,y,[u(g,{size:`lg`}),r[0]||=f(`div`,{class:`text-center`},[f(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Restarting… `),f(`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?(e(),p(d,{key:1},[r[1]||=f(`div`,{class:`flex items-start gap-3 mb-4`},[f(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center`},[f(`svg`,{class:`w-5 h-5 text-red-600 dark:text-red-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[f(`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`})])]),f(`div`,null,[f(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Service Did Not Restart `),f(`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),f(`div`,{class:`flex justify-end`},[f(`button`,{onClick:N,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`},` Dismiss `)])],64)):(e(),p(d,{key:2},[f(`div`,b,[r[2]||=f(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center`},[f(`svg`,{class:`w-5 h-5 text-amber-600 dark:text-amber-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[f(`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),f(`div`,null,[f(`h3`,x,o(a.title),1),f(`p`,S,o(a.message),1)])]),f(`div`,{class:`flex justify-end gap-2`},[f(`button`,{onClick:N,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`},` Cancel `),f(`button`,{onClick:P,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`},` Restart `)])],64))])])):l(``,!0)]),_:1})]))}});export{T as t};
|
||||
import{E as e,H as t,N as n,S as r,c as i,g as a,gt as o,i as s,j as c,l,m as u,r as d,s as f,u as p}from"./runtime-core.esm-bundler-BiwffnQU.js";import{t as m}from"./api-D2kWYBuR.js";import{_ as h,n as g,u as _}from"./index-T8oWhJNd.js";var v={class:`bg-white dark:bg-[var(--color-surface-elevated)] rounded-xl shadow-xl border border-stroke-subtle dark:border-stroke/20 p-6 max-w-md w-full mx-4`},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=t(!1),k=t(!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 c(()=>E.modelValue,e=>{e||(O.value=!1,k.value=!1,A&&=(clearTimeout(A),null),j=0,M=0)}),r(()=>{A&&clearTimeout(A)}),(t,r)=>(e(),i(s,{to:`body`},[u(_,{"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:n(()=>[a.modelValue?(e(),p(`div`,{key:0,class:`fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm`,onClick:h(N,[`self`])},[f(`div`,v,[O.value?(e(),p(`div`,y,[u(g,{size:`lg`}),r[0]||=f(`div`,{class:`text-center`},[f(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Restarting… `),f(`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?(e(),p(d,{key:1},[r[1]||=f(`div`,{class:`flex items-start gap-3 mb-4`},[f(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center`},[f(`svg`,{class:`w-5 h-5 text-red-600 dark:text-red-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[f(`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`})])]),f(`div`,null,[f(`h3`,{class:`text-base font-semibold text-content-primary dark:text-content-primary`},` Service Did Not Restart `),f(`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),f(`div`,{class:`flex justify-end`},[f(`button`,{onClick:N,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`},` Dismiss `)])],64)):(e(),p(d,{key:2},[f(`div`,b,[r[2]||=f(`div`,{class:`flex-shrink-0 w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center`},[f(`svg`,{class:`w-5 h-5 text-amber-600 dark:text-amber-400`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[f(`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),f(`div`,null,[f(`h3`,x,o(a.title),1),f(`p`,S,o(a.message),1)])]),f(`div`,{class:`flex justify-end gap-2`},[f(`button`,{onClick:N,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`},` Cancel `),f(`button`,{onClick:P,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`},` Restart `)])],64))])])):l(``,!0)]),_:1})]))}});export{T as t};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{E as e,O as t,g as n,gt as r,l as i,mt as a,o,r as s,s as c,u as l}from"./runtime-core.esm-bundler-BiwffnQU.js";import{t as u}from"./system--i3xLloC.js";import{t as d}from"./index-T8oWhJNd.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=n({name:`SensorsView`,__name:`Sensors`,setup(n){let M=u(),N=o(()=>M.stats?.sensors??null),P=o(()=>N.value?.readings??[]),F=o(()=>{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}),(n,o)=>(e(),l(`div`,f,[c(`div`,p,[c(`div`,{class:`flex items-start justify-between gap-4`},[o[0]||=c(`div`,null,[c(`h1`,{class:`text-xl sm:text-2xl font-semibold text-content-heading dark:text-white`},`Sensors`),c(`p`,{class:`mt-1 text-sm text-content-muted`},` Live sensor summary from the existing stats API. `)],-1),c(`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 `)]),c(`div`,m,[(e(!0),l(s,null,t(F.value,t=>(e(),l(`div`,{key:t.label,class:`rounded-[12px] border border-stroke-subtle dark:border-white/10 p-3`},[c(`p`,h,r(t.label),1),c(`p`,g,r(t.value),1)]))),128))])]),N.value?i(``,!0):(e(),l(`div`,_,` Sensor data is not available yet. Ensure the repeater has started and stats are loading. `)),(e(!0),l(s,null,t(P.value,(n,u)=>(e(),l(`div`,{key:`${n.name||`sensor`}-${u}`,class:`glass-card rounded-[15px] p-4 sm:p-5`},[c(`div`,v,[c(`div`,null,[c(`h2`,y,r(n.name||`Sensor ${u+1}`),1),c(`p`,b,`Type: `+r(n.type||`unknown`),1)]),c(`span`,{class:a([`rounded-full px-3 py-1 text-xs font-semibold`,n.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`])},r(n.ok?`OK`:`Error`),3)]),c(`div`,x,[c(`div`,S,[o[1]||=c(`span`,{class:`text-content-muted`},`Timestamp:`,-1),c(`span`,C,r(L(n.timestamp)),1)]),n.error?(e(),l(`div`,w,[o[2]||=c(`span`,{class:`text-content-muted`},`Error:`,-1),c(`span`,T,r(n.error),1)])):i(``,!0)]),c(`div`,E,[c(`table`,D,[o[4]||=c(`thead`,{class:`bg-black/5 dark:bg-white/5`},[c(`tr`,null,[c(`th`,{class:`px-3 py-2 text-left text-content-muted`},`Field`),c(`th`,{class:`px-3 py-2 text-left text-content-muted`},`Value`)])],-1),c(`tbody`,null,[(e(!0),l(s,null,t(n.data||{},(t,n)=>(e(),l(`tr`,{key:String(n),class:`border-t border-stroke-subtle dark:border-white/10`},[c(`td`,O,r(n),1),c(`td`,k,r(I(t)),1)]))),128)),!n.data||Object.keys(n.data).length===0?(e(),l(`tr`,A,[...o[3]||=[c(`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?(e(),l(`div`,j,` Sensors are configured but no readings are available yet. `)):i(``,!0)]))}});export{M as default};
|
||||
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
@@ -1 +0,0 @@
|
||||
.glass-card[data-v-c3cf9234]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffff0d;border:1px solid #ffffff1a}.modal-enter-active[data-v-c3cf9234],.modal-leave-active[data-v-c3cf9234]{transition:opacity .3s}.modal-enter-from[data-v-c3cf9234],.modal-leave-to[data-v-c3cf9234]{opacity:0}.modal-enter-active .glass-card[data-v-c3cf9234],.modal-leave-active .glass-card[data-v-c3cf9234]{transition:transform .3s}.modal-enter-from .glass-card[data-v-c3cf9234],.modal-leave-to .glass-card[data-v-c3cf9234]{transform:scale(.9)}.slide-enter-active[data-v-c3cf9234],.slide-leave-active[data-v-c3cf9234]{transition:all .3s}.slide-enter-from[data-v-c3cf9234],.slide-leave-to[data-v-c3cf9234]{opacity:0;transform:translateY(-10px)}@keyframes float-slow-c3cf9234{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-c3cf9234{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-c3cf9234{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-c3cf9234]{will-change:transform, opacity;animation:15s ease-in-out infinite float-slow-c3cf9234}.animate-pulse-slower[data-v-c3cf9234]{will-change:transform, opacity;animation:18s ease-in-out infinite float-slower-c3cf9234}.animate-pulse-slowest[data-v-c3cf9234]{will-change:transform, opacity;animation:20s ease-in-out infinite float-slowest-c3cf9234}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.glass-card[data-v-923df164]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffff0d;border:1px solid #ffffff1a}.modal-enter-active[data-v-923df164],.modal-leave-active[data-v-923df164]{transition:opacity .3s}.modal-enter-from[data-v-923df164],.modal-leave-to[data-v-923df164]{opacity:0}.modal-enter-active .glass-card[data-v-923df164],.modal-leave-active .glass-card[data-v-923df164]{transition:transform .3s}.modal-enter-from .glass-card[data-v-923df164],.modal-leave-to .glass-card[data-v-923df164]{transform:scale(.9)}.slide-enter-active[data-v-923df164],.slide-leave-active[data-v-923df164]{transition:all .3s}.slide-enter-from[data-v-923df164],.slide-leave-to[data-v-923df164]{opacity:0;transform:translateY(-10px)}@keyframes float-slow-923df164{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-923df164{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-923df164{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-923df164]{will-change:transform, opacity;animation:15s ease-in-out infinite float-slow-923df164}.animate-pulse-slower[data-v-923df164]{will-change:transform, opacity;animation:18s ease-in-out infinite float-slower-923df164}.animate-pulse-slowest[data-v-923df164]{will-change:transform, opacity;animation:20s ease-in-out infinite float-slowest-923df164}
|
||||
@@ -0,0 +1 @@
|
||||
.plotly-chart[data-v-3467abe2]{background:0 0!important}
|
||||
@@ -1 +0,0 @@
|
||||
.plotly-chart[data-v-41e22744]{background:0 0!important}
|
||||
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
@@ -1 +0,0 @@
|
||||
.glass-card[data-v-f0591faf]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffffbf;border:1px solid #0000000f;box-shadow:0 2px 8px #0000000a}.dark .glass-card[data-v-f0591faf]{box-shadow:none;background:#0000004d;border:1px solid #ffffff1a}.chart-updating[data-v-f0591faf]{animation:.8s ease-in-out subtle-pulse-f0591faf}@keyframes subtle-pulse-f0591faf{0%{transform:scale(1)}50%{transform:scale(1.02)}to{transform:scale(1)}}.chart-container[data-v-f0591faf]{transition:all .3s;position:relative}.chart-container[data-v-f0591faf]:hover{background:#0000000a}.dark .chart-container[data-v-f0591faf]:hover{background:#ffffff14}.process-row[data-v-f0591faf]{transition:all .3s}.process-row[data-v-f0591faf]:hover{background:#00000005;transform:translate(2px)}.dark .process-row[data-v-f0591faf]:hover{background:#ffffff0d}.process-row-enter-active[data-v-f0591faf],.process-row-leave-active[data-v-f0591faf]{transition:all .4s}.process-row-enter-from[data-v-f0591faf]{opacity:0;transform:translateY(-10px)scale(.95)}.process-row-leave-to[data-v-f0591faf]{opacity:0;transform:translateY(10px)scale(.95)}.process-row-move[data-v-f0591faf]{transition:transform .4s}.cpu-value[data-v-f0591faf],.memory-value[data-v-f0591faf]{border-radius:4px;padding:2px 6px;transition:all .3s}.cpu-value[data-v-f0591faf]:hover,.memory-value[data-v-f0591faf]:hover{background:#f59e0b1a;transform:scale(1.05)}@keyframes value-update-f0591faf{0%{background:#f59e0b4d}to{background:0 0}}.value-updated[data-v-f0591faf]{animation:.6s ease-out value-update-f0591faf}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.glass-card[data-v-1b55faef]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffffbf;border:1px solid #0000000f;box-shadow:0 2px 8px #0000000a}.dark .glass-card[data-v-1b55faef]{box-shadow:none;background:#0000004d;border:1px solid #ffffff1a}.chart-updating[data-v-1b55faef]{animation:.8s ease-in-out subtle-pulse-1b55faef}@keyframes subtle-pulse-1b55faef{0%{transform:scale(1)}50%{transform:scale(1.02)}to{transform:scale(1)}}.chart-container[data-v-1b55faef]{transition:all .3s;position:relative}.chart-container[data-v-1b55faef]:hover{background:#0000000a}.dark .chart-container[data-v-1b55faef]:hover{background:#ffffff14}.process-row[data-v-1b55faef]{transition:all .3s}.process-row[data-v-1b55faef]:hover{background:#00000005;transform:translate(2px)}.dark .process-row[data-v-1b55faef]:hover{background:#ffffff0d}.process-row-enter-active[data-v-1b55faef],.process-row-leave-active[data-v-1b55faef]{transition:all .4s}.process-row-enter-from[data-v-1b55faef]{opacity:0;transform:translateY(-10px)scale(.95)}.process-row-leave-to[data-v-1b55faef]{opacity:0;transform:translateY(10px)scale(.95)}.process-row-move[data-v-1b55faef]{transition:transform .4s}.cpu-value[data-v-1b55faef],.memory-value[data-v-1b55faef]{border-radius:4px;padding:2px 6px;transition:all .3s}.cpu-value[data-v-1b55faef]:hover,.memory-value[data-v-1b55faef]:hover{background:#f59e0b1a;transform:scale(1.05)}@keyframes value-update-1b55faef{0%{background:#f59e0b4d}to{background:0 0}}.value-updated[data-v-1b55faef]{animation:.6s ease-out value-update-1b55faef}
|
||||
+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
@@ -1 +1 @@
|
||||
import{H as e,V as t,o as n}from"./runtime-core.esm-bundler-BiwffnQU.js";import{n as r}from"./pinia-IvTtuI9d.js";import{t as i}from"./api-DRn_D8oU.js";import{t as a}from"./packets-D3t50H8n.js";import{t as o}from"./system-DN2LtoaR.js";var s={0:`Unknown`,1:`Chat Node`,2:`Repeater`,3:`Room Server`,4:`Hybrid Node`},c=r(`neighbors`,()=>{let t=e({}),r=e(!1),a=e(null),o=e(48),c=n(()=>Object.values(t.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(e=o.value){r.value=!0,o.value=e;let n=Object.entries(s),c=await Promise.allSettled(n.map(async([t,n])=>{try{let r=[],a=0,o=0;for(;o<200;){let t=await i.get(`/adverts_by_contact_type?contact_type=${encodeURIComponent(n)}&hours=${e}&limit=500&offset=${a}`),s=t.success&&Array.isArray(t.data)?t.data:[];if(s.length===0||(r.push(...s),s.length<500))break;a+=500,o+=1}return{typeKey:t,adverts:r}}catch{return{typeKey:t,adverts:[]}}})),l={};for(let e of c)e.status===`fulfilled`&&e.value.adverts.length>0&&(l[e.value.typeKey]=e.value.adverts);t.value=l,a.value=Date.now(),r.value=!1}function f(){t.value={},r.value=!1,a.value=null,o.value=48}return{advertsByType:t,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=e({currentTier:`unknown`,advertsAllowed:0,advertsDropped:0,activePenalties:0}),d=e(!1),f=e(null),p=t({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};
|
||||
import{H as e,V as t,o as n}from"./runtime-core.esm-bundler-BiwffnQU.js";import{n as r}from"./pinia-IvTtuI9d.js";import{t as i}from"./api-D2kWYBuR.js";import{t as a}from"./packets-BzRHEedS.js";import{t as o}from"./system--i3xLloC.js";var s={0:`Unknown`,1:`Chat Node`,2:`Repeater`,3:`Room Server`,4:`Hybrid Node`},c=r(`neighbors`,()=>{let t=e({}),r=e(!1),a=e(null),o=e(48),c=n(()=>Object.values(t.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(e=o.value){r.value=!0,o.value=e;let n=Object.entries(s),c=await Promise.allSettled(n.map(async([t,n])=>{try{let r=[],a=0,o=0;for(;o<200;){let t=await i.get(`/adverts_by_contact_type?contact_type=${encodeURIComponent(n)}&hours=${e}&limit=500&offset=${a}`),s=t.success&&Array.isArray(t.data)?t.data:[];if(s.length===0||(r.push(...s),s.length<500))break;a+=500,o+=1}return{typeKey:t,adverts:r}}catch{return{typeKey:t,adverts:[]}}})),l={};for(let e of c)e.status===`fulfilled`&&e.value.adverts.length>0&&(l[e.value.typeKey]=e.value.adverts);t.value=l,a.value=Date.now(),r.value=!1}function f(){t.value={},r.value=!1,a.value=null,o.value=48}return{advertsByType:t,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=e({currentTier:`unknown`,advertsAllowed:0,advertsDropped:0,activePenalties:0}),d=e(!1),f=e(null),p=t({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
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: 612 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 806 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{t as e}from"./packets-D3t50H8n.js";export{e as usePacketStore};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./packets-BzRHEedS.js";export{e as usePacketStore};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{H as e,o as t}from"./runtime-core.esm-bundler-BiwffnQU.js";import{n}from"./pinia-IvTtuI9d.js";import{n as r,t as i}from"./api-DRn_D8oU.js";import{t as a}from"./packets-D3t50H8n.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{H as e,o as t}from"./runtime-core.esm-bundler-BiwffnQU.js";import{n}from"./pinia-IvTtuI9d.js";import{n as r,t as i}from"./api-D2kWYBuR.js";import{t as a}from"./packets-BzRHEedS.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};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./system-DN2LtoaR.js";export{e as useSystemStore};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./system--i3xLloC.js";export{e as useSystemStore};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{o as e}from"./runtime-core.esm-bundler-BiwffnQU.js";import{t}from"./system-DN2LtoaR.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-BiwffnQU.js";import{t}from"./system--i3xLloC.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};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./websocket-BBUHaGQm.js";export{e as useWebSocketStore};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{H as e,o as t}from"./runtime-core.esm-bundler-BiwffnQU.js";import{n}from"./pinia-IvTtuI9d.js";import{c as r,f as i,i as a,l as o}from"./api-DRn_D8oU.js";import{t as s}from"./packets-D3t50H8n.js";import{t as c}from"./system-DN2LtoaR.js";import{t as l}from"./dataService-C7SHAYZL.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{H as e,o as t}from"./runtime-core.esm-bundler-BiwffnQU.js";import{n}from"./pinia-IvTtuI9d.js";import{c as r,f as i,i as a,l as o}from"./api-D2kWYBuR.js";import{t as s}from"./packets-BzRHEedS.js";import{t as c}from"./system--i3xLloC.js";import{t as l}from"./dataService-CmHmU2Vk.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};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./websocket-CiIDRmrE.js";export{e as useWebSocketStore};
|
||||
@@ -8,18 +8,18 @@
|
||||
<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-Bwi6QvPf.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-T8oWhJNd.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-BiwffnQU.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pinia-IvTtuI9d.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/api-DRn_D8oU.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/api-D2kWYBuR.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/useTheme-CZ2Aak59.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/packets-D3t50H8n.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/system-DN2LtoaR.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/dataService-C7SHAYZL.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/websocket-CiIDRmrE.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CDyznwmx.css">
|
||||
<link rel="modulepreload" crossorigin href="/assets/packets-BzRHEedS.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/system--i3xLloC.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/dataService-CmHmU2Vk.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/websocket-BBUHaGQm.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-QD4zKXF9.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
from repeater.sensors import SensorBase, SensorConfigManager, SensorManager, SensorRegistry
|
||||
|
||||
|
||||
class _DummySensor(SensorBase):
|
||||
sensor_type = "dummy"
|
||||
|
||||
def _read(self):
|
||||
return {"value": self.settings.get("value", 0)}
|
||||
|
||||
|
||||
class _FailingSensor(SensorBase):
|
||||
sensor_type = "failing"
|
||||
|
||||
def _read(self):
|
||||
raise RuntimeError("boom")
|
||||
|
||||
|
||||
SensorRegistry.register("dummy", _DummySensor)
|
||||
SensorRegistry.register("failing", _FailingSensor)
|
||||
|
||||
|
||||
def test_sensor_config_manager_reads_sensor_definitions():
|
||||
config = {
|
||||
"sensors": {
|
||||
"enabled": True,
|
||||
"poll_interval_seconds": 12,
|
||||
"definitions": [
|
||||
{"name": "demo", "type": "dummy", "settings": {"value": 7}},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
manager = SensorConfigManager(config)
|
||||
|
||||
assert manager.is_enabled() is True
|
||||
assert manager.get_poll_interval_seconds() == 12.0
|
||||
assert manager.get_definitions()[0]["type"] == "dummy"
|
||||
|
||||
|
||||
def test_sensor_manager_loads_and_reads_sensors_without_stopping_on_failure():
|
||||
config = {
|
||||
"sensors": {
|
||||
"enabled": True,
|
||||
"definitions": [
|
||||
{"name": "good", "type": "dummy", "settings": {"value": 11}},
|
||||
{"name": "bad", "type": "failing"},
|
||||
{"name": "skipped", "type": "missing", "enabled": True},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
manager = SensorManager(config)
|
||||
|
||||
summary = manager.get_summary()
|
||||
assert summary["configured"] == 3
|
||||
assert summary["loaded"] == 2
|
||||
|
||||
readings = manager.read_all()
|
||||
assert len(readings) == 2
|
||||
assert readings[0]["ok"] is True
|
||||
assert readings[0]["data"]["value"] == 11
|
||||
assert readings[1]["ok"] is False
|
||||
assert readings[1]["error"].startswith("RuntimeError:")
|
||||
Reference in New Issue
Block a user